github.com/varialus/godfly@v0.0.0-20130904042352-1934f9f095ab/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 "strings" 18 "testing" 19 ) 20 21 type respTest struct { 22 Raw string 23 Resp Response 24 Body string 25 } 26 27 func dummyReq(method string) *Request { 28 return &Request{Method: method} 29 } 30 31 var respTests = []respTest{ 32 // Unchunked response without Content-Length. 33 { 34 "HTTP/1.0 200 OK\r\n" + 35 "Connection: close\r\n" + 36 "\r\n" + 37 "Body here\n", 38 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? 48 }, 49 Close: true, 50 ContentLength: -1, 51 }, 52 53 "Body here\n", 54 }, 55 56 // Unchunked HTTP/1.1 response without Content-Length or 57 // Connection headers. 58 { 59 "HTTP/1.1 200 OK\r\n" + 60 "\r\n" + 61 "Body here\n", 62 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, 73 }, 74 75 "Body here\n", 76 }, 77 78 // Unchunked HTTP/1.1 204 response without Content-Length. 79 { 80 "HTTP/1.1 204 No Content\r\n" + 81 "\r\n" + 82 "Body should not be read!\n", 83 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, 94 }, 95 96 "", 97 }, 98 99 // Unchunked response with Content-Length. 100 { 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", 106 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"}, 117 }, 118 Close: true, 119 ContentLength: 10, 120 }, 121 122 "Body here\n", 123 }, 124 125 // Chunked response without Content-Length. 126 { 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", 136 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"}, 148 }, 149 150 "Body here\ncontinued", 151 }, 152 153 // Chunked response with Content-Length. 154 { 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", 163 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"}, 175 }, 176 177 "Body here\n", 178 }, 179 180 // Chunked response in response to a HEAD request 181 { 182 "HTTP/1.1 200 OK\r\n" + 183 "Transfer-Encoding: chunked\r\n" + 184 "\r\n", 185 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, 197 }, 198 199 "", 200 }, 201 202 // Content-Length in response to a HEAD request 203 { 204 "HTTP/1.0 200 OK\r\n" + 205 "Content-Length: 256\r\n" + 206 "\r\n", 207 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, 219 }, 220 221 "", 222 }, 223 224 // Content-Length in response to a HEAD request with HTTP/1.1 225 { 226 "HTTP/1.1 200 OK\r\n" + 227 "Content-Length: 256\r\n" + 228 "\r\n", 229 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, 241 }, 242 243 "", 244 }, 245 246 // No Content-Length or Chunked in response to a HEAD request 247 { 248 "HTTP/1.0 200 OK\r\n" + 249 "\r\n", 250 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, 262 }, 263 264 "", 265 }, 266 267 // explicit Content-Length of 0. 268 { 269 "HTTP/1.1 200 OK\r\n" + 270 "Content-Length: 0\r\n" + 271 "\r\n", 272 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"}, 282 }, 283 Close: false, 284 ContentLength: 0, 285 }, 286 287 "", 288 }, 289 290 // Status line without a Reason-Phrase, but trailing space. 291 // (permitted by RFC 2616) 292 { 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, 304 }, 305 306 "", 307 }, 308 309 // Status line without a Reason-Phrase, and no trailing space. 310 // (not permitted by RFC 2616, but we'll accept it anyway) 311 { 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, 323 }, 324 325 "", 326 }, 327 328 // golang.org/issue/4767: don't special-case multipart/byteranges responses 329 { 330 `HTTP/1.1 206 Partial Content 331 Connection: close 332 Content-Type: multipart/byteranges; boundary=18a75608c8f47cef 333 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"}, 344 }, 345 Close: true, 346 ContentLength: -1, 347 }, 348 349 "some body", 350 }, 351 352 // Unchunked response without Content-Length, Request is nil 353 { 354 "HTTP/1.0 200 OK\r\n" + 355 "Connection: close\r\n" + 356 "\r\n" + 357 "Body here\n", 358 359 Response{ 360 Status: "200 OK", 361 StatusCode: 200, 362 Proto: "HTTP/1.0", 363 ProtoMajor: 1, 364 ProtoMinor: 0, 365 Header: Header{ 366 "Connection": {"close"}, // TODO(rsc): Delete? 367 }, 368 Close: true, 369 ContentLength: -1, 370 }, 371 372 "Body here\n", 373 }, 374 } 375 376 func TestReadResponse(t *testing.T) { 377 for i, tt := range respTests { 378 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) 379 if err != nil { 380 t.Errorf("#%d: %v", i, err) 381 continue 382 } 383 rbody := resp.Body 384 resp.Body = nil 385 diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp) 386 var bout bytes.Buffer 387 if rbody != nil { 388 _, err = io.Copy(&bout, rbody) 389 if err != nil { 390 t.Errorf("#%d: %v", i, err) 391 continue 392 } 393 rbody.Close() 394 } 395 body := bout.String() 396 if body != tt.Body { 397 t.Errorf("#%d: Body = %q want %q", i, body, tt.Body) 398 } 399 } 400 } 401 402 func TestWriteResponse(t *testing.T) { 403 for i, tt := range respTests { 404 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) 405 if err != nil { 406 t.Errorf("#%d: %v", i, err) 407 continue 408 } 409 bout := bytes.NewBuffer(nil) 410 err = resp.Write(bout) 411 if err != nil { 412 t.Errorf("#%d: %v", i, err) 413 continue 414 } 415 } 416 } 417 418 var readResponseCloseInMiddleTests = []struct { 419 chunked, compressed bool 420 }{ 421 {false, false}, 422 {true, false}, 423 {true, true}, 424 } 425 426 // TestReadResponseCloseInMiddle tests that closing a body after 427 // reading only part of its contents advances the read to the end of 428 // the request, right up until the next request. 429 func TestReadResponseCloseInMiddle(t *testing.T) { 430 for _, test := range readResponseCloseInMiddleTests { 431 fatalf := func(format string, args ...interface{}) { 432 args = append([]interface{}{test.chunked, test.compressed}, args...) 433 t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...) 434 } 435 checkErr := func(err error, msg string) { 436 if err == nil { 437 return 438 } 439 fatalf(msg+": %v", err) 440 } 441 var buf bytes.Buffer 442 buf.WriteString("HTTP/1.1 200 OK\r\n") 443 if test.chunked { 444 buf.WriteString("Transfer-Encoding: chunked\r\n") 445 } else { 446 buf.WriteString("Content-Length: 1000000\r\n") 447 } 448 var wr io.Writer = &buf 449 if test.chunked { 450 wr = newChunkedWriter(wr) 451 } 452 if test.compressed { 453 buf.WriteString("Content-Encoding: gzip\r\n") 454 wr = gzip.NewWriter(wr) 455 } 456 buf.WriteString("\r\n") 457 458 chunk := bytes.Repeat([]byte{'x'}, 1000) 459 for i := 0; i < 1000; i++ { 460 if test.compressed { 461 // Otherwise this compresses too well. 462 _, err := io.ReadFull(rand.Reader, chunk) 463 checkErr(err, "rand.Reader ReadFull") 464 } 465 wr.Write(chunk) 466 } 467 if test.compressed { 468 err := wr.(*gzip.Writer).Close() 469 checkErr(err, "compressor close") 470 } 471 if test.chunked { 472 buf.WriteString("0\r\n\r\n") 473 } 474 buf.WriteString("Next Request Here") 475 476 bufr := bufio.NewReader(&buf) 477 resp, err := ReadResponse(bufr, dummyReq("GET")) 478 checkErr(err, "ReadResponse") 479 expectedLength := int64(-1) 480 if !test.chunked { 481 expectedLength = 1000000 482 } 483 if resp.ContentLength != expectedLength { 484 fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength) 485 } 486 if resp.Body == nil { 487 fatalf("nil body") 488 } 489 if test.compressed { 490 gzReader, err := gzip.NewReader(resp.Body) 491 checkErr(err, "gzip.NewReader") 492 resp.Body = &readerAndCloser{gzReader, resp.Body} 493 } 494 495 rbuf := make([]byte, 2500) 496 n, err := io.ReadFull(resp.Body, rbuf) 497 checkErr(err, "2500 byte ReadFull") 498 if n != 2500 { 499 fatalf("ReadFull only read %d bytes", n) 500 } 501 if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) { 502 fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf)) 503 } 504 resp.Body.Close() 505 506 rest, err := ioutil.ReadAll(bufr) 507 checkErr(err, "ReadAll on remainder") 508 if e, g := "Next Request Here", string(rest); e != g { 509 fatalf("remainder = %q, expected %q", g, e) 510 } 511 } 512 } 513 514 func diff(t *testing.T, prefix string, have, want interface{}) { 515 hv := reflect.ValueOf(have).Elem() 516 wv := reflect.ValueOf(want).Elem() 517 if hv.Type() != wv.Type() { 518 t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type()) 519 } 520 for i := 0; i < hv.NumField(); i++ { 521 hf := hv.Field(i).Interface() 522 wf := wv.Field(i).Interface() 523 if !reflect.DeepEqual(hf, wf) { 524 t.Errorf("%s: %s = %v want %v", prefix, hv.Type().Field(i).Name, hf, wf) 525 } 526 } 527 } 528 529 type responseLocationTest struct { 530 location string // Response's Location header or "" 531 requrl string // Response.Request.URL or "" 532 want string 533 wantErr error 534 } 535 536 var responseLocationTests = []responseLocationTest{ 537 {"/foo", "http://bar.com/baz", "http://bar.com/foo", nil}, 538 {"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil}, 539 {"", "http://bar.com/baz", "", ErrNoLocation}, 540 } 541 542 func TestLocationResponse(t *testing.T) { 543 for i, tt := range responseLocationTests { 544 res := new(Response) 545 res.Header = make(Header) 546 res.Header.Set("Location", tt.location) 547 if tt.requrl != "" { 548 res.Request = &Request{} 549 var err error 550 res.Request.URL, err = url.Parse(tt.requrl) 551 if err != nil { 552 t.Fatalf("bad test URL %q: %v", tt.requrl, err) 553 } 554 } 555 556 got, err := res.Location() 557 if tt.wantErr != nil { 558 if err == nil { 559 t.Errorf("%d. err=nil; want %q", i, tt.wantErr) 560 continue 561 } 562 if g, e := err.Error(), tt.wantErr.Error(); g != e { 563 t.Errorf("%d. err=%q; want %q", i, g, e) 564 continue 565 } 566 continue 567 } 568 if err != nil { 569 t.Errorf("%d. err=%q", i, err) 570 continue 571 } 572 if g, e := got.String(), tt.want; g != e { 573 t.Errorf("%d. Location=%q; want %q", i, g, e) 574 } 575 } 576 } 577 578 func TestResponseStatusStutter(t *testing.T) { 579 r := &Response{ 580 Status: "123 some status", 581 StatusCode: 123, 582 ProtoMajor: 1, 583 ProtoMinor: 3, 584 } 585 var buf bytes.Buffer 586 r.Write(&buf) 587 if strings.Contains(buf.String(), "123 123") { 588 t.Errorf("stutter in status: %s", buf.String()) 589 } 590 } 591 592 func TestResponseContentLengthShortBody(t *testing.T) { 593 const shortBody = "Short body, not 123 bytes." 594 br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" + 595 "Content-Length: 123\r\n" + 596 "\r\n" + 597 shortBody)) 598 res, err := ReadResponse(br, &Request{Method: "GET"}) 599 if err != nil { 600 t.Fatal(err) 601 } 602 if res.ContentLength != 123 { 603 t.Fatalf("Content-Length = %d; want 123", res.ContentLength) 604 } 605 var buf bytes.Buffer 606 n, err := io.Copy(&buf, res.Body) 607 if n != int64(len(shortBody)) { 608 t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody) 609 } 610 if buf.String() != shortBody { 611 t.Errorf("Read body %q; want %q", buf.String(), shortBody) 612 } 613 if err != io.ErrUnexpectedEOF { 614 t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err) 615 } 616 }