github.com/4ad/go@v0.0.0-20161219182952-69a12818b605/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 189 func TestDumpRequest(t *testing.T) { 190 numg0 := runtime.NumGoroutine() 191 for i, tt := range dumpTests { 192 setBody := func() { 193 if tt.Body == nil { 194 return 195 } 196 switch b := tt.Body.(type) { 197 case []byte: 198 tt.Req.Body = ioutil.NopCloser(bytes.NewReader(b)) 199 case func() io.ReadCloser: 200 tt.Req.Body = b() 201 default: 202 t.Fatalf("Test %d: unsupported Body of %T", i, tt.Body) 203 } 204 } 205 setBody() 206 if tt.Req.Header == nil { 207 tt.Req.Header = make(http.Header) 208 } 209 210 if tt.WantDump != "" { 211 setBody() 212 dump, err := DumpRequest(&tt.Req, !tt.NoBody) 213 if err != nil { 214 t.Errorf("DumpRequest #%d: %s", i, err) 215 continue 216 } 217 if string(dump) != tt.WantDump { 218 t.Errorf("DumpRequest %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDump, string(dump)) 219 continue 220 } 221 } 222 223 if tt.WantDumpOut != "" { 224 setBody() 225 dump, err := DumpRequestOut(&tt.Req, !tt.NoBody) 226 if err != nil { 227 t.Errorf("DumpRequestOut #%d: %s", i, err) 228 continue 229 } 230 if string(dump) != tt.WantDumpOut { 231 t.Errorf("DumpRequestOut %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDumpOut, string(dump)) 232 continue 233 } 234 } 235 } 236 if dg := runtime.NumGoroutine() - numg0; dg > 4 { 237 buf := make([]byte, 4096) 238 buf = buf[:runtime.Stack(buf, true)] 239 t.Errorf("Unexpectedly large number of new goroutines: %d new: %s", dg, buf) 240 } 241 } 242 243 func chunk(s string) string { 244 return fmt.Sprintf("%x\r\n%s\r\n", len(s), s) 245 } 246 247 func mustParseURL(s string) *url.URL { 248 u, err := url.Parse(s) 249 if err != nil { 250 panic(fmt.Sprintf("Error parsing URL %q: %v", s, err)) 251 } 252 return u 253 } 254 255 func mustNewRequest(method, url string, body io.Reader) *http.Request { 256 req, err := http.NewRequest(method, url, body) 257 if err != nil { 258 panic(fmt.Sprintf("NewRequest(%q, %q, %p) err = %v", method, url, body, err)) 259 } 260 return req 261 } 262 263 func mustReadRequest(s string) *http.Request { 264 req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(s))) 265 if err != nil { 266 panic(err) 267 } 268 return req 269 } 270 271 var dumpResTests = []struct { 272 res *http.Response 273 body bool 274 want string 275 }{ 276 { 277 res: &http.Response{ 278 Status: "200 OK", 279 StatusCode: 200, 280 Proto: "HTTP/1.1", 281 ProtoMajor: 1, 282 ProtoMinor: 1, 283 ContentLength: 50, 284 Header: http.Header{ 285 "Foo": []string{"Bar"}, 286 }, 287 Body: ioutil.NopCloser(strings.NewReader("foo")), // shouldn't be used 288 }, 289 body: false, // to verify we see 50, not empty or 3. 290 want: `HTTP/1.1 200 OK 291 Content-Length: 50 292 Foo: Bar`, 293 }, 294 295 { 296 res: &http.Response{ 297 Status: "200 OK", 298 StatusCode: 200, 299 Proto: "HTTP/1.1", 300 ProtoMajor: 1, 301 ProtoMinor: 1, 302 ContentLength: 3, 303 Body: ioutil.NopCloser(strings.NewReader("foo")), 304 }, 305 body: true, 306 want: `HTTP/1.1 200 OK 307 Content-Length: 3 308 309 foo`, 310 }, 311 312 { 313 res: &http.Response{ 314 Status: "200 OK", 315 StatusCode: 200, 316 Proto: "HTTP/1.1", 317 ProtoMajor: 1, 318 ProtoMinor: 1, 319 ContentLength: -1, 320 Body: ioutil.NopCloser(strings.NewReader("foo")), 321 TransferEncoding: []string{"chunked"}, 322 }, 323 body: true, 324 want: `HTTP/1.1 200 OK 325 Transfer-Encoding: chunked 326 327 3 328 foo 329 0`, 330 }, 331 { 332 res: &http.Response{ 333 Status: "200 OK", 334 StatusCode: 200, 335 Proto: "HTTP/1.1", 336 ProtoMajor: 1, 337 ProtoMinor: 1, 338 ContentLength: 0, 339 Header: http.Header{ 340 // To verify if headers are not filtered out. 341 "Foo1": []string{"Bar1"}, 342 "Foo2": []string{"Bar2"}, 343 }, 344 Body: nil, 345 }, 346 body: false, // to verify we see 0, not empty. 347 want: `HTTP/1.1 200 OK 348 Foo1: Bar1 349 Foo2: Bar2 350 Content-Length: 0`, 351 }, 352 } 353 354 func TestDumpResponse(t *testing.T) { 355 for i, tt := range dumpResTests { 356 gotb, err := DumpResponse(tt.res, tt.body) 357 if err != nil { 358 t.Errorf("%d. DumpResponse = %v", i, err) 359 continue 360 } 361 got := string(gotb) 362 got = strings.TrimSpace(got) 363 got = strings.Replace(got, "\r", "", -1) 364 365 if got != tt.want { 366 t.Errorf("%d.\nDumpResponse got:\n%s\n\nWant:\n%s\n", i, got, tt.want) 367 } 368 } 369 }