libgo: Update to Go 1.1.1.
[official-gcc.git] / libgo / go / net / http / response_test.go
blob02796e88b4c4453ea2543fb44eebe6efbd486df1
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.
5 package http
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 "strings"
18 "testing"
21 type respTest struct {
22 Raw string
23 Resp Response
24 Body string
27 func dummyReq(method string) *Request {
28 return &Request{Method: method}
31 var respTests = []respTest{
32 // Unchunked response without Content-Length.
34 "HTTP/1.0 200 OK\r\n" +
35 "Connection: close\r\n" +
36 "\r\n" +
37 "Body here\n",
39 Response{
40 Status: "200 OK",
41 StatusCode: 200,
42 Proto: "HTTP/1.0",
43 ProtoMajor: 1,
44 ProtoMinor: 0,
45 Request: dummyReq("GET"),
46 Header: Header{
47 "Connection": {"close"}, // TODO(rsc): Delete?
49 Close: true,
50 ContentLength: -1,
53 "Body here\n",
56 // Unchunked HTTP/1.1 response without Content-Length or
57 // Connection headers.
59 "HTTP/1.1 200 OK\r\n" +
60 "\r\n" +
61 "Body here\n",
63 Response{
64 Status: "200 OK",
65 StatusCode: 200,
66 Proto: "HTTP/1.1",
67 ProtoMajor: 1,
68 ProtoMinor: 1,
69 Header: Header{},
70 Request: dummyReq("GET"),
71 Close: true,
72 ContentLength: -1,
75 "Body here\n",
78 // Unchunked HTTP/1.1 204 response without Content-Length.
80 "HTTP/1.1 204 No Content\r\n" +
81 "\r\n" +
82 "Body should not be read!\n",
84 Response{
85 Status: "204 No Content",
86 StatusCode: 204,
87 Proto: "HTTP/1.1",
88 ProtoMajor: 1,
89 ProtoMinor: 1,
90 Header: Header{},
91 Request: dummyReq("GET"),
92 Close: false,
93 ContentLength: 0,
96 "",
99 // Unchunked response with Content-Length.
101 "HTTP/1.0 200 OK\r\n" +
102 "Content-Length: 10\r\n" +
103 "Connection: close\r\n" +
104 "\r\n" +
105 "Body here\n",
107 Response{
108 Status: "200 OK",
109 StatusCode: 200,
110 Proto: "HTTP/1.0",
111 ProtoMajor: 1,
112 ProtoMinor: 0,
113 Request: dummyReq("GET"),
114 Header: Header{
115 "Connection": {"close"},
116 "Content-Length": {"10"},
118 Close: true,
119 ContentLength: 10,
122 "Body here\n",
125 // Chunked response without Content-Length.
127 "HTTP/1.1 200 OK\r\n" +
128 "Transfer-Encoding: chunked\r\n" +
129 "\r\n" +
130 "0a\r\n" +
131 "Body here\n\r\n" +
132 "09\r\n" +
133 "continued\r\n" +
134 "0\r\n" +
135 "\r\n",
137 Response{
138 Status: "200 OK",
139 StatusCode: 200,
140 Proto: "HTTP/1.1",
141 ProtoMajor: 1,
142 ProtoMinor: 1,
143 Request: dummyReq("GET"),
144 Header: Header{},
145 Close: false,
146 ContentLength: -1,
147 TransferEncoding: []string{"chunked"},
150 "Body here\ncontinued",
153 // Chunked response with Content-Length.
155 "HTTP/1.1 200 OK\r\n" +
156 "Transfer-Encoding: chunked\r\n" +
157 "Content-Length: 10\r\n" +
158 "\r\n" +
159 "0a\r\n" +
160 "Body here\n\r\n" +
161 "0\r\n" +
162 "\r\n",
164 Response{
165 Status: "200 OK",
166 StatusCode: 200,
167 Proto: "HTTP/1.1",
168 ProtoMajor: 1,
169 ProtoMinor: 1,
170 Request: dummyReq("GET"),
171 Header: Header{},
172 Close: false,
173 ContentLength: -1,
174 TransferEncoding: []string{"chunked"},
177 "Body here\n",
180 // Chunked response in response to a HEAD request
182 "HTTP/1.1 200 OK\r\n" +
183 "Transfer-Encoding: chunked\r\n" +
184 "\r\n",
186 Response{
187 Status: "200 OK",
188 StatusCode: 200,
189 Proto: "HTTP/1.1",
190 ProtoMajor: 1,
191 ProtoMinor: 1,
192 Request: dummyReq("HEAD"),
193 Header: Header{},
194 TransferEncoding: []string{"chunked"},
195 Close: false,
196 ContentLength: -1,
202 // Content-Length in response to a HEAD request
204 "HTTP/1.0 200 OK\r\n" +
205 "Content-Length: 256\r\n" +
206 "\r\n",
208 Response{
209 Status: "200 OK",
210 StatusCode: 200,
211 Proto: "HTTP/1.0",
212 ProtoMajor: 1,
213 ProtoMinor: 0,
214 Request: dummyReq("HEAD"),
215 Header: Header{"Content-Length": {"256"}},
216 TransferEncoding: nil,
217 Close: true,
218 ContentLength: 256,
224 // Content-Length in response to a HEAD request with HTTP/1.1
226 "HTTP/1.1 200 OK\r\n" +
227 "Content-Length: 256\r\n" +
228 "\r\n",
230 Response{
231 Status: "200 OK",
232 StatusCode: 200,
233 Proto: "HTTP/1.1",
234 ProtoMajor: 1,
235 ProtoMinor: 1,
236 Request: dummyReq("HEAD"),
237 Header: Header{"Content-Length": {"256"}},
238 TransferEncoding: nil,
239 Close: false,
240 ContentLength: 256,
246 // No Content-Length or Chunked in response to a HEAD request
248 "HTTP/1.0 200 OK\r\n" +
249 "\r\n",
251 Response{
252 Status: "200 OK",
253 StatusCode: 200,
254 Proto: "HTTP/1.0",
255 ProtoMajor: 1,
256 ProtoMinor: 0,
257 Request: dummyReq("HEAD"),
258 Header: Header{},
259 TransferEncoding: nil,
260 Close: true,
261 ContentLength: -1,
267 // explicit Content-Length of 0.
269 "HTTP/1.1 200 OK\r\n" +
270 "Content-Length: 0\r\n" +
271 "\r\n",
273 Response{
274 Status: "200 OK",
275 StatusCode: 200,
276 Proto: "HTTP/1.1",
277 ProtoMajor: 1,
278 ProtoMinor: 1,
279 Request: dummyReq("GET"),
280 Header: Header{
281 "Content-Length": {"0"},
283 Close: false,
284 ContentLength: 0,
290 // Status line without a Reason-Phrase, but trailing space.
291 // (permitted by RFC 2616)
293 "HTTP/1.0 303 \r\n\r\n",
294 Response{
295 Status: "303 ",
296 StatusCode: 303,
297 Proto: "HTTP/1.0",
298 ProtoMajor: 1,
299 ProtoMinor: 0,
300 Request: dummyReq("GET"),
301 Header: Header{},
302 Close: true,
303 ContentLength: -1,
309 // Status line without a Reason-Phrase, and no trailing space.
310 // (not permitted by RFC 2616, but we'll accept it anyway)
312 "HTTP/1.0 303\r\n\r\n",
313 Response{
314 Status: "303 ",
315 StatusCode: 303,
316 Proto: "HTTP/1.0",
317 ProtoMajor: 1,
318 ProtoMinor: 0,
319 Request: dummyReq("GET"),
320 Header: Header{},
321 Close: true,
322 ContentLength: -1,
328 // golang.org/issue/4767: don't special-case multipart/byteranges responses
330 `HTTP/1.1 206 Partial Content
331 Connection: close
332 Content-Type: multipart/byteranges; boundary=18a75608c8f47cef
334 some body`,
335 Response{
336 Status: "206 Partial Content",
337 StatusCode: 206,
338 Proto: "HTTP/1.1",
339 ProtoMajor: 1,
340 ProtoMinor: 1,
341 Request: dummyReq("GET"),
342 Header: Header{
343 "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"},
345 Close: true,
346 ContentLength: -1,
349 "some body",
353 func TestReadResponse(t *testing.T) {
354 for i, tt := range respTests {
355 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
356 if err != nil {
357 t.Errorf("#%d: %v", i, err)
358 continue
360 rbody := resp.Body
361 resp.Body = nil
362 diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp)
363 var bout bytes.Buffer
364 if rbody != nil {
365 _, err = io.Copy(&bout, rbody)
366 if err != nil {
367 t.Errorf("#%d: %v", i, err)
368 continue
370 rbody.Close()
372 body := bout.String()
373 if body != tt.Body {
374 t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
379 func TestWriteResponse(t *testing.T) {
380 for i, tt := range respTests {
381 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
382 if err != nil {
383 t.Errorf("#%d: %v", i, err)
384 continue
386 bout := bytes.NewBuffer(nil)
387 err = resp.Write(bout)
388 if err != nil {
389 t.Errorf("#%d: %v", i, err)
390 continue
395 var readResponseCloseInMiddleTests = []struct {
396 chunked, compressed bool
398 {false, false},
399 {true, false},
400 {true, true},
403 // TestReadResponseCloseInMiddle tests that closing a body after
404 // reading only part of its contents advances the read to the end of
405 // the request, right up until the next request.
406 func TestReadResponseCloseInMiddle(t *testing.T) {
407 for _, test := range readResponseCloseInMiddleTests {
408 fatalf := func(format string, args ...interface{}) {
409 args = append([]interface{}{test.chunked, test.compressed}, args...)
410 t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...)
412 checkErr := func(err error, msg string) {
413 if err == nil {
414 return
416 fatalf(msg+": %v", err)
418 var buf bytes.Buffer
419 buf.WriteString("HTTP/1.1 200 OK\r\n")
420 if test.chunked {
421 buf.WriteString("Transfer-Encoding: chunked\r\n")
422 } else {
423 buf.WriteString("Content-Length: 1000000\r\n")
425 var wr io.Writer = &buf
426 if test.chunked {
427 wr = newChunkedWriter(wr)
429 if test.compressed {
430 buf.WriteString("Content-Encoding: gzip\r\n")
431 wr = gzip.NewWriter(wr)
433 buf.WriteString("\r\n")
435 chunk := bytes.Repeat([]byte{'x'}, 1000)
436 for i := 0; i < 1000; i++ {
437 if test.compressed {
438 // Otherwise this compresses too well.
439 _, err := io.ReadFull(rand.Reader, chunk)
440 checkErr(err, "rand.Reader ReadFull")
442 wr.Write(chunk)
444 if test.compressed {
445 err := wr.(*gzip.Writer).Close()
446 checkErr(err, "compressor close")
448 if test.chunked {
449 buf.WriteString("0\r\n\r\n")
451 buf.WriteString("Next Request Here")
453 bufr := bufio.NewReader(&buf)
454 resp, err := ReadResponse(bufr, dummyReq("GET"))
455 checkErr(err, "ReadResponse")
456 expectedLength := int64(-1)
457 if !test.chunked {
458 expectedLength = 1000000
460 if resp.ContentLength != expectedLength {
461 fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
463 if resp.Body == nil {
464 fatalf("nil body")
466 if test.compressed {
467 gzReader, err := gzip.NewReader(resp.Body)
468 checkErr(err, "gzip.NewReader")
469 resp.Body = &readerAndCloser{gzReader, resp.Body}
472 rbuf := make([]byte, 2500)
473 n, err := io.ReadFull(resp.Body, rbuf)
474 checkErr(err, "2500 byte ReadFull")
475 if n != 2500 {
476 fatalf("ReadFull only read %d bytes", n)
478 if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) {
479 fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf))
481 resp.Body.Close()
483 rest, err := ioutil.ReadAll(bufr)
484 checkErr(err, "ReadAll on remainder")
485 if e, g := "Next Request Here", string(rest); e != g {
486 fatalf("remainder = %q, expected %q", g, e)
491 func diff(t *testing.T, prefix string, have, want interface{}) {
492 hv := reflect.ValueOf(have).Elem()
493 wv := reflect.ValueOf(want).Elem()
494 if hv.Type() != wv.Type() {
495 t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type())
497 for i := 0; i < hv.NumField(); i++ {
498 hf := hv.Field(i).Interface()
499 wf := wv.Field(i).Interface()
500 if !reflect.DeepEqual(hf, wf) {
501 t.Errorf("%s: %s = %v want %v", prefix, hv.Type().Field(i).Name, hf, wf)
506 type responseLocationTest struct {
507 location string // Response's Location header or ""
508 requrl string // Response.Request.URL or ""
509 want string
510 wantErr error
513 var responseLocationTests = []responseLocationTest{
514 {"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
515 {"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
516 {"", "http://bar.com/baz", "", ErrNoLocation},
519 func TestLocationResponse(t *testing.T) {
520 for i, tt := range responseLocationTests {
521 res := new(Response)
522 res.Header = make(Header)
523 res.Header.Set("Location", tt.location)
524 if tt.requrl != "" {
525 res.Request = &Request{}
526 var err error
527 res.Request.URL, err = url.Parse(tt.requrl)
528 if err != nil {
529 t.Fatalf("bad test URL %q: %v", tt.requrl, err)
533 got, err := res.Location()
534 if tt.wantErr != nil {
535 if err == nil {
536 t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
537 continue
539 if g, e := err.Error(), tt.wantErr.Error(); g != e {
540 t.Errorf("%d. err=%q; want %q", i, g, e)
541 continue
543 continue
545 if err != nil {
546 t.Errorf("%d. err=%q", i, err)
547 continue
549 if g, e := got.String(), tt.want; g != e {
550 t.Errorf("%d. Location=%q; want %q", i, g, e)
555 func TestResponseStatusStutter(t *testing.T) {
556 r := &Response{
557 Status: "123 some status",
558 StatusCode: 123,
559 ProtoMajor: 1,
560 ProtoMinor: 3,
562 var buf bytes.Buffer
563 r.Write(&buf)
564 if strings.Contains(buf.String(), "123 123") {
565 t.Errorf("stutter in status: %s", buf.String())