github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/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 // Verify that DumpRequest preserves the HTTP version number, doesn't add a Host, 314 // and doesn't add a User-Agent. 315 { 316 Req: Request{ 317 Method: "GET", 318 URL: mustParseURL("/foo"), 319 ProtoMajor: 1, 320 ProtoMinor: 0, 321 Header: Header{ 322 "X-Foo": []string{"X-Bar"}, 323 }, 324 }, 325 326 WantWrite: "GET /foo HTTP/1.1\r\n" + 327 "Host: \r\n" + 328 "User-Agent: Go 1.1 package http\r\n" + 329 "X-Foo: X-Bar\r\n\r\n", 330 }, 331 332 // If no Request.Host and no Request.URL.Host, we send 333 // an empty Host header, and don't use 334 // Request.Header["Host"]. This is just testing that 335 // we don't change Go 1.0 behavior. 336 { 337 Req: Request{ 338 Method: "GET", 339 Host: "", 340 URL: &url.URL{ 341 Scheme: "http", 342 Host: "", 343 Path: "/search", 344 }, 345 ProtoMajor: 1, 346 ProtoMinor: 1, 347 Header: Header{ 348 "Host": []string{"bad.example.com"}, 349 }, 350 }, 351 352 WantWrite: "GET /search HTTP/1.1\r\n" + 353 "Host: \r\n" + 354 "User-Agent: Go 1.1 package http\r\n\r\n", 355 }, 356 357 // Opaque test #1 from golang.org/issue/4860 358 { 359 Req: Request{ 360 Method: "GET", 361 URL: &url.URL{ 362 Scheme: "http", 363 Host: "www.google.com", 364 Opaque: "/%2F/%2F/", 365 }, 366 ProtoMajor: 1, 367 ProtoMinor: 1, 368 Header: Header{}, 369 }, 370 371 WantWrite: "GET /%2F/%2F/ HTTP/1.1\r\n" + 372 "Host: www.google.com\r\n" + 373 "User-Agent: Go 1.1 package http\r\n\r\n", 374 }, 375 376 // Opaque test #2 from golang.org/issue/4860 377 { 378 Req: Request{ 379 Method: "GET", 380 URL: &url.URL{ 381 Scheme: "http", 382 Host: "x.google.com", 383 Opaque: "//y.google.com/%2F/%2F/", 384 }, 385 ProtoMajor: 1, 386 ProtoMinor: 1, 387 Header: Header{}, 388 }, 389 390 WantWrite: "GET http://y.google.com/%2F/%2F/ HTTP/1.1\r\n" + 391 "Host: x.google.com\r\n" + 392 "User-Agent: Go 1.1 package http\r\n\r\n", 393 }, 394 395 // Testing custom case in header keys. Issue 5022. 396 { 397 Req: Request{ 398 Method: "GET", 399 URL: &url.URL{ 400 Scheme: "http", 401 Host: "www.google.com", 402 Path: "/", 403 }, 404 Proto: "HTTP/1.1", 405 ProtoMajor: 1, 406 ProtoMinor: 1, 407 Header: Header{ 408 "ALL-CAPS": {"x"}, 409 }, 410 }, 411 412 WantWrite: "GET / HTTP/1.1\r\n" + 413 "Host: www.google.com\r\n" + 414 "User-Agent: Go 1.1 package http\r\n" + 415 "ALL-CAPS: x\r\n" + 416 "\r\n", 417 }, 418 } 419 420 func TestRequestWrite(t *testing.T) { 421 for i := range reqWriteTests { 422 tt := &reqWriteTests[i] 423 424 setBody := func() { 425 if tt.Body == nil { 426 return 427 } 428 switch b := tt.Body.(type) { 429 case []byte: 430 tt.Req.Body = ioutil.NopCloser(bytes.NewBuffer(b)) 431 case func() io.ReadCloser: 432 tt.Req.Body = b() 433 } 434 } 435 setBody() 436 if tt.Req.Header == nil { 437 tt.Req.Header = make(Header) 438 } 439 440 var braw bytes.Buffer 441 err := tt.Req.Write(&braw) 442 if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.WantError); g != e { 443 t.Errorf("writing #%d, err = %q, want %q", i, g, e) 444 continue 445 } 446 if err != nil { 447 continue 448 } 449 450 if tt.WantWrite != "" { 451 sraw := braw.String() 452 if sraw != tt.WantWrite { 453 t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantWrite, sraw) 454 continue 455 } 456 } 457 458 if tt.WantProxy != "" { 459 setBody() 460 var praw bytes.Buffer 461 err = tt.Req.WriteProxy(&praw) 462 if err != nil { 463 t.Errorf("WriteProxy #%d: %s", i, err) 464 continue 465 } 466 sraw := praw.String() 467 if sraw != tt.WantProxy { 468 t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantProxy, sraw) 469 continue 470 } 471 } 472 } 473 } 474 475 type closeChecker struct { 476 io.Reader 477 closed bool 478 } 479 480 func (rc *closeChecker) Close() error { 481 rc.closed = true 482 return nil 483 } 484 485 // TestRequestWriteClosesBody tests that Request.Write does close its request.Body. 486 // It also indirectly tests NewRequest and that it doesn't wrap an existing Closer 487 // inside a NopCloser, and that it serializes it correctly. 488 func TestRequestWriteClosesBody(t *testing.T) { 489 rc := &closeChecker{Reader: strings.NewReader("my body")} 490 req, _ := NewRequest("POST", "http://foo.com/", rc) 491 if req.ContentLength != 0 { 492 t.Errorf("got req.ContentLength %d, want 0", req.ContentLength) 493 } 494 buf := new(bytes.Buffer) 495 req.Write(buf) 496 if !rc.closed { 497 t.Error("body not closed after write") 498 } 499 expected := "POST / HTTP/1.1\r\n" + 500 "Host: foo.com\r\n" + 501 "User-Agent: Go 1.1 package http\r\n" + 502 "Transfer-Encoding: chunked\r\n\r\n" + 503 // TODO: currently we don't buffer before chunking, so we get a 504 // single "m" chunk before the other chunks, as this was the 1-byte 505 // read from our MultiReader where we stiched the Body back together 506 // after sniffing whether the Body was 0 bytes or not. 507 chunk("m") + 508 chunk("y body") + 509 chunk("") 510 if buf.String() != expected { 511 t.Errorf("write:\n got: %s\nwant: %s", buf.String(), expected) 512 } 513 } 514 515 func chunk(s string) string { 516 return fmt.Sprintf("%x\r\n%s\r\n", len(s), s) 517 } 518 519 func mustParseURL(s string) *url.URL { 520 u, err := url.Parse(s) 521 if err != nil { 522 panic(fmt.Sprintf("Error parsing URL %q: %v", s, err)) 523 } 524 return u 525 }