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