github.com/ader1990/go@v0.0.0-20140630135419-8c24447fa791/src/pkg/net/http/requestwrite_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 "bytes" 9 "errors" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net/url" 14 "strings" 15 "testing" 16 ) 17 18 type reqWriteTest struct { 19 Req Request 20 Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body 21 22 // Any of these three may be empty to skip that test. 23 WantWrite string // Request.Write 24 WantProxy string // Request.WriteProxy 25 26 WantError error // wanted error from Request.Write 27 } 28 29 var reqWriteTests = []reqWriteTest{ 30 // HTTP/1.1 => chunked coding; no body; no trailer 31 { 32 Req: Request{ 33 Method: "GET", 34 URL: &url.URL{ 35 Scheme: "http", 36 Host: "www.techcrunch.com", 37 Path: "/", 38 }, 39 Proto: "HTTP/1.1", 40 ProtoMajor: 1, 41 ProtoMinor: 1, 42 Header: Header{ 43 "Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}, 44 "Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"}, 45 "Accept-Encoding": {"gzip,deflate"}, 46 "Accept-Language": {"en-us,en;q=0.5"}, 47 "Keep-Alive": {"300"}, 48 "Proxy-Connection": {"keep-alive"}, 49 "User-Agent": {"Fake"}, 50 }, 51 Body: nil, 52 Close: false, 53 Host: "www.techcrunch.com", 54 Form: map[string][]string{}, 55 }, 56 57 WantWrite: "GET / HTTP/1.1\r\n" + 58 "Host: www.techcrunch.com\r\n" + 59 "User-Agent: Fake\r\n" + 60 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + 61 "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + 62 "Accept-Encoding: gzip,deflate\r\n" + 63 "Accept-Language: en-us,en;q=0.5\r\n" + 64 "Keep-Alive: 300\r\n" + 65 "Proxy-Connection: keep-alive\r\n\r\n", 66 67 WantProxy: "GET http://www.techcrunch.com/ HTTP/1.1\r\n" + 68 "Host: www.techcrunch.com\r\n" + 69 "User-Agent: Fake\r\n" + 70 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + 71 "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + 72 "Accept-Encoding: gzip,deflate\r\n" + 73 "Accept-Language: en-us,en;q=0.5\r\n" + 74 "Keep-Alive: 300\r\n" + 75 "Proxy-Connection: keep-alive\r\n\r\n", 76 }, 77 // HTTP/1.1 => chunked coding; body; empty trailer 78 { 79 Req: Request{ 80 Method: "GET", 81 URL: &url.URL{ 82 Scheme: "http", 83 Host: "www.google.com", 84 Path: "/search", 85 }, 86 ProtoMajor: 1, 87 ProtoMinor: 1, 88 Header: Header{}, 89 TransferEncoding: []string{"chunked"}, 90 }, 91 92 Body: []byte("abcdef"), 93 94 WantWrite: "GET /search HTTP/1.1\r\n" + 95 "Host: www.google.com\r\n" + 96 "User-Agent: Go 1.1 package http\r\n" + 97 "Transfer-Encoding: chunked\r\n\r\n" + 98 chunk("abcdef") + chunk(""), 99 100 WantProxy: "GET http://www.google.com/search HTTP/1.1\r\n" + 101 "Host: www.google.com\r\n" + 102 "User-Agent: Go 1.1 package http\r\n" + 103 "Transfer-Encoding: chunked\r\n\r\n" + 104 chunk("abcdef") + chunk(""), 105 }, 106 // HTTP/1.1 POST => chunked coding; body; empty trailer 107 { 108 Req: Request{ 109 Method: "POST", 110 URL: &url.URL{ 111 Scheme: "http", 112 Host: "www.google.com", 113 Path: "/search", 114 }, 115 ProtoMajor: 1, 116 ProtoMinor: 1, 117 Header: Header{}, 118 Close: true, 119 TransferEncoding: []string{"chunked"}, 120 }, 121 122 Body: []byte("abcdef"), 123 124 WantWrite: "POST /search HTTP/1.1\r\n" + 125 "Host: www.google.com\r\n" + 126 "User-Agent: Go 1.1 package http\r\n" + 127 "Connection: close\r\n" + 128 "Transfer-Encoding: chunked\r\n\r\n" + 129 chunk("abcdef") + chunk(""), 130 131 WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" + 132 "Host: www.google.com\r\n" + 133 "User-Agent: Go 1.1 package http\r\n" + 134 "Connection: close\r\n" + 135 "Transfer-Encoding: chunked\r\n\r\n" + 136 chunk("abcdef") + chunk(""), 137 }, 138 139 // HTTP/1.1 POST with Content-Length, no chunking 140 { 141 Req: Request{ 142 Method: "POST", 143 URL: &url.URL{ 144 Scheme: "http", 145 Host: "www.google.com", 146 Path: "/search", 147 }, 148 ProtoMajor: 1, 149 ProtoMinor: 1, 150 Header: Header{}, 151 Close: true, 152 ContentLength: 6, 153 }, 154 155 Body: []byte("abcdef"), 156 157 WantWrite: "POST /search HTTP/1.1\r\n" + 158 "Host: www.google.com\r\n" + 159 "User-Agent: Go 1.1 package http\r\n" + 160 "Connection: close\r\n" + 161 "Content-Length: 6\r\n" + 162 "\r\n" + 163 "abcdef", 164 165 WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" + 166 "Host: www.google.com\r\n" + 167 "User-Agent: Go 1.1 package http\r\n" + 168 "Connection: close\r\n" + 169 "Content-Length: 6\r\n" + 170 "\r\n" + 171 "abcdef", 172 }, 173 174 // HTTP/1.1 POST with Content-Length in headers 175 { 176 Req: Request{ 177 Method: "POST", 178 URL: mustParseURL("http://example.com/"), 179 Host: "example.com", 180 Header: Header{ 181 "Content-Length": []string{"10"}, // ignored 182 }, 183 ContentLength: 6, 184 }, 185 186 Body: []byte("abcdef"), 187 188 WantWrite: "POST / HTTP/1.1\r\n" + 189 "Host: example.com\r\n" + 190 "User-Agent: Go 1.1 package http\r\n" + 191 "Content-Length: 6\r\n" + 192 "\r\n" + 193 "abcdef", 194 195 WantProxy: "POST http://example.com/ HTTP/1.1\r\n" + 196 "Host: example.com\r\n" + 197 "User-Agent: Go 1.1 package http\r\n" + 198 "Content-Length: 6\r\n" + 199 "\r\n" + 200 "abcdef", 201 }, 202 203 // default to HTTP/1.1 204 { 205 Req: Request{ 206 Method: "GET", 207 URL: mustParseURL("/search"), 208 Host: "www.google.com", 209 }, 210 211 WantWrite: "GET /search HTTP/1.1\r\n" + 212 "Host: www.google.com\r\n" + 213 "User-Agent: Go 1.1 package http\r\n" + 214 "\r\n", 215 }, 216 217 // Request with a 0 ContentLength and a 0 byte body. 218 { 219 Req: Request{ 220 Method: "POST", 221 URL: mustParseURL("/"), 222 Host: "example.com", 223 ProtoMajor: 1, 224 ProtoMinor: 1, 225 ContentLength: 0, // as if unset by user 226 }, 227 228 Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 0)) }, 229 230 // RFC 2616 Section 14.13 says Content-Length should be specified 231 // unless body is prohibited by the request method. 232 // Also, nginx expects it for POST and PUT. 233 WantWrite: "POST / HTTP/1.1\r\n" + 234 "Host: example.com\r\n" + 235 "User-Agent: Go 1.1 package http\r\n" + 236 "Content-Length: 0\r\n" + 237 "\r\n", 238 239 WantProxy: "POST / HTTP/1.1\r\n" + 240 "Host: example.com\r\n" + 241 "User-Agent: Go 1.1 package http\r\n" + 242 "Content-Length: 0\r\n" + 243 "\r\n", 244 }, 245 246 // Request with a 0 ContentLength and a 1 byte body. 247 { 248 Req: Request{ 249 Method: "POST", 250 URL: mustParseURL("/"), 251 Host: "example.com", 252 ProtoMajor: 1, 253 ProtoMinor: 1, 254 ContentLength: 0, // as if unset by user 255 }, 256 257 Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 1)) }, 258 259 WantWrite: "POST / HTTP/1.1\r\n" + 260 "Host: example.com\r\n" + 261 "User-Agent: Go 1.1 package http\r\n" + 262 "Transfer-Encoding: chunked\r\n\r\n" + 263 chunk("x") + chunk(""), 264 265 WantProxy: "POST / HTTP/1.1\r\n" + 266 "Host: example.com\r\n" + 267 "User-Agent: Go 1.1 package http\r\n" + 268 "Transfer-Encoding: chunked\r\n\r\n" + 269 chunk("x") + chunk(""), 270 }, 271 272 // Request with a ContentLength of 10 but a 5 byte body. 273 { 274 Req: Request{ 275 Method: "POST", 276 URL: mustParseURL("/"), 277 Host: "example.com", 278 ProtoMajor: 1, 279 ProtoMinor: 1, 280 ContentLength: 10, // but we're going to send only 5 bytes 281 }, 282 Body: []byte("12345"), 283 WantError: errors.New("http: Request.ContentLength=10 with Body length 5"), 284 }, 285 286 // Request with a ContentLength of 4 but an 8 byte body. 287 { 288 Req: Request{ 289 Method: "POST", 290 URL: mustParseURL("/"), 291 Host: "example.com", 292 ProtoMajor: 1, 293 ProtoMinor: 1, 294 ContentLength: 4, // but we're going to try to send 8 bytes 295 }, 296 Body: []byte("12345678"), 297 WantError: errors.New("http: Request.ContentLength=4 with Body length 8"), 298 }, 299 300 // Request with a 5 ContentLength and nil body. 301 { 302 Req: Request{ 303 Method: "POST", 304 URL: mustParseURL("/"), 305 Host: "example.com", 306 ProtoMajor: 1, 307 ProtoMinor: 1, 308 ContentLength: 5, // but we'll omit the body 309 }, 310 WantError: errors.New("http: Request.ContentLength=5 with nil Body"), 311 }, 312 313 // Request with a 0 ContentLength and a body with 1 byte content and an error. 314 { 315 Req: Request{ 316 Method: "POST", 317 URL: mustParseURL("/"), 318 Host: "example.com", 319 ProtoMajor: 1, 320 ProtoMinor: 1, 321 ContentLength: 0, // as if unset by user 322 }, 323 324 Body: func() io.ReadCloser { 325 err := errors.New("Custom reader error") 326 errReader := &errorReader{err} 327 return ioutil.NopCloser(io.MultiReader(strings.NewReader("x"), errReader)) 328 }, 329 330 WantError: errors.New("Custom reader error"), 331 }, 332 333 // Request with a 0 ContentLength and a body without content and an error. 334 { 335 Req: Request{ 336 Method: "POST", 337 URL: mustParseURL("/"), 338 Host: "example.com", 339 ProtoMajor: 1, 340 ProtoMinor: 1, 341 ContentLength: 0, // as if unset by user 342 }, 343 344 Body: func() io.ReadCloser { 345 err := errors.New("Custom reader error") 346 errReader := &errorReader{err} 347 return ioutil.NopCloser(errReader) 348 }, 349 350 WantError: errors.New("Custom reader error"), 351 }, 352 353 // Verify that DumpRequest preserves the HTTP version number, doesn't add a Host, 354 // and doesn't add a User-Agent. 355 { 356 Req: Request{ 357 Method: "GET", 358 URL: mustParseURL("/foo"), 359 ProtoMajor: 1, 360 ProtoMinor: 0, 361 Header: Header{ 362 "X-Foo": []string{"X-Bar"}, 363 }, 364 }, 365 366 WantWrite: "GET /foo HTTP/1.1\r\n" + 367 "Host: \r\n" + 368 "User-Agent: Go 1.1 package http\r\n" + 369 "X-Foo: X-Bar\r\n\r\n", 370 }, 371 372 // If no Request.Host and no Request.URL.Host, we send 373 // an empty Host header, and don't use 374 // Request.Header["Host"]. This is just testing that 375 // we don't change Go 1.0 behavior. 376 { 377 Req: Request{ 378 Method: "GET", 379 Host: "", 380 URL: &url.URL{ 381 Scheme: "http", 382 Host: "", 383 Path: "/search", 384 }, 385 ProtoMajor: 1, 386 ProtoMinor: 1, 387 Header: Header{ 388 "Host": []string{"bad.example.com"}, 389 }, 390 }, 391 392 WantWrite: "GET /search HTTP/1.1\r\n" + 393 "Host: \r\n" + 394 "User-Agent: Go 1.1 package http\r\n\r\n", 395 }, 396 397 // Opaque test #1 from golang.org/issue/4860 398 { 399 Req: Request{ 400 Method: "GET", 401 URL: &url.URL{ 402 Scheme: "http", 403 Host: "www.google.com", 404 Opaque: "/%2F/%2F/", 405 }, 406 ProtoMajor: 1, 407 ProtoMinor: 1, 408 Header: Header{}, 409 }, 410 411 WantWrite: "GET /%2F/%2F/ HTTP/1.1\r\n" + 412 "Host: www.google.com\r\n" + 413 "User-Agent: Go 1.1 package http\r\n\r\n", 414 }, 415 416 // Opaque test #2 from golang.org/issue/4860 417 { 418 Req: Request{ 419 Method: "GET", 420 URL: &url.URL{ 421 Scheme: "http", 422 Host: "x.google.com", 423 Opaque: "//y.google.com/%2F/%2F/", 424 }, 425 ProtoMajor: 1, 426 ProtoMinor: 1, 427 Header: Header{}, 428 }, 429 430 WantWrite: "GET http://y.google.com/%2F/%2F/ HTTP/1.1\r\n" + 431 "Host: x.google.com\r\n" + 432 "User-Agent: Go 1.1 package http\r\n\r\n", 433 }, 434 435 // Testing custom case in header keys. Issue 5022. 436 { 437 Req: Request{ 438 Method: "GET", 439 URL: &url.URL{ 440 Scheme: "http", 441 Host: "www.google.com", 442 Path: "/", 443 }, 444 Proto: "HTTP/1.1", 445 ProtoMajor: 1, 446 ProtoMinor: 1, 447 Header: Header{ 448 "ALL-CAPS": {"x"}, 449 }, 450 }, 451 452 WantWrite: "GET / HTTP/1.1\r\n" + 453 "Host: www.google.com\r\n" + 454 "User-Agent: Go 1.1 package http\r\n" + 455 "ALL-CAPS: x\r\n" + 456 "\r\n", 457 }, 458 } 459 460 func TestRequestWrite(t *testing.T) { 461 for i := range reqWriteTests { 462 tt := &reqWriteTests[i] 463 464 setBody := func() { 465 if tt.Body == nil { 466 return 467 } 468 switch b := tt.Body.(type) { 469 case []byte: 470 tt.Req.Body = ioutil.NopCloser(bytes.NewReader(b)) 471 case func() io.ReadCloser: 472 tt.Req.Body = b() 473 } 474 } 475 setBody() 476 if tt.Req.Header == nil { 477 tt.Req.Header = make(Header) 478 } 479 480 var braw bytes.Buffer 481 err := tt.Req.Write(&braw) 482 if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.WantError); g != e { 483 t.Errorf("writing #%d, err = %q, want %q", i, g, e) 484 continue 485 } 486 if err != nil { 487 continue 488 } 489 490 if tt.WantWrite != "" { 491 sraw := braw.String() 492 if sraw != tt.WantWrite { 493 t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantWrite, sraw) 494 continue 495 } 496 } 497 498 if tt.WantProxy != "" { 499 setBody() 500 var praw bytes.Buffer 501 err = tt.Req.WriteProxy(&praw) 502 if err != nil { 503 t.Errorf("WriteProxy #%d: %s", i, err) 504 continue 505 } 506 sraw := praw.String() 507 if sraw != tt.WantProxy { 508 t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantProxy, sraw) 509 continue 510 } 511 } 512 } 513 } 514 515 type closeChecker struct { 516 io.Reader 517 closed bool 518 } 519 520 func (rc *closeChecker) Close() error { 521 rc.closed = true 522 return nil 523 } 524 525 // TestRequestWriteClosesBody tests that Request.Write does close its request.Body. 526 // It also indirectly tests NewRequest and that it doesn't wrap an existing Closer 527 // inside a NopCloser, and that it serializes it correctly. 528 func TestRequestWriteClosesBody(t *testing.T) { 529 rc := &closeChecker{Reader: strings.NewReader("my body")} 530 req, _ := NewRequest("POST", "http://foo.com/", rc) 531 if req.ContentLength != 0 { 532 t.Errorf("got req.ContentLength %d, want 0", req.ContentLength) 533 } 534 buf := new(bytes.Buffer) 535 req.Write(buf) 536 if !rc.closed { 537 t.Error("body not closed after write") 538 } 539 expected := "POST / HTTP/1.1\r\n" + 540 "Host: foo.com\r\n" + 541 "User-Agent: Go 1.1 package http\r\n" + 542 "Transfer-Encoding: chunked\r\n\r\n" + 543 // TODO: currently we don't buffer before chunking, so we get a 544 // single "m" chunk before the other chunks, as this was the 1-byte 545 // read from our MultiReader where we stiched the Body back together 546 // after sniffing whether the Body was 0 bytes or not. 547 chunk("m") + 548 chunk("y body") + 549 chunk("") 550 if buf.String() != expected { 551 t.Errorf("write:\n got: %s\nwant: %s", buf.String(), expected) 552 } 553 } 554 555 func chunk(s string) string { 556 return fmt.Sprintf("%x\r\n%s\r\n", len(s), s) 557 } 558 559 func mustParseURL(s string) *url.URL { 560 u, err := url.Parse(s) 561 if err != nil { 562 panic(fmt.Sprintf("Error parsing URL %q: %v", s, err)) 563 } 564 return u 565 }