OSDN Git Service

Daily bump.
[pf3gnuchains/gcc-fork.git] / libgo / go / net / http / response_test.go
1 // Copyright 2010 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 package http
6
7 import (
8         "bufio"
9         "bytes"
10         "compress/gzip"
11         "crypto/rand"
12         "fmt"
13         "io"
14         "io/ioutil"
15         "net/url"
16         "reflect"
17         "testing"
18 )
19
20 type respTest struct {
21         Raw  string
22         Resp Response
23         Body string
24 }
25
26 func dummyReq(method string) *Request {
27         return &Request{Method: method}
28 }
29
30 var respTests = []respTest{
31         // Unchunked response without Content-Length.
32         {
33                 "HTTP/1.0 200 OK\r\n" +
34                         "Connection: close\r\n" +
35                         "\r\n" +
36                         "Body here\n",
37
38                 Response{
39                         Status:     "200 OK",
40                         StatusCode: 200,
41                         Proto:      "HTTP/1.0",
42                         ProtoMajor: 1,
43                         ProtoMinor: 0,
44                         Request:    dummyReq("GET"),
45                         Header: Header{
46                                 "Connection": {"close"}, // TODO(rsc): Delete?
47                         },
48                         Close:         true,
49                         ContentLength: -1,
50                 },
51
52                 "Body here\n",
53         },
54
55         // Unchunked HTTP/1.1 response without Content-Length or
56         // Connection headers.
57         {
58                 "HTTP/1.1 200 OK\r\n" +
59                         "\r\n" +
60                         "Body here\n",
61
62                 Response{
63                         Status:        "200 OK",
64                         StatusCode:    200,
65                         Proto:         "HTTP/1.1",
66                         ProtoMajor:    1,
67                         ProtoMinor:    1,
68                         Request:       dummyReq("GET"),
69                         Close:         true,
70                         ContentLength: -1,
71                 },
72
73                 "Body here\n",
74         },
75
76         // Unchunked HTTP/1.1 204 response without Content-Length.
77         {
78                 "HTTP/1.1 204 No Content\r\n" +
79                         "\r\n" +
80                         "Body should not be read!\n",
81
82                 Response{
83                         Status:        "204 No Content",
84                         StatusCode:    204,
85                         Proto:         "HTTP/1.1",
86                         ProtoMajor:    1,
87                         ProtoMinor:    1,
88                         Request:       dummyReq("GET"),
89                         Close:         false,
90                         ContentLength: 0,
91                 },
92
93                 "",
94         },
95
96         // Unchunked response with Content-Length.
97         {
98                 "HTTP/1.0 200 OK\r\n" +
99                         "Content-Length: 10\r\n" +
100                         "Connection: close\r\n" +
101                         "\r\n" +
102                         "Body here\n",
103
104                 Response{
105                         Status:     "200 OK",
106                         StatusCode: 200,
107                         Proto:      "HTTP/1.0",
108                         ProtoMajor: 1,
109                         ProtoMinor: 0,
110                         Request:    dummyReq("GET"),
111                         Header: Header{
112                                 "Connection":     {"close"}, // TODO(rsc): Delete?
113                                 "Content-Length": {"10"},    // TODO(rsc): Delete?
114                         },
115                         Close:         true,
116                         ContentLength: 10,
117                 },
118
119                 "Body here\n",
120         },
121
122         // Chunked response without Content-Length.
123         {
124                 "HTTP/1.0 200 OK\r\n" +
125                         "Transfer-Encoding: chunked\r\n" +
126                         "\r\n" +
127                         "0a\r\n" +
128                         "Body here\n\r\n" +
129                         "09\r\n" +
130                         "continued\r\n" +
131                         "0\r\n" +
132                         "\r\n",
133
134                 Response{
135                         Status:           "200 OK",
136                         StatusCode:       200,
137                         Proto:            "HTTP/1.0",
138                         ProtoMajor:       1,
139                         ProtoMinor:       0,
140                         Request:          dummyReq("GET"),
141                         Header:           Header{},
142                         Close:            true,
143                         ContentLength:    -1,
144                         TransferEncoding: []string{"chunked"},
145                 },
146
147                 "Body here\ncontinued",
148         },
149
150         // Chunked response with Content-Length.
151         {
152                 "HTTP/1.0 200 OK\r\n" +
153                         "Transfer-Encoding: chunked\r\n" +
154                         "Content-Length: 10\r\n" +
155                         "\r\n" +
156                         "0a\r\n" +
157                         "Body here\n" +
158                         "0\r\n" +
159                         "\r\n",
160
161                 Response{
162                         Status:           "200 OK",
163                         StatusCode:       200,
164                         Proto:            "HTTP/1.0",
165                         ProtoMajor:       1,
166                         ProtoMinor:       0,
167                         Request:          dummyReq("GET"),
168                         Header:           Header{},
169                         Close:            true,
170                         ContentLength:    -1, // TODO(rsc): Fix?
171                         TransferEncoding: []string{"chunked"},
172                 },
173
174                 "Body here\n",
175         },
176
177         // Chunked response in response to a HEAD request (the "chunked" should
178         // be ignored, as HEAD responses never have bodies)
179         {
180                 "HTTP/1.0 200 OK\r\n" +
181                         "Transfer-Encoding: chunked\r\n" +
182                         "\r\n",
183
184                 Response{
185                         Status:        "200 OK",
186                         StatusCode:    200,
187                         Proto:         "HTTP/1.0",
188                         ProtoMajor:    1,
189                         ProtoMinor:    0,
190                         Request:       dummyReq("HEAD"),
191                         Header:        Header{},
192                         Close:         true,
193                         ContentLength: 0,
194                 },
195
196                 "",
197         },
198
199         // explicit Content-Length of 0.
200         {
201                 "HTTP/1.1 200 OK\r\n" +
202                         "Content-Length: 0\r\n" +
203                         "\r\n",
204
205                 Response{
206                         Status:     "200 OK",
207                         StatusCode: 200,
208                         Proto:      "HTTP/1.1",
209                         ProtoMajor: 1,
210                         ProtoMinor: 1,
211                         Request:    dummyReq("GET"),
212                         Header: Header{
213                                 "Content-Length": {"0"},
214                         },
215                         Close:         false,
216                         ContentLength: 0,
217                 },
218
219                 "",
220         },
221
222         // Status line without a Reason-Phrase, but trailing space.
223         // (permitted by RFC 2616)
224         {
225                 "HTTP/1.0 303 \r\n\r\n",
226                 Response{
227                         Status:        "303 ",
228                         StatusCode:    303,
229                         Proto:         "HTTP/1.0",
230                         ProtoMajor:    1,
231                         ProtoMinor:    0,
232                         Request:       dummyReq("GET"),
233                         Header:        Header{},
234                         Close:         true,
235                         ContentLength: -1,
236                 },
237
238                 "",
239         },
240
241         // Status line without a Reason-Phrase, and no trailing space.
242         // (not permitted by RFC 2616, but we'll accept it anyway)
243         {
244                 "HTTP/1.0 303\r\n\r\n",
245                 Response{
246                         Status:        "303 ",
247                         StatusCode:    303,
248                         Proto:         "HTTP/1.0",
249                         ProtoMajor:    1,
250                         ProtoMinor:    0,
251                         Request:       dummyReq("GET"),
252                         Header:        Header{},
253                         Close:         true,
254                         ContentLength: -1,
255                 },
256
257                 "",
258         },
259 }
260
261 func TestReadResponse(t *testing.T) {
262         for i := range respTests {
263                 tt := &respTests[i]
264                 var braw bytes.Buffer
265                 braw.WriteString(tt.Raw)
266                 resp, err := ReadResponse(bufio.NewReader(&braw), tt.Resp.Request)
267                 if err != nil {
268                         t.Errorf("#%d: %s", i, err)
269                         continue
270                 }
271                 rbody := resp.Body
272                 resp.Body = nil
273                 diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp)
274                 var bout bytes.Buffer
275                 if rbody != nil {
276                         io.Copy(&bout, rbody)
277                         rbody.Close()
278                 }
279                 body := bout.String()
280                 if body != tt.Body {
281                         t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
282                 }
283         }
284 }
285
286 var readResponseCloseInMiddleTests = []struct {
287         chunked, compressed bool
288 }{
289         {false, false},
290         {true, false},
291         {true, true},
292 }
293
294 // TestReadResponseCloseInMiddle tests that closing a body after
295 // reading only part of its contents advances the read to the end of
296 // the request, right up until the next request.
297 func TestReadResponseCloseInMiddle(t *testing.T) {
298         for _, test := range readResponseCloseInMiddleTests {
299                 fatalf := func(format string, args ...interface{}) {
300                         args = append([]interface{}{test.chunked, test.compressed}, args...)
301                         t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...)
302                 }
303                 checkErr := func(err error, msg string) {
304                         if err == nil {
305                                 return
306                         }
307                         fatalf(msg+": %v", err)
308                 }
309                 var buf bytes.Buffer
310                 buf.WriteString("HTTP/1.1 200 OK\r\n")
311                 if test.chunked {
312                         buf.WriteString("Transfer-Encoding: chunked\r\n")
313                 } else {
314                         buf.WriteString("Content-Length: 1000000\r\n")
315                 }
316                 var wr io.Writer = &buf
317                 if test.chunked {
318                         wr = &chunkedWriter{wr}
319                 }
320                 if test.compressed {
321                         buf.WriteString("Content-Encoding: gzip\r\n")
322                         var err error
323                         wr, err = gzip.NewWriter(wr)
324                         checkErr(err, "gzip.NewWriter")
325                 }
326                 buf.WriteString("\r\n")
327
328                 chunk := bytes.Repeat([]byte{'x'}, 1000)
329                 for i := 0; i < 1000; i++ {
330                         if test.compressed {
331                                 // Otherwise this compresses too well.
332                                 _, err := io.ReadFull(rand.Reader, chunk)
333                                 checkErr(err, "rand.Reader ReadFull")
334                         }
335                         wr.Write(chunk)
336                 }
337                 if test.compressed {
338                         err := wr.(*gzip.Compressor).Close()
339                         checkErr(err, "compressor close")
340                 }
341                 if test.chunked {
342                         buf.WriteString("0\r\n\r\n")
343                 }
344                 buf.WriteString("Next Request Here")
345
346                 bufr := bufio.NewReader(&buf)
347                 resp, err := ReadResponse(bufr, dummyReq("GET"))
348                 checkErr(err, "ReadResponse")
349                 expectedLength := int64(-1)
350                 if !test.chunked {
351                         expectedLength = 1000000
352                 }
353                 if resp.ContentLength != expectedLength {
354                         fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
355                 }
356                 if resp.Body == nil {
357                         fatalf("nil body")
358                 }
359                 if test.compressed {
360                         gzReader, err := gzip.NewReader(resp.Body)
361                         checkErr(err, "gzip.NewReader")
362                         resp.Body = &readFirstCloseBoth{gzReader, resp.Body}
363                 }
364
365                 rbuf := make([]byte, 2500)
366                 n, err := io.ReadFull(resp.Body, rbuf)
367                 checkErr(err, "2500 byte ReadFull")
368                 if n != 2500 {
369                         fatalf("ReadFull only read %d bytes", n)
370                 }
371                 if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) {
372                         fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf))
373                 }
374                 resp.Body.Close()
375
376                 rest, err := ioutil.ReadAll(bufr)
377                 checkErr(err, "ReadAll on remainder")
378                 if e, g := "Next Request Here", string(rest); e != g {
379                         fatalf("remainder = %q, expected %q", g, e)
380                 }
381         }
382 }
383
384 func diff(t *testing.T, prefix string, have, want interface{}) {
385         hv := reflect.ValueOf(have).Elem()
386         wv := reflect.ValueOf(want).Elem()
387         if hv.Type() != wv.Type() {
388                 t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type())
389         }
390         for i := 0; i < hv.NumField(); i++ {
391                 hf := hv.Field(i).Interface()
392                 wf := wv.Field(i).Interface()
393                 if !reflect.DeepEqual(hf, wf) {
394                         t.Errorf("%s: %s = %v want %v", prefix, hv.Type().Field(i).Name, hf, wf)
395                 }
396         }
397 }
398
399 type responseLocationTest struct {
400         location string // Response's Location header or ""
401         requrl   string // Response.Request.URL or ""
402         want     string
403         wantErr  error
404 }
405
406 var responseLocationTests = []responseLocationTest{
407         {"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
408         {"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
409         {"", "http://bar.com/baz", "", ErrNoLocation},
410 }
411
412 func TestLocationResponse(t *testing.T) {
413         for i, tt := range responseLocationTests {
414                 res := new(Response)
415                 res.Header = make(Header)
416                 res.Header.Set("Location", tt.location)
417                 if tt.requrl != "" {
418                         res.Request = &Request{}
419                         var err error
420                         res.Request.URL, err = url.Parse(tt.requrl)
421                         if err != nil {
422                                 t.Fatalf("bad test URL %q: %v", tt.requrl, err)
423                         }
424                 }
425
426                 got, err := res.Location()
427                 if tt.wantErr != nil {
428                         if err == nil {
429                                 t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
430                                 continue
431                         }
432                         if g, e := err.Error(), tt.wantErr.Error(); g != e {
433                                 t.Errorf("%d. err=%q; want %q", i, g, e)
434                                 continue
435                         }
436                         continue
437                 }
438                 if err != nil {
439                         t.Errorf("%d. err=%q", i, err)
440                         continue
441                 }
442                 if g, e := got.String(), tt.want; g != e {
443                         t.Errorf("%d. Location=%q; want %q", i, g, e)
444                 }
445         }
446 }