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