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