github.com/aloncn/graphics-go@v0.0.1/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 // tests successful calls to ReadResponse, and inspects the returned Response. 511 // For error cases, see TestReadResponseErrors below. 512 func TestReadResponse(t *testing.T) { 513 for i, tt := range respTests { 514 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) 515 if err != nil { 516 t.Errorf("#%d: %v", i, err) 517 continue 518 } 519 rbody := resp.Body 520 resp.Body = nil 521 diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp) 522 var bout bytes.Buffer 523 if rbody != nil { 524 _, err = io.Copy(&bout, rbody) 525 if err != nil { 526 t.Errorf("#%d: %v", i, err) 527 continue 528 } 529 rbody.Close() 530 } 531 body := bout.String() 532 if body != tt.Body { 533 t.Errorf("#%d: Body = %q want %q", i, body, tt.Body) 534 } 535 } 536 } 537 538 func TestWriteResponse(t *testing.T) { 539 for i, tt := range respTests { 540 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) 541 if err != nil { 542 t.Errorf("#%d: %v", i, err) 543 continue 544 } 545 err = resp.Write(ioutil.Discard) 546 if err != nil { 547 t.Errorf("#%d: %v", i, err) 548 continue 549 } 550 } 551 } 552 553 var readResponseCloseInMiddleTests = []struct { 554 chunked, compressed bool 555 }{ 556 {false, false}, 557 {true, false}, 558 {true, true}, 559 } 560 561 // TestReadResponseCloseInMiddle tests that closing a body after 562 // reading only part of its contents advances the read to the end of 563 // the request, right up until the next request. 564 func TestReadResponseCloseInMiddle(t *testing.T) { 565 for _, test := range readResponseCloseInMiddleTests { 566 fatalf := func(format string, args ...interface{}) { 567 args = append([]interface{}{test.chunked, test.compressed}, args...) 568 t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...) 569 } 570 checkErr := func(err error, msg string) { 571 if err == nil { 572 return 573 } 574 fatalf(msg+": %v", err) 575 } 576 var buf bytes.Buffer 577 buf.WriteString("HTTP/1.1 200 OK\r\n") 578 if test.chunked { 579 buf.WriteString("Transfer-Encoding: chunked\r\n") 580 } else { 581 buf.WriteString("Content-Length: 1000000\r\n") 582 } 583 var wr io.Writer = &buf 584 if test.chunked { 585 wr = internal.NewChunkedWriter(wr) 586 } 587 if test.compressed { 588 buf.WriteString("Content-Encoding: gzip\r\n") 589 wr = gzip.NewWriter(wr) 590 } 591 buf.WriteString("\r\n") 592 593 chunk := bytes.Repeat([]byte{'x'}, 1000) 594 for i := 0; i < 1000; i++ { 595 if test.compressed { 596 // Otherwise this compresses too well. 597 _, err := io.ReadFull(rand.Reader, chunk) 598 checkErr(err, "rand.Reader ReadFull") 599 } 600 wr.Write(chunk) 601 } 602 if test.compressed { 603 err := wr.(*gzip.Writer).Close() 604 checkErr(err, "compressor close") 605 } 606 if test.chunked { 607 buf.WriteString("0\r\n\r\n") 608 } 609 buf.WriteString("Next Request Here") 610 611 bufr := bufio.NewReader(&buf) 612 resp, err := ReadResponse(bufr, dummyReq("GET")) 613 checkErr(err, "ReadResponse") 614 expectedLength := int64(-1) 615 if !test.chunked { 616 expectedLength = 1000000 617 } 618 if resp.ContentLength != expectedLength { 619 fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength) 620 } 621 if resp.Body == nil { 622 fatalf("nil body") 623 } 624 if test.compressed { 625 gzReader, err := gzip.NewReader(resp.Body) 626 checkErr(err, "gzip.NewReader") 627 resp.Body = &readerAndCloser{gzReader, resp.Body} 628 } 629 630 rbuf := make([]byte, 2500) 631 n, err := io.ReadFull(resp.Body, rbuf) 632 checkErr(err, "2500 byte ReadFull") 633 if n != 2500 { 634 fatalf("ReadFull only read %d bytes", n) 635 } 636 if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) { 637 fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf)) 638 } 639 resp.Body.Close() 640 641 rest, err := ioutil.ReadAll(bufr) 642 checkErr(err, "ReadAll on remainder") 643 if e, g := "Next Request Here", string(rest); e != g { 644 g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string { 645 return fmt.Sprintf("x(repeated x%d)", len(match)) 646 }) 647 fatalf("remainder = %q, expected %q", g, e) 648 } 649 } 650 } 651 652 func diff(t *testing.T, prefix string, have, want interface{}) { 653 hv := reflect.ValueOf(have).Elem() 654 wv := reflect.ValueOf(want).Elem() 655 if hv.Type() != wv.Type() { 656 t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type()) 657 } 658 for i := 0; i < hv.NumField(); i++ { 659 hf := hv.Field(i).Interface() 660 wf := wv.Field(i).Interface() 661 if !reflect.DeepEqual(hf, wf) { 662 t.Errorf("%s: %s = %v want %v", prefix, hv.Type().Field(i).Name, hf, wf) 663 } 664 } 665 } 666 667 type responseLocationTest struct { 668 location string // Response's Location header or "" 669 requrl string // Response.Request.URL or "" 670 want string 671 wantErr error 672 } 673 674 var responseLocationTests = []responseLocationTest{ 675 {"/foo", "http://bar.com/baz", "http://bar.com/foo", nil}, 676 {"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil}, 677 {"", "http://bar.com/baz", "", ErrNoLocation}, 678 {"/bar", "", "/bar", nil}, 679 } 680 681 func TestLocationResponse(t *testing.T) { 682 for i, tt := range responseLocationTests { 683 res := new(Response) 684 res.Header = make(Header) 685 res.Header.Set("Location", tt.location) 686 if tt.requrl != "" { 687 res.Request = &Request{} 688 var err error 689 res.Request.URL, err = url.Parse(tt.requrl) 690 if err != nil { 691 t.Fatalf("bad test URL %q: %v", tt.requrl, err) 692 } 693 } 694 695 got, err := res.Location() 696 if tt.wantErr != nil { 697 if err == nil { 698 t.Errorf("%d. err=nil; want %q", i, tt.wantErr) 699 continue 700 } 701 if g, e := err.Error(), tt.wantErr.Error(); g != e { 702 t.Errorf("%d. err=%q; want %q", i, g, e) 703 continue 704 } 705 continue 706 } 707 if err != nil { 708 t.Errorf("%d. err=%q", i, err) 709 continue 710 } 711 if g, e := got.String(), tt.want; g != e { 712 t.Errorf("%d. Location=%q; want %q", i, g, e) 713 } 714 } 715 } 716 717 func TestResponseStatusStutter(t *testing.T) { 718 r := &Response{ 719 Status: "123 some status", 720 StatusCode: 123, 721 ProtoMajor: 1, 722 ProtoMinor: 3, 723 } 724 var buf bytes.Buffer 725 r.Write(&buf) 726 if strings.Contains(buf.String(), "123 123") { 727 t.Errorf("stutter in status: %s", buf.String()) 728 } 729 } 730 731 func TestResponseContentLengthShortBody(t *testing.T) { 732 const shortBody = "Short body, not 123 bytes." 733 br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" + 734 "Content-Length: 123\r\n" + 735 "\r\n" + 736 shortBody)) 737 res, err := ReadResponse(br, &Request{Method: "GET"}) 738 if err != nil { 739 t.Fatal(err) 740 } 741 if res.ContentLength != 123 { 742 t.Fatalf("Content-Length = %d; want 123", res.ContentLength) 743 } 744 var buf bytes.Buffer 745 n, err := io.Copy(&buf, res.Body) 746 if n != int64(len(shortBody)) { 747 t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody) 748 } 749 if buf.String() != shortBody { 750 t.Errorf("Read body %q; want %q", buf.String(), shortBody) 751 } 752 if err != io.ErrUnexpectedEOF { 753 t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err) 754 } 755 } 756 757 // Test various ReadResponse error cases. (also tests success cases, but mostly 758 // it's about errors). This does not test anything involving the bodies. Only 759 // the return value from ReadResponse itself. 760 func TestReadResponseErrors(t *testing.T) { 761 type testCase struct { 762 name string // optional, defaults to in 763 in string 764 wantErr interface{} // nil, err value, or string substring 765 } 766 767 status := func(s string, wantErr interface{}) testCase { 768 if wantErr == true { 769 wantErr = "malformed HTTP status code" 770 } 771 return testCase{ 772 name: fmt.Sprintf("status %q", s), 773 in: "HTTP/1.1 " + s + "\r\nFoo: bar\r\n\r\n", 774 wantErr: wantErr, 775 } 776 } 777 778 version := func(s string, wantErr interface{}) testCase { 779 if wantErr == true { 780 wantErr = "malformed HTTP version" 781 } 782 return testCase{ 783 name: fmt.Sprintf("version %q", s), 784 in: s + " 200 OK\r\n\r\n", 785 wantErr: wantErr, 786 } 787 } 788 789 tests := []testCase{ 790 {"", "", io.ErrUnexpectedEOF}, 791 {"", "HTTP/1.1 301 Moved Permanently\r\nFoo: bar", io.ErrUnexpectedEOF}, 792 {"", "HTTP/1.1", "malformed HTTP response"}, 793 {"", "HTTP/2.0", "malformed HTTP response"}, 794 status("20X Unknown", true), 795 status("abcd Unknown", true), 796 status("二百/两百 OK", true), 797 status(" Unknown", true), 798 status("c8 OK", true), 799 status("0x12d Moved Permanently", true), 800 status("200 OK", nil), 801 status("000 OK", nil), 802 status("001 OK", nil), 803 status("404 NOTFOUND", nil), 804 status("20 OK", true), 805 status("00 OK", true), 806 status("-10 OK", true), 807 status("1000 OK", true), 808 status("999 Done", nil), 809 status("-1 OK", true), 810 status("-200 OK", true), 811 version("HTTP/1.2", nil), 812 version("HTTP/2.0", nil), 813 version("HTTP/1.100000000002", true), 814 version("HTTP/1.-1", true), 815 version("HTTP/A.B", true), 816 version("HTTP/1", true), 817 version("http/1.1", true), 818 } 819 for i, tt := range tests { 820 br := bufio.NewReader(strings.NewReader(tt.in)) 821 _, rerr := ReadResponse(br, nil) 822 if err := matchErr(rerr, tt.wantErr); err != nil { 823 name := tt.name 824 if name == "" { 825 name = fmt.Sprintf("%d. input %q", i, tt.in) 826 } 827 t.Errorf("%s: %v", name, err) 828 } 829 } 830 } 831 832 // wantErr can be nil, an error value to match exactly, or type string to 833 // match a substring. 834 func matchErr(err error, wantErr interface{}) error { 835 if err == nil { 836 if wantErr == nil { 837 return nil 838 } 839 if sub, ok := wantErr.(string); ok { 840 return fmt.Errorf("unexpected success; want error with substring %q", sub) 841 } 842 return fmt.Errorf("unexpected success; want error %v", wantErr) 843 } 844 if wantErr == nil { 845 return fmt.Errorf("%v; want success", err) 846 } 847 if sub, ok := wantErr.(string); ok { 848 if strings.Contains(err.Error(), sub) { 849 return nil 850 } 851 return fmt.Errorf("error = %v; want an error with substring %q", err, sub) 852 } 853 if err == wantErr { 854 return nil 855 } 856 return fmt.Errorf("%v; want %v", err, wantErr) 857 } 858 859 func TestNeedsSniff(t *testing.T) { 860 // needsSniff returns true with an empty response. 861 r := &response{} 862 if got, want := r.needsSniff(), true; got != want { 863 t.Errorf("needsSniff = %t; want %t", got, want) 864 } 865 // needsSniff returns false when Content-Type = nil. 866 r.handlerHeader = Header{"Content-Type": nil} 867 if got, want := r.needsSniff(), false; got != want { 868 t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want) 869 } 870 }