github.com/guyezi/gofrontend@v0.0.0-20200228202240-7a62a49e62c0/libgo/go/net/http/httputil/dump_test.go (about) 1 // Copyright 2011 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 httputil 6 7 import ( 8 "bufio" 9 "bytes" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net/http" 14 "net/url" 15 "runtime" 16 "strings" 17 "testing" 18 ) 19 20 type eofReader struct{} 21 22 func (n eofReader) Close() error { return nil } 23 24 func (n eofReader) Read([]byte) (int, error) { return 0, io.EOF } 25 26 type dumpTest struct { 27 // Either Req or GetReq can be set/nil but not both. 28 Req *http.Request 29 GetReq func() *http.Request 30 31 Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body 32 33 WantDump string 34 WantDumpOut string 35 MustError bool // if true, the test is expected to throw an error 36 NoBody bool // if true, set DumpRequest{,Out} body to false 37 } 38 39 var dumpTests = []dumpTest{ 40 // HTTP/1.1 => chunked coding; body; empty trailer 41 { 42 Req: &http.Request{ 43 Method: "GET", 44 URL: &url.URL{ 45 Scheme: "http", 46 Host: "www.google.com", 47 Path: "/search", 48 }, 49 ProtoMajor: 1, 50 ProtoMinor: 1, 51 TransferEncoding: []string{"chunked"}, 52 }, 53 54 Body: []byte("abcdef"), 55 56 WantDump: "GET /search HTTP/1.1\r\n" + 57 "Host: www.google.com\r\n" + 58 "Transfer-Encoding: chunked\r\n\r\n" + 59 chunk("abcdef") + chunk(""), 60 }, 61 62 // Verify that DumpRequest preserves the HTTP version number, doesn't add a Host, 63 // and doesn't add a User-Agent. 64 { 65 Req: &http.Request{ 66 Method: "GET", 67 URL: mustParseURL("/foo"), 68 ProtoMajor: 1, 69 ProtoMinor: 0, 70 Header: http.Header{ 71 "X-Foo": []string{"X-Bar"}, 72 }, 73 }, 74 75 WantDump: "GET /foo HTTP/1.0\r\n" + 76 "X-Foo: X-Bar\r\n\r\n", 77 }, 78 79 { 80 Req: mustNewRequest("GET", "http://example.com/foo", nil), 81 82 WantDumpOut: "GET /foo HTTP/1.1\r\n" + 83 "Host: example.com\r\n" + 84 "User-Agent: Go-http-client/1.1\r\n" + 85 "Accept-Encoding: gzip\r\n\r\n", 86 }, 87 88 // Test that an https URL doesn't try to do an SSL negotiation 89 // with a bytes.Buffer and hang with all goroutines not 90 // runnable. 91 { 92 Req: mustNewRequest("GET", "https://example.com/foo", nil), 93 WantDumpOut: "GET /foo HTTP/1.1\r\n" + 94 "Host: example.com\r\n" + 95 "User-Agent: Go-http-client/1.1\r\n" + 96 "Accept-Encoding: gzip\r\n\r\n", 97 }, 98 99 // Request with Body, but Dump requested without it. 100 { 101 Req: &http.Request{ 102 Method: "POST", 103 URL: &url.URL{ 104 Scheme: "http", 105 Host: "post.tld", 106 Path: "/", 107 }, 108 ContentLength: 6, 109 ProtoMajor: 1, 110 ProtoMinor: 1, 111 }, 112 113 Body: []byte("abcdef"), 114 115 WantDumpOut: "POST / HTTP/1.1\r\n" + 116 "Host: post.tld\r\n" + 117 "User-Agent: Go-http-client/1.1\r\n" + 118 "Content-Length: 6\r\n" + 119 "Accept-Encoding: gzip\r\n\r\n", 120 121 NoBody: true, 122 }, 123 124 // Request with Body > 8196 (default buffer size) 125 { 126 Req: &http.Request{ 127 Method: "POST", 128 URL: &url.URL{ 129 Scheme: "http", 130 Host: "post.tld", 131 Path: "/", 132 }, 133 Header: http.Header{ 134 "Content-Length": []string{"8193"}, 135 }, 136 137 ContentLength: 8193, 138 ProtoMajor: 1, 139 ProtoMinor: 1, 140 }, 141 142 Body: bytes.Repeat([]byte("a"), 8193), 143 144 WantDumpOut: "POST / HTTP/1.1\r\n" + 145 "Host: post.tld\r\n" + 146 "User-Agent: Go-http-client/1.1\r\n" + 147 "Content-Length: 8193\r\n" + 148 "Accept-Encoding: gzip\r\n\r\n" + 149 strings.Repeat("a", 8193), 150 WantDump: "POST / HTTP/1.1\r\n" + 151 "Host: post.tld\r\n" + 152 "Content-Length: 8193\r\n\r\n" + 153 strings.Repeat("a", 8193), 154 }, 155 156 { 157 GetReq: func() *http.Request { 158 return mustReadRequest("GET http://foo.com/ HTTP/1.1\r\n" + 159 "User-Agent: blah\r\n\r\n") 160 }, 161 NoBody: true, 162 WantDump: "GET http://foo.com/ HTTP/1.1\r\n" + 163 "User-Agent: blah\r\n\r\n", 164 }, 165 166 // Issue #7215. DumpRequest should return the "Content-Length" when set 167 { 168 GetReq: func() *http.Request { 169 return mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" + 170 "Host: passport.myhost.com\r\n" + 171 "Content-Length: 3\r\n" + 172 "\r\nkey1=name1&key2=name2") 173 }, 174 WantDump: "POST /v2/api/?login HTTP/1.1\r\n" + 175 "Host: passport.myhost.com\r\n" + 176 "Content-Length: 3\r\n" + 177 "\r\nkey", 178 }, 179 // Issue #7215. DumpRequest should return the "Content-Length" in ReadRequest 180 { 181 GetReq: func() *http.Request { 182 return mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" + 183 "Host: passport.myhost.com\r\n" + 184 "Content-Length: 0\r\n" + 185 "\r\nkey1=name1&key2=name2") 186 }, 187 WantDump: "POST /v2/api/?login HTTP/1.1\r\n" + 188 "Host: passport.myhost.com\r\n" + 189 "Content-Length: 0\r\n\r\n", 190 }, 191 192 // Issue #7215. DumpRequest should not return the "Content-Length" if unset 193 { 194 GetReq: func() *http.Request { 195 return mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" + 196 "Host: passport.myhost.com\r\n" + 197 "\r\nkey1=name1&key2=name2") 198 }, 199 WantDump: "POST /v2/api/?login HTTP/1.1\r\n" + 200 "Host: passport.myhost.com\r\n\r\n", 201 }, 202 203 // Issue 18506: make drainBody recognize NoBody. Otherwise 204 // this was turning into a chunked request. 205 { 206 Req: mustNewRequest("POST", "http://example.com/foo", http.NoBody), 207 WantDumpOut: "POST /foo HTTP/1.1\r\n" + 208 "Host: example.com\r\n" + 209 "User-Agent: Go-http-client/1.1\r\n" + 210 "Content-Length: 0\r\n" + 211 "Accept-Encoding: gzip\r\n\r\n", 212 }, 213 214 // Issue 34504: a non-nil Body without ContentLength set should be chunked 215 { 216 Req: &http.Request{ 217 Method: "PUT", 218 URL: &url.URL{ 219 Scheme: "http", 220 Host: "post.tld", 221 Path: "/test", 222 }, 223 ContentLength: 0, 224 Proto: "HTTP/1.1", 225 ProtoMajor: 1, 226 ProtoMinor: 1, 227 Body: &eofReader{}, 228 }, 229 NoBody: true, 230 WantDumpOut: "PUT /test HTTP/1.1\r\n" + 231 "Host: post.tld\r\n" + 232 "User-Agent: Go-http-client/1.1\r\n" + 233 "Transfer-Encoding: chunked\r\n" + 234 "Accept-Encoding: gzip\r\n\r\n", 235 }, 236 } 237 238 func TestDumpRequest(t *testing.T) { 239 // Make a copy of dumpTests and add 10 new cases with an empty URL 240 // to test that no goroutines are leaked. See golang.org/issue/32571. 241 // 10 seems to be a decent number which always triggers the failure. 242 dumpTests := dumpTests[:] 243 for i := 0; i < 10; i++ { 244 dumpTests = append(dumpTests, dumpTest{ 245 Req: mustNewRequest("GET", "", nil), 246 MustError: true, 247 }) 248 } 249 numg0 := runtime.NumGoroutine() 250 for i, tt := range dumpTests { 251 if tt.Req != nil && tt.GetReq != nil || tt.Req == nil && tt.GetReq == nil { 252 t.Errorf("#%d: either .Req(%p) or .GetReq(%p) can be set/nil but not both", i, tt.Req, tt.GetReq) 253 continue 254 } 255 256 freshReq := func(ti dumpTest) *http.Request { 257 req := ti.Req 258 if req == nil { 259 req = ti.GetReq() 260 } 261 262 if req.Header == nil { 263 req.Header = make(http.Header) 264 } 265 266 if ti.Body == nil { 267 return req 268 } 269 switch b := ti.Body.(type) { 270 case []byte: 271 req.Body = ioutil.NopCloser(bytes.NewReader(b)) 272 case func() io.ReadCloser: 273 req.Body = b() 274 default: 275 t.Fatalf("Test %d: unsupported Body of %T", i, ti.Body) 276 } 277 return req 278 } 279 280 if tt.WantDump != "" { 281 req := freshReq(tt) 282 dump, err := DumpRequest(req, !tt.NoBody) 283 if err != nil { 284 t.Errorf("DumpRequest #%d: %s\nWantDump:\n%s", i, err, tt.WantDump) 285 continue 286 } 287 if string(dump) != tt.WantDump { 288 t.Errorf("DumpRequest %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDump, string(dump)) 289 continue 290 } 291 } 292 293 if tt.MustError { 294 req := freshReq(tt) 295 _, err := DumpRequestOut(req, !tt.NoBody) 296 if err == nil { 297 t.Errorf("DumpRequestOut #%d: expected an error, got nil", i) 298 } 299 continue 300 } 301 302 if tt.WantDumpOut != "" { 303 req := freshReq(tt) 304 dump, err := DumpRequestOut(req, !tt.NoBody) 305 if err != nil { 306 t.Errorf("DumpRequestOut #%d: %s", i, err) 307 continue 308 } 309 if string(dump) != tt.WantDumpOut { 310 t.Errorf("DumpRequestOut %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDumpOut, string(dump)) 311 continue 312 } 313 } 314 } 315 if dg := runtime.NumGoroutine() - numg0; dg > 4 { 316 buf := make([]byte, 4096) 317 buf = buf[:runtime.Stack(buf, true)] 318 t.Errorf("Unexpectedly large number of new goroutines: %d new: %s", dg, buf) 319 } 320 } 321 322 func chunk(s string) string { 323 return fmt.Sprintf("%x\r\n%s\r\n", len(s), s) 324 } 325 326 func mustParseURL(s string) *url.URL { 327 u, err := url.Parse(s) 328 if err != nil { 329 panic(fmt.Sprintf("Error parsing URL %q: %v", s, err)) 330 } 331 return u 332 } 333 334 func mustNewRequest(method, url string, body io.Reader) *http.Request { 335 req, err := http.NewRequest(method, url, body) 336 if err != nil { 337 panic(fmt.Sprintf("NewRequest(%q, %q, %p) err = %v", method, url, body, err)) 338 } 339 return req 340 } 341 342 func mustReadRequest(s string) *http.Request { 343 req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(s))) 344 if err != nil { 345 panic(err) 346 } 347 return req 348 } 349 350 var dumpResTests = []struct { 351 res *http.Response 352 body bool 353 want string 354 }{ 355 { 356 res: &http.Response{ 357 Status: "200 OK", 358 StatusCode: 200, 359 Proto: "HTTP/1.1", 360 ProtoMajor: 1, 361 ProtoMinor: 1, 362 ContentLength: 50, 363 Header: http.Header{ 364 "Foo": []string{"Bar"}, 365 }, 366 Body: ioutil.NopCloser(strings.NewReader("foo")), // shouldn't be used 367 }, 368 body: false, // to verify we see 50, not empty or 3. 369 want: `HTTP/1.1 200 OK 370 Content-Length: 50 371 Foo: Bar`, 372 }, 373 374 { 375 res: &http.Response{ 376 Status: "200 OK", 377 StatusCode: 200, 378 Proto: "HTTP/1.1", 379 ProtoMajor: 1, 380 ProtoMinor: 1, 381 ContentLength: 3, 382 Body: ioutil.NopCloser(strings.NewReader("foo")), 383 }, 384 body: true, 385 want: `HTTP/1.1 200 OK 386 Content-Length: 3 387 388 foo`, 389 }, 390 391 { 392 res: &http.Response{ 393 Status: "200 OK", 394 StatusCode: 200, 395 Proto: "HTTP/1.1", 396 ProtoMajor: 1, 397 ProtoMinor: 1, 398 ContentLength: -1, 399 Body: ioutil.NopCloser(strings.NewReader("foo")), 400 TransferEncoding: []string{"chunked"}, 401 }, 402 body: true, 403 want: `HTTP/1.1 200 OK 404 Transfer-Encoding: chunked 405 406 3 407 foo 408 0`, 409 }, 410 { 411 res: &http.Response{ 412 Status: "200 OK", 413 StatusCode: 200, 414 Proto: "HTTP/1.1", 415 ProtoMajor: 1, 416 ProtoMinor: 1, 417 ContentLength: 0, 418 Header: http.Header{ 419 // To verify if headers are not filtered out. 420 "Foo1": []string{"Bar1"}, 421 "Foo2": []string{"Bar2"}, 422 }, 423 Body: nil, 424 }, 425 body: false, // to verify we see 0, not empty. 426 want: `HTTP/1.1 200 OK 427 Foo1: Bar1 428 Foo2: Bar2 429 Content-Length: 0`, 430 }, 431 } 432 433 func TestDumpResponse(t *testing.T) { 434 for i, tt := range dumpResTests { 435 gotb, err := DumpResponse(tt.res, tt.body) 436 if err != nil { 437 t.Errorf("%d. DumpResponse = %v", i, err) 438 continue 439 } 440 got := string(gotb) 441 got = strings.TrimSpace(got) 442 got = strings.ReplaceAll(got, "\r", "") 443 444 if got != tt.want { 445 t.Errorf("%d.\nDumpResponse got:\n%s\n\nWant:\n%s\n", i, got, tt.want) 446 } 447 } 448 }