OSDN Git Service

Update to current version of Go library.
[pf3gnuchains/gcc-fork.git] / libgo / go / 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         "fmt"
12         "io"
13         "net/textproto"
14         "os"
15         "sort"
16         "strconv"
17         "strings"
18 )
19
20 var respExcludeHeader = map[string]bool{
21         "Content-Length":    true,
22         "Transfer-Encoding": true,
23         "Trailer":           true,
24 }
25
26 // Response represents the response from an HTTP request.
27 //
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
34
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.
38
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.
45         //
46         // Keys in the map are canonicalized (see CanonicalHeaderKey).
47         Header Header
48
49         // SetCookie records the Set-Cookie requests sent with the response.
50         SetCookie []*Cookie
51
52         // Body represents the response body.
53         Body io.ReadCloser
54
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
58         // be read from Body.
59         ContentLength int64
60
61         // Contains transfer encodings from outer-most to inner-most. Value is
62         // nil, means that "identity" encoding is used.
63         TransferEncoding []string
64
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.
68         Close bool
69
70         // Trailer maps trailer keys to values, in the same
71         // format as the header.
72         Trailer Header
73 }
74
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) {
81
82         tp := textproto.NewReader(r)
83         resp = new(Response)
84
85         resp.RequestMethod = strings.ToUpper(requestMethod)
86
87         // Parse the first line of the response.
88         line, err := tp.ReadLine()
89         if err != nil {
90                 if err == os.EOF {
91                         err = io.ErrUnexpectedEOF
92                 }
93                 return nil, err
94         }
95         f := strings.Split(line, " ", 3)
96         if len(f) < 2 {
97                 return nil, &badStringError{"malformed HTTP response", line}
98         }
99         reasonPhrase := ""
100         if len(f) > 2 {
101                 reasonPhrase = f[2]
102         }
103         resp.Status = f[1] + " " + reasonPhrase
104         resp.StatusCode, err = strconv.Atoi(f[1])
105         if err != nil {
106                 return nil, &badStringError{"malformed HTTP status code", f[1]}
107         }
108
109         resp.Proto = f[0]
110         var ok bool
111         if resp.ProtoMajor, resp.ProtoMinor, ok = ParseHTTPVersion(resp.Proto); !ok {
112                 return nil, &badStringError{"malformed HTTP version", resp.Proto}
113         }
114
115         // Parse the response headers.
116         mimeHeader, err := tp.ReadMIMEHeader()
117         if err != nil {
118                 return nil, err
119         }
120         resp.Header = Header(mimeHeader)
121
122         fixPragmaCacheControl(resp.Header)
123
124         err = readTransfer(resp, r)
125         if err != nil {
126                 return nil, err
127         }
128
129         resp.SetCookie = readSetCookies(resp.Header)
130
131         return resp, nil
132 }
133
134 // RFC2616: Should treat
135 //      Pragma: no-cache
136 // like
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"}
142                 }
143         }
144 }
145
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
151 }
152
153 // Writes the response (header, body and trailer) in wire format. This method
154 // consults the following fields of resp:
155 //
156 //  StatusCode
157 //  ProtoMajor
158 //  ProtoMinor
159 //  RequestMethod
160 //  TransferEncoding
161 //  Trailer
162 //  Body
163 //  ContentLength
164 //  Header, values for non-canonical keys will have unpredictable behavior
165 //
166 func (resp *Response) Write(w io.Writer) os.Error {
167
168         // RequestMethod should be upper-case
169         resp.RequestMethod = strings.ToUpper(resp.RequestMethod)
170
171         // Status line
172         text := resp.Status
173         if text == "" {
174                 var ok bool
175                 text, ok = statusText[resp.StatusCode]
176                 if !ok {
177                         text = "status code " + strconv.Itoa(resp.StatusCode)
178                 }
179         }
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")
183
184         // Process Body,ContentLength,Close,Trailer
185         tw, err := newTransferWriter(resp)
186         if err != nil {
187                 return err
188         }
189         err = tw.WriteHeader(w)
190         if err != nil {
191                 return err
192         }
193
194         // Rest of header
195         err = writeSortedHeader(w, resp.Header, respExcludeHeader)
196         if err != nil {
197                 return err
198         }
199
200         if err = writeSetCookies(w, resp.SetCookie); err != nil {
201                 return err
202         }
203
204         // End-of-header
205         io.WriteString(w, "\r\n")
206
207         // Write body and trailer
208         err = tw.WriteBody(w)
209         if err != nil {
210                 return err
211         }
212
213         // Success
214         return nil
215 }
216
217 func writeSortedHeader(w io.Writer, h Header, exclude map[string]bool) os.Error {
218         keys := make([]string, 0, len(h))
219         for k := range h {
220                 if exclude == nil || !exclude[k] {
221                         keys = append(keys, k)
222                 }
223         }
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)
230                         if v == "" {
231                                 continue
232                         }
233                         if _, err := fmt.Fprintf(w, "%s: %s\r\n", k, v); err != nil {
234                                 return err
235                         }
236                 }
237         }
238         return nil
239 }