Source file src/pkg/net/http/response.go
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.
4
5 // HTTP Response reading and parsing.
6
7 package http
8
9 import (
10 "bufio"
11 "errors"
12 "io"
13 "net/textproto"
14 "net/url"
15 "strconv"
16 "strings"
17 )
18
19 var respExcludeHeader = map[string]bool{
20 "Content-Length": true,
21 "Transfer-Encoding": true,
22 "Trailer": true,
23 }
24
25 // Response represents the response from an HTTP request.
26 //
27 type Response struct {
28 Status string // e.g. "200 OK"
29 StatusCode int // e.g. 200
30 Proto string // e.g. "HTTP/1.0"
31 ProtoMajor int // e.g. 1
32 ProtoMinor int // e.g. 0
33
34 // Header maps header keys to values. If the response had multiple
35 // headers with the same key, they will be concatenated, with comma
36 // delimiters. (Section 4.2 of RFC 2616 requires that multiple headers
37 // be semantically equivalent to a comma-delimited sequence.) Values
38 // duplicated by other fields in this struct (e.g., ContentLength) are
39 // omitted from Header.
40 //
41 // Keys in the map are canonicalized (see CanonicalHeaderKey).
42 Header Header
43
44 // Body represents the response body.
45 //
46 // The http Client and Transport guarantee that Body is always
47 // non-nil, even on responses without a body or responses with
48 // a zero-lengthed body.
49 Body io.ReadCloser
50
51 // ContentLength records the length of the associated content. The
52 // value -1 indicates that the length is unknown. Unless RequestMethod
53 // is "HEAD", values >= 0 indicate that the given number of bytes may
54 // be read from Body.
55 ContentLength int64
56
57 // Contains transfer encodings from outer-most to inner-most. Value is
58 // nil, means that "identity" encoding is used.
59 TransferEncoding []string
60
61 // Close records whether the header directed that the connection be
62 // closed after reading Body. The value is advice for clients: neither
63 // ReadResponse nor Response.Write ever closes a connection.
64 Close bool
65
66 // Trailer maps trailer keys to values, in the same
67 // format as the header.
68 Trailer Header
69
70 // The Request that was sent to obtain this Response.
71 // Request's Body is nil (having already been consumed).
72 // This is only populated for Client requests.
73 Request *Request
74 }
75
76 // Cookies parses and returns the cookies set in the Set-Cookie headers.
77 func (r *Response) Cookies() []*Cookie {
78 return readSetCookies(r.Header)
79 }
80
81 var ErrNoLocation = errors.New("http: no Location header in response")
82
83 // Location returns the URL of the response's "Location" header,
84 // if present. Relative redirects are resolved relative to
85 // the Response's Request. ErrNoLocation is returned if no
86 // Location header is present.
87 func (r *Response) Location() (*url.URL, error) {
88 lv := r.Header.Get("Location")
89 if lv == "" {
90 return nil, ErrNoLocation
91 }
92 if r.Request != nil && r.Request.URL != nil {
93 return r.Request.URL.Parse(lv)
94 }
95 return url.Parse(lv)
96 }
97
98 // ReadResponse reads and returns an HTTP response from r. The
99 // req parameter specifies the Request that corresponds to
100 // this Response. Clients must call resp.Body.Close when finished
101 // reading resp.Body. After that call, clients can inspect
102 // resp.Trailer to find key/value pairs included in the response
103 // trailer.
104 func ReadResponse(r *bufio.Reader, req *Request) (resp *Response, err error) {
105
106 tp := textproto.NewReader(r)
107 resp = new(Response)
108
109 resp.Request = req
110 resp.Request.Method = strings.ToUpper(resp.Request.Method)
111
112 // Parse the first line of the response.
113 line, err := tp.ReadLine()
114 if err != nil {
115 if err == io.EOF {
116 err = io.ErrUnexpectedEOF
117 }
118 return nil, err
119 }
120 f := strings.SplitN(line, " ", 3)
121 if len(f) < 2 {
122 return nil, &badStringError{"malformed HTTP response", line}
123 }
124 reasonPhrase := ""
125 if len(f) > 2 {
126 reasonPhrase = f[2]
127 }
128 resp.Status = f[1] + " " + reasonPhrase
129 resp.StatusCode, err = strconv.Atoi(f[1])
130 if err != nil {
131 return nil, &badStringError{"malformed HTTP status code", f[1]}
132 }
133
134 resp.Proto = f[0]
135 var ok bool
136 if resp.ProtoMajor, resp.ProtoMinor, ok = ParseHTTPVersion(resp.Proto); !ok {
137 return nil, &badStringError{"malformed HTTP version", resp.Proto}
138 }
139
140 // Parse the response headers.
141 mimeHeader, err := tp.ReadMIMEHeader()
142 if err != nil {
143 return nil, err
144 }
145 resp.Header = Header(mimeHeader)
146
147 fixPragmaCacheControl(resp.Header)
148
149 err = readTransfer(resp, r)
150 if err != nil {
151 return nil, err
152 }
153
154 return resp, nil
155 }
156
157 // RFC2616: Should treat
158 // Pragma: no-cache
159 // like
160 // Cache-Control: no-cache
161 func fixPragmaCacheControl(header Header) {
162 if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" {
163 if _, presentcc := header["Cache-Control"]; !presentcc {
164 header["Cache-Control"] = []string{"no-cache"}
165 }
166 }
167 }
168
169 // ProtoAtLeast returns whether the HTTP protocol used
170 // in the response is at least major.minor.
171 func (r *Response) ProtoAtLeast(major, minor int) bool {
172 return r.ProtoMajor > major ||
173 r.ProtoMajor == major && r.ProtoMinor >= minor
174 }
175
176 // Writes the response (header, body and trailer) in wire format. This method
177 // consults the following fields of the response:
178 //
179 // StatusCode
180 // ProtoMajor
181 // ProtoMinor
182 // RequestMethod
183 // TransferEncoding
184 // Trailer
185 // Body
186 // ContentLength
187 // Header, values for non-canonical keys will have unpredictable behavior
188 //
189 func (r *Response) Write(w io.Writer) error {
190
191 // RequestMethod should be upper-case
192 if r.Request != nil {
193 r.Request.Method = strings.ToUpper(r.Request.Method)
194 }
195
196 // Status line
197 text := r.Status
198 if text == "" {
199 var ok bool
200 text, ok = statusText[r.StatusCode]
201 if !ok {
202 text = "status code " + strconv.Itoa(r.StatusCode)
203 }
204 }
205 io.WriteString(w, "HTTP/"+strconv.Itoa(r.ProtoMajor)+".")
206 io.WriteString(w, strconv.Itoa(r.ProtoMinor)+" ")
207 io.WriteString(w, strconv.Itoa(r.StatusCode)+" "+text+"\r\n")
208
209 // Process Body,ContentLength,Close,Trailer
210 tw, err := newTransferWriter(r)
211 if err != nil {
212 return err
213 }
214 err = tw.WriteHeader(w)
215 if err != nil {
216 return err
217 }
218
219 // Rest of header
220 err = r.Header.WriteSubset(w, respExcludeHeader)
221 if err != nil {
222 return err
223 }
224
225 // End-of-header
226 io.WriteString(w, "\r\n")
227
228 // Write body and trailer
229 err = tw.WriteBody(w)
230 if err != nil {
231 return err
232 }
233
234 // Success
235 return nil
236 }