1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // HTTP Response reading and parsing.
20 var respExcludeHeader = map[string]bool{
21 "Content-Length": true,
22 "Transfer-Encoding": true,
26 // Response represents the response from an HTTP request.
28 type Response struct {
29 Status string // e.g. "200 OK"
30 StatusCode int // e.g. 200
31 Proto string // e.g. "HTTP/1.0"
32 ProtoMajor int // e.g. 1
33 ProtoMinor int // e.g. 0
35 // RequestMethod records the method used in the HTTP request.
36 // Header fields such as Content-Length have method-specific meaning.
37 RequestMethod string // e.g. "HEAD", "CONNECT", "GET", etc.
39 // Header maps header keys to values. If the response had multiple
40 // headers with the same key, they will be concatenated, with comma
41 // delimiters. (Section 4.2 of RFC 2616 requires that multiple headers
42 // be semantically equivalent to a comma-delimited sequence.) Values
43 // duplicated by other fields in this struct (e.g., ContentLength) are
44 // omitted from Header.
46 // Keys in the map are canonicalized (see CanonicalHeaderKey).
49 // SetCookie records the Set-Cookie requests sent with the response.
52 // Body represents the response body.
55 // ContentLength records the length of the associated content. The
56 // value -1 indicates that the length is unknown. Unless RequestMethod
57 // is "HEAD", values >= 0 indicate that the given number of bytes may
61 // Contains transfer encodings from outer-most to inner-most. Value is
62 // nil, means that "identity" encoding is used.
63 TransferEncoding []string
65 // Close records whether the header directed that the connection be
66 // closed after reading Body. The value is advice for clients: neither
67 // ReadResponse nor Response.Write ever closes a connection.
70 // Trailer maps trailer keys to values, in the same
71 // format as the header.
75 // ReadResponse reads and returns an HTTP response from r. The RequestMethod
76 // parameter specifies the method used in the corresponding request (e.g.,
77 // "GET", "HEAD"). Clients must call resp.Body.Close when finished reading
78 // resp.Body. After that call, clients can inspect resp.Trailer to find
79 // key/value pairs included in the response trailer.
80 func ReadResponse(r *bufio.Reader, requestMethod string) (resp *Response, err os.Error) {
82 tp := textproto.NewReader(r)
85 resp.RequestMethod = strings.ToUpper(requestMethod)
87 // Parse the first line of the response.
88 line, err := tp.ReadLine()
91 err = io.ErrUnexpectedEOF
95 f := strings.Split(line, " ", 3)
97 return nil, &badStringError{"malformed HTTP response", line}
103 resp.Status = f[1] + " " + reasonPhrase
104 resp.StatusCode, err = strconv.Atoi(f[1])
106 return nil, &badStringError{"malformed HTTP status code", f[1]}
111 if resp.ProtoMajor, resp.ProtoMinor, ok = ParseHTTPVersion(resp.Proto); !ok {
112 return nil, &badStringError{"malformed HTTP version", resp.Proto}
115 // Parse the response headers.
116 mimeHeader, err := tp.ReadMIMEHeader()
120 resp.Header = Header(mimeHeader)
122 fixPragmaCacheControl(resp.Header)
124 err = readTransfer(resp, r)
129 resp.SetCookie = readSetCookies(resp.Header)
134 // RFC2616: Should treat
137 // Cache-Control: no-cache
138 func fixPragmaCacheControl(header Header) {
139 if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" {
140 if _, presentcc := header["Cache-Control"]; !presentcc {
141 header["Cache-Control"] = []string{"no-cache"}
146 // ProtoAtLeast returns whether the HTTP protocol used
147 // in the response is at least major.minor.
148 func (r *Response) ProtoAtLeast(major, minor int) bool {
149 return r.ProtoMajor > major ||
150 r.ProtoMajor == major && r.ProtoMinor >= minor
153 // Writes the response (header, body and trailer) in wire format. This method
154 // consults the following fields of resp:
164 // Header, values for non-canonical keys will have unpredictable behavior
166 func (resp *Response) Write(w io.Writer) os.Error {
168 // RequestMethod should be upper-case
169 resp.RequestMethod = strings.ToUpper(resp.RequestMethod)
175 text, ok = statusText[resp.StatusCode]
177 text = "status code " + strconv.Itoa(resp.StatusCode)
180 io.WriteString(w, "HTTP/"+strconv.Itoa(resp.ProtoMajor)+".")
181 io.WriteString(w, strconv.Itoa(resp.ProtoMinor)+" ")
182 io.WriteString(w, strconv.Itoa(resp.StatusCode)+" "+text+"\r\n")
184 // Process Body,ContentLength,Close,Trailer
185 tw, err := newTransferWriter(resp)
189 err = tw.WriteHeader(w)
195 err = writeSortedHeader(w, resp.Header, respExcludeHeader)
200 if err = writeSetCookies(w, resp.SetCookie); err != nil {
205 io.WriteString(w, "\r\n")
207 // Write body and trailer
208 err = tw.WriteBody(w)
217 func writeSortedHeader(w io.Writer, h Header, exclude map[string]bool) os.Error {
218 keys := make([]string, 0, len(h))
220 if exclude == nil || !exclude[k] {
221 keys = append(keys, k)
224 sort.SortStrings(keys)
225 for _, k := range keys {
226 for _, v := range h[k] {
227 v = strings.Replace(v, "\n", " ", -1)
228 v = strings.Replace(v, "\r", " ", -1)
229 v = strings.TrimSpace(v)
233 if _, err := fmt.Fprintf(w, "%s: %s\r\n", k, v); err != nil {