github.com/d4l3k/go@v0.0.0-20151015000803-65fc379daeda/src/net/http/response_test.go (about) 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/http/internal" 16 "net/url" 17 "reflect" 18 "regexp" 19 "strings" 20 "testing" 21 ) 22 23 type respTest struct { 24 Raw string 25 Resp Response 26 Body string 27 } 28 29 func dummyReq(method string) *Request { 30 return &Request{Method: method} 31 } 32 33 func dummyReq11(method string) *Request { 34 return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1} 35 } 36 37 var respTests = []respTest{ 38 // Unchunked response without Content-Length. 39 { 40 "HTTP/1.0 200 OK\r\n" + 41 "Connection: close\r\n" + 42 "\r\n" + 43 "Body here\n", 44 45 Response{ 46 Status: "200 OK", 47 StatusCode: 200, 48 Proto: "HTTP/1.0", 49 ProtoMajor: 1, 50 ProtoMinor: 0, 51 Request: dummyReq("GET"), 52 Header: Header{ 53 "Connection": {"close"}, // TODO(rsc): Delete? 54 }, 55 Close: true, 56 ContentLength: -1, 57 }, 58 59 "Body here\n", 60 }, 61 62 // Unchunked HTTP/1.1 response without Content-Length or 63 // Connection headers. 64 { 65 "HTTP/1.1 200 OK\r\n" + 66 "\r\n" + 67 "Body here\n", 68 69 Response{ 70 Status: "200 OK", 71 StatusCode: 200, 72 Proto: "HTTP/1.1", 73 ProtoMajor: 1, 74 ProtoMinor: 1, 75 Header: Header{}, 76 Request: dummyReq("GET"), 77 Close: true, 78 ContentLength: -1, 79 }, 80 81 "Body here\n", 82 }, 83 84 // Unchunked HTTP/1.1 204 response without Content-Length. 85 { 86 "HTTP/1.1 204 No Content\r\n" + 87 "\r\n" + 88 "Body should not be read!\n", 89 90 Response{ 91 Status: "204 No Content", 92 StatusCode: 204, 93 Proto: "HTTP/1.1", 94 ProtoMajor: 1, 95 ProtoMinor: 1, 96 Header: Header{}, 97 Request: dummyReq("GET"), 98 Close: false, 99 ContentLength: 0, 100 }, 101 102 "", 103 }, 104 105 // Unchunked response with Content-Length. 106 { 107 "HTTP/1.0 200 OK\r\n" + 108 "Content-Length: 10\r\n" + 109 "Connection: close\r\n" + 110 "\r\n" + 111 "Body here\n", 112 113 Response{ 114 Status: "200 OK", 115 StatusCode: 200, 116 Proto: "HTTP/1.0", 117 ProtoMajor: 1, 118 ProtoMinor: 0, 119 Request: dummyReq("GET"), 120 Header: Header{ 121 "Connection": {"close"}, 122 "Content-Length": {"10"}, 123 }, 124 Close: true, 125 ContentLength: 10, 126 }, 127 128 "Body here\n", 129 }, 130 131 // Chunked response without Content-Length. 132 { 133 "HTTP/1.1 200 OK\r\n" + 134 "Transfer-Encoding: chunked\r\n" + 135 "\r\n" + 136 "0a\r\n" + 137 "Body here\n\r\n" + 138 "09\r\n" + 139 "continued\r\n" + 140 "0\r\n" + 141 "\r\n", 142 143 Response{ 144 Status: "200 OK", 145 StatusCode: 200, 146 Proto: "HTTP/1.1", 147 ProtoMajor: 1, 148 ProtoMinor: 1, 149 Request: dummyReq("GET"), 150 Header: Header{}, 151 Close: false, 152 ContentLength: -1, 153 TransferEncoding: []string{"chunked"}, 154 }, 155 156 "Body here\ncontinued", 157 }, 158 159 // Chunked response with Content-Length. 160 { 161 "HTTP/1.1 200 OK\r\n" + 162 "Transfer-Encoding: chunked\r\n" + 163 "Content-Length: 10\r\n" + 164 "\r\n" + 165 "0a\r\n" + 166 "Body here\n\r\n" + 167 "0\r\n" + 168 "\r\n", 169 170 Response{ 171 Status: "200 OK", 172 StatusCode: 200, 173 Proto: "HTTP/1.1", 174 ProtoMajor: 1, 175 ProtoMinor: 1, 176 Request: dummyReq("GET"), 177 Header: Header{}, 178 Close: false, 179 ContentLength: -1, 180 TransferEncoding: []string{"chunked"}, 181 }, 182 183 "Body here\n", 184 }, 185 186 // Chunked response in response to a HEAD request 187 { 188 "HTTP/1.1 200 OK\r\n" + 189 "Transfer-Encoding: chunked\r\n" + 190 "\r\n", 191 192 Response{ 193 Status: "200 OK", 194 StatusCode: 200, 195 Proto: "HTTP/1.1", 196 ProtoMajor: 1, 197 ProtoMinor: 1, 198 Request: dummyReq("HEAD"), 199 Header: Header{}, 200 TransferEncoding: []string{"chunked"}, 201 Close: false, 202 ContentLength: -1, 203 }, 204 205 "", 206 }, 207 208 // Content-Length in response to a HEAD request 209 { 210 "HTTP/1.0 200 OK\r\n" + 211 "Content-Length: 256\r\n" + 212 "\r\n", 213 214 Response{ 215 Status: "200 OK", 216 StatusCode: 200, 217 Proto: "HTTP/1.0", 218 ProtoMajor: 1, 219 ProtoMinor: 0, 220 Request: dummyReq("HEAD"), 221 Header: Header{"Content-Length": {"256"}}, 222 TransferEncoding: nil, 223 Close: true, 224 ContentLength: 256, 225 }, 226 227 "", 228 }, 229 230 // Content-Length in response to a HEAD request with HTTP/1.1 231 { 232 "HTTP/1.1 200 OK\r\n" + 233 "Content-Length: 256\r\n" + 234 "\r\n", 235 236 Response{ 237 Status: "200 OK", 238 StatusCode: 200, 239 Proto: "HTTP/1.1", 240 ProtoMajor: 1, 241 ProtoMinor: 1, 242 Request: dummyReq("HEAD"), 243 Header: Header{"Content-Length": {"256"}}, 244 TransferEncoding: nil, 245 Close: false, 246 ContentLength: 256, 247 }, 248 249 "", 250 }, 251 252 // No Content-Length or Chunked in response to a HEAD request 253 { 254 "HTTP/1.0 200 OK\r\n" + 255 "\r\n", 256 257 Response{ 258 Status: "200 OK", 259 StatusCode: 200, 260 Proto: "HTTP/1.0", 261 ProtoMajor: 1, 262 ProtoMinor: 0, 263 Request: dummyReq("HEAD"), 264 Header: Header{}, 265 TransferEncoding: nil, 266 Close: true, 267 ContentLength: -1, 268 }, 269 270 "", 271 }, 272 273 // explicit Content-Length of 0. 274 { 275 "HTTP/1.1 200 OK\r\n" + 276 "Content-Length: 0\r\n" + 277 "\r\n", 278 279 Response{ 280 Status: "200 OK", 281 StatusCode: 200, 282 Proto: "HTTP/1.1", 283 ProtoMajor: 1, 284 ProtoMinor: 1, 285 Request: dummyReq("GET"), 286 Header: Header{ 287 "Content-Length": {"0"}, 288 }, 289 Close: false, 290 ContentLength: 0, 291 }, 292 293 "", 294 }, 295 296 // Status line without a Reason-Phrase, but trailing space. 297 // (permitted by RFC 2616) 298 { 299 "HTTP/1.0 303 \r\n\r\n", 300 Response{ 301 Status: "303 ", 302 StatusCode: 303, 303 Proto: "HTTP/1.0", 304 ProtoMajor: 1, 305 ProtoMinor: 0, 306 Request: dummyReq("GET"), 307 Header: Header{}, 308 Close: true, 309 ContentLength: -1, 310 }, 311 312 "", 313 }, 314 315 // Status line without a Reason-Phrase, and no trailing space. 316 // (not permitted by RFC 2616, but we'll accept it anyway) 317 { 318 "HTTP/1.0 303\r\n\r\n", 319 Response{ 320 Status: "303 ", 321 StatusCode: 303, 322 Proto: "HTTP/1.0", 323 ProtoMajor: 1, 324 ProtoMinor: 0, 325 Request: dummyReq("GET"), 326 Header: Header{}, 327 Close: true, 328 ContentLength: -1, 329 }, 330 331 "", 332 }, 333 334 // golang.org/issue/4767: don't special-case multipart/byteranges responses 335 { 336 `HTTP/1.1 206 Partial Content 337 Connection: close 338 Content-Type: multipart/byteranges; boundary=18a75608c8f47cef 339 340 some body`, 341 Response{ 342 Status: "206 Partial Content", 343 StatusCode: 206, 344 Proto: "HTTP/1.1", 345 ProtoMajor: 1, 346 ProtoMinor: 1, 347 Request: dummyReq("GET"), 348 Header: Header{ 349 "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"}, 350 }, 351 Close: true, 352 ContentLength: -1, 353 }, 354 355 "some body", 356 }, 357 358 // Unchunked response without Content-Length, Request is nil 359 { 360 "HTTP/1.0 200 OK\r\n" + 361 "Connection: close\r\n" + 362 "\r\n" + 363 "Body here\n", 364 365 Response{ 366 Status: "200 OK", 367 StatusCode: 200, 368 Proto: "HTTP/1.0", 369 ProtoMajor: 1, 370 ProtoMinor: 0, 371 Header: Header{ 372 "Connection": {"close"}, // TODO(rsc): Delete? 373 }, 374 Close: true, 375 ContentLength: -1, 376 }, 377 378 "Body here\n", 379 }, 380 381 // 206 Partial Content. golang.org/issue/8923 382 { 383 "HTTP/1.1 206 Partial Content\r\n" + 384 "Content-Type: text/plain; charset=utf-8\r\n" + 385 "Accept-Ranges: bytes\r\n" + 386 "Content-Range: bytes 0-5/1862\r\n" + 387 "Content-Length: 6\r\n\r\n" + 388 "foobar", 389 390 Response{ 391 Status: "206 Partial Content", 392 StatusCode: 206, 393 Proto: "HTTP/1.1", 394 ProtoMajor: 1, 395 ProtoMinor: 1, 396 Request: dummyReq("GET"), 397 Header: Header{ 398 "Accept-Ranges": []string{"bytes"}, 399 "Content-Length": []string{"6"}, 400 "Content-Type": []string{"text/plain; charset=utf-8"}, 401 "Content-Range": []string{"bytes 0-5/1862"}, 402 }, 403 ContentLength: 6, 404 }, 405 406 "foobar", 407 }, 408 409 // Both keep-alive and close, on the same Connection line. (Issue 8840) 410 { 411 "HTTP/1.1 200 OK\r\n" + 412 "Content-Length: 256\r\n" + 413 "Connection: keep-alive, close\r\n" + 414 "\r\n", 415 416 Response{ 417 Status: "200 OK", 418 StatusCode: 200, 419 Proto: "HTTP/1.1", 420 ProtoMajor: 1, 421 ProtoMinor: 1, 422 Request: dummyReq("HEAD"), 423 Header: Header{ 424 "Content-Length": {"256"}, 425 }, 426 TransferEncoding: nil, 427 Close: true, 428 ContentLength: 256, 429 }, 430 431 "", 432 }, 433 434 // Both keep-alive and close, on different Connection lines. (Issue 8840) 435 { 436 "HTTP/1.1 200 OK\r\n" + 437 "Content-Length: 256\r\n" + 438 "Connection: keep-alive\r\n" + 439 "Connection: close\r\n" + 440 "\r\n", 441 442 Response{ 443 Status: "200 OK", 444 StatusCode: 200, 445 Proto: "HTTP/1.1", 446 ProtoMajor: 1, 447 ProtoMinor: 1, 448 Request: dummyReq("HEAD"), 449 Header: Header{ 450 "Content-Length": {"256"}, 451 }, 452 TransferEncoding: nil, 453 Close: true, 454 ContentLength: 256, 455 }, 456 457 "", 458 }, 459 460 // Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding. 461 // Without a Content-Length. 462 { 463 "HTTP/1.0 200 OK\r\n" + 464 "Transfer-Encoding: bogus\r\n" + 465 "\r\n" + 466 "Body here\n", 467 468 Response{ 469 Status: "200 OK", 470 StatusCode: 200, 471 Proto: "HTTP/1.0", 472 ProtoMajor: 1, 473 ProtoMinor: 0, 474 Request: dummyReq("GET"), 475 Header: Header{}, 476 Close: true, 477 ContentLength: -1, 478 }, 479 480 "Body here\n", 481 }, 482 483 // Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding. 484 // With a Content-Length. 485 { 486 "HTTP/1.0 200 OK\r\n" + 487 "Transfer-Encoding: bogus\r\n" + 488 "Content-Length: 10\r\n" + 489 "\r\n" + 490 "Body here\n", 491 492 Response{ 493 Status: "200 OK", 494 StatusCode: 200, 495 Proto: "HTTP/1.0", 496 ProtoMajor: 1, 497 ProtoMinor: 0, 498 Request: dummyReq("GET"), 499 Header: Header{ 500 "Content-Length": {"10"}, 501 }, 502 Close: true, 503 ContentLength: 10, 504 }, 505 506 "Body here\n", 507 }, 508 } 509 510 func TestReadResponse(t *testing.T) { 511 for i, tt := range respTests { 512 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) 513 if err != nil { 514 t.Errorf("#%d: %v", i, err) 515 continue 516 } 517 rbody := resp.Body 518 resp.Body = nil 519 diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp) 520 var bout bytes.Buffer 521 if rbody != nil { 522 _, err = io.Copy(&bout, rbody) 523 if err != nil { 524 t.Errorf("#%d: %v", i, err) 525 continue 526 } 527 rbody.Close() 528 } 529 body := bout.String() 530 if body != tt.Body { 531 t.Errorf("#%d: Body = %q want %q", i, body, tt.Body) 532 } 533 } 534 } 535 536 func TestWriteResponse(t *testing.T) { 537 for i, tt := range respTests { 538 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) 539 if err != nil { 540 t.Errorf("#%d: %v", i, err) 541 continue 542 } 543 err = resp.Write(ioutil.Discard) 544 if err != nil { 545 t.Errorf("#%d: %v", i, err) 546 continue 547 } 548 } 549 } 550 551 var readResponseCloseInMiddleTests = []struct { 552 chunked, compressed bool 553 }{ 554 {false, false}, 555 {true, false}, 556 {true, true}, 557 } 558 559 // TestReadResponseCloseInMiddle tests that closing a body after 560 // reading only part of its contents advances the read to the end of 561 // the request, right up until the next request. 562 func TestReadResponseCloseInMiddle(t *testing.T) { 563 for _, test := range readResponseCloseInMiddleTests { 564 fatalf := func(format string, args ...interface{}) { 565 args = append([]interface{}{test.chunked, test.compressed}, args...) 566 t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...) 567 } 568 checkErr := func(err error, msg string) { 569 if err == nil { 570 return 571 } 572 fatalf(msg+": %v", err) 573 } 574 var buf bytes.Buffer 575 buf.WriteString("HTTP/1.1 200 OK\r\n") 576 if test.chunked { 577 buf.WriteString("Transfer-Encoding: chunked\r\n") 578 } else { 579 buf.WriteString("Content-Length: 1000000\r\n") 580 } 581 var wr io.Writer = &buf 582 if test.chunked { 583 wr = internal.NewChunkedWriter(wr) 584 } 585 if test.compressed { 586 buf.WriteString("Content-Encoding: gzip\r\n") 587 wr = gzip.NewWriter(wr) 588 } 589 buf.WriteString("\r\n") 590 591 chunk := bytes.Repeat([]byte{'x'}, 1000) 592 for i := 0; i < 1000; i++ { 593 if test.compressed { 594 // Otherwise this compresses too well. 595 _, err := io.ReadFull(rand.Reader, chunk) 596 checkErr(err, "rand.Reader ReadFull") 597 } 598 wr.Write(chunk) 599 } 600 if test.compressed { 601 err := wr.(*gzip.Writer).Close() 602 checkErr(err, "compressor close") 603 } 604 if test.chunked { 605 buf.WriteString("0\r\n\r\n") 606 } 607 buf.WriteString("Next Request Here") 608 609 bufr := bufio.NewReader(&buf) 610 resp, err := ReadResponse(bufr, dummyReq("GET")) 611 checkErr(err, "ReadResponse") 612 expectedLength := int64(-1) 613 if !test.chunked { 614 expectedLength = 1000000 615 } 616 if resp.ContentLength != expectedLength { 617 fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength) 618 } 619 if resp.Body == nil { 620 fatalf("nil body") 621 } 622 if test.compressed { 623 gzReader, err := gzip.NewReader(resp.Body) 624 checkErr(err, "gzip.NewReader") 625 resp.Body = &readerAndCloser{gzReader, resp.Body} 626 } 627 628 rbuf := make([]byte, 2500) 629 n, err := io.ReadFull(resp.Body, rbuf) 630 checkErr(err, "2500 byte ReadFull") 631 if n != 2500 { 632 fatalf("ReadFull only read %d bytes", n) 633 } 634 if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) { 635 fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf)) 636 } 637 resp.Body.Close() 638 639 rest, err := ioutil.ReadAll(bufr) 640 checkErr(err, "ReadAll on remainder") 641 if e, g := "Next Request Here", string(rest); e != g { 642 g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string { 643 return fmt.Sprintf("x(repeated x%d)", len(match)) 644 }) 645 fatalf("remainder = %q, expected %q", g, e) 646 } 647 } 648 } 649 650 func diff(t *testing.T, prefix string, have, want interface{}) { 651 hv := reflect.ValueOf(have).Elem() 652 wv := reflect.ValueOf(want).Elem() 653 if hv.Type() != wv.Type() { 654 t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type()) 655 } 656 for i := 0; i < hv.NumField(); i++ { 657 hf := hv.Field(i).Interface() 658 wf := wv.Field(i).Interface() 659 if !reflect.DeepEqual(hf, wf) { 660 t.Errorf("%s: %s = %v want %v", prefix, hv.Type().Field(i).Name, hf, wf) 661 } 662 } 663 } 664 665 type responseLocationTest struct { 666 location string // Response's Location header or "" 667 requrl string // Response.Request.URL or "" 668 want string 669 wantErr error 670 } 671 672 var responseLocationTests = []responseLocationTest{ 673 {"/foo", "http://bar.com/baz", "http://bar.com/foo", nil}, 674 {"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil}, 675 {"", "http://bar.com/baz", "", ErrNoLocation}, 676 } 677 678 func TestLocationResponse(t *testing.T) { 679 for i, tt := range responseLocationTests { 680 res := new(Response) 681 res.Header = make(Header) 682 res.Header.Set("Location", tt.location) 683 if tt.requrl != "" { 684 res.Request = &Request{} 685 var err error 686 res.Request.URL, err = url.Parse(tt.requrl) 687 if err != nil { 688 t.Fatalf("bad test URL %q: %v", tt.requrl, err) 689 } 690 } 691 692 got, err := res.Location() 693 if tt.wantErr != nil { 694 if err == nil { 695 t.Errorf("%d. err=nil; want %q", i, tt.wantErr) 696 continue 697 } 698 if g, e := err.Error(), tt.wantErr.Error(); g != e { 699 t.Errorf("%d. err=%q; want %q", i, g, e) 700 continue 701 } 702 continue 703 } 704 if err != nil { 705 t.Errorf("%d. err=%q", i, err) 706 continue 707 } 708 if g, e := got.String(), tt.want; g != e { 709 t.Errorf("%d. Location=%q; want %q", i, g, e) 710 } 711 } 712 } 713 714 func TestResponseStatusStutter(t *testing.T) { 715 r := &Response{ 716 Status: "123 some status", 717 StatusCode: 123, 718 ProtoMajor: 1, 719 ProtoMinor: 3, 720 } 721 var buf bytes.Buffer 722 r.Write(&buf) 723 if strings.Contains(buf.String(), "123 123") { 724 t.Errorf("stutter in status: %s", buf.String()) 725 } 726 } 727 728 func TestResponseContentLengthShortBody(t *testing.T) { 729 const shortBody = "Short body, not 123 bytes." 730 br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" + 731 "Content-Length: 123\r\n" + 732 "\r\n" + 733 shortBody)) 734 res, err := ReadResponse(br, &Request{Method: "GET"}) 735 if err != nil { 736 t.Fatal(err) 737 } 738 if res.ContentLength != 123 { 739 t.Fatalf("Content-Length = %d; want 123", res.ContentLength) 740 } 741 var buf bytes.Buffer 742 n, err := io.Copy(&buf, res.Body) 743 if n != int64(len(shortBody)) { 744 t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody) 745 } 746 if buf.String() != shortBody { 747 t.Errorf("Read body %q; want %q", buf.String(), shortBody) 748 } 749 if err != io.ErrUnexpectedEOF { 750 t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err) 751 } 752 } 753 754 func TestReadResponseUnexpectedEOF(t *testing.T) { 755 br := bufio.NewReader(strings.NewReader("HTTP/1.1 301 Moved Permanently\r\n" + 756 "Location: http://example.com")) 757 _, err := ReadResponse(br, nil) 758 if err != io.ErrUnexpectedEOF { 759 t.Errorf("ReadResponse = %v; want io.ErrUnexpectedEOF", err) 760 } 761 } 762 763 func TestNeedsSniff(t *testing.T) { 764 // needsSniff returns true with an empty response. 765 r := &response{} 766 if got, want := r.needsSniff(), true; got != want { 767 t.Errorf("needsSniff = %t; want %t", got, want) 768 } 769 // needsSniff returns false when Content-Type = nil. 770 r.handlerHeader = Header{"Content-Type": nil} 771 if got, want := r.needsSniff(), false; got != want { 772 t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want) 773 } 774 }