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