github.com/miolini/go@v0.0.0-20160405192216-fca68c8cb408/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 ContentLength: 8193, 126 ProtoMajor: 1, 127 ProtoMinor: 1, 128 }, 129 130 Body: bytes.Repeat([]byte("a"), 8193), 131 132 WantDumpOut: "POST / HTTP/1.1\r\n" + 133 "Host: post.tld\r\n" + 134 "User-Agent: Go-http-client/1.1\r\n" + 135 "Content-Length: 8193\r\n" + 136 "Accept-Encoding: gzip\r\n\r\n" + 137 strings.Repeat("a", 8193), 138 }, 139 140 { 141 Req: *mustReadRequest("GET http://foo.com/ HTTP/1.1\r\n" + 142 "User-Agent: blah\r\n\r\n"), 143 NoBody: true, 144 WantDump: "GET http://foo.com/ HTTP/1.1\r\n" + 145 "User-Agent: blah\r\n\r\n", 146 }, 147 } 148 149 func TestDumpRequest(t *testing.T) { 150 numg0 := runtime.NumGoroutine() 151 for i, tt := range dumpTests { 152 setBody := func() { 153 if tt.Body == nil { 154 return 155 } 156 switch b := tt.Body.(type) { 157 case []byte: 158 tt.Req.Body = ioutil.NopCloser(bytes.NewReader(b)) 159 case func() io.ReadCloser: 160 tt.Req.Body = b() 161 default: 162 t.Fatalf("Test %d: unsupported Body of %T", i, tt.Body) 163 } 164 } 165 setBody() 166 if tt.Req.Header == nil { 167 tt.Req.Header = make(http.Header) 168 } 169 170 if tt.WantDump != "" { 171 setBody() 172 dump, err := DumpRequest(&tt.Req, !tt.NoBody) 173 if err != nil { 174 t.Errorf("DumpRequest #%d: %s", i, err) 175 continue 176 } 177 if string(dump) != tt.WantDump { 178 t.Errorf("DumpRequest %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDump, string(dump)) 179 continue 180 } 181 } 182 183 if tt.WantDumpOut != "" { 184 setBody() 185 dump, err := DumpRequestOut(&tt.Req, !tt.NoBody) 186 if err != nil { 187 t.Errorf("DumpRequestOut #%d: %s", i, err) 188 continue 189 } 190 if string(dump) != tt.WantDumpOut { 191 t.Errorf("DumpRequestOut %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDumpOut, string(dump)) 192 continue 193 } 194 } 195 } 196 if dg := runtime.NumGoroutine() - numg0; dg > 4 { 197 buf := make([]byte, 4096) 198 buf = buf[:runtime.Stack(buf, true)] 199 t.Errorf("Unexpectedly large number of new goroutines: %d new: %s", dg, buf) 200 } 201 } 202 203 func chunk(s string) string { 204 return fmt.Sprintf("%x\r\n%s\r\n", len(s), s) 205 } 206 207 func mustParseURL(s string) *url.URL { 208 u, err := url.Parse(s) 209 if err != nil { 210 panic(fmt.Sprintf("Error parsing URL %q: %v", s, err)) 211 } 212 return u 213 } 214 215 func mustNewRequest(method, url string, body io.Reader) *http.Request { 216 req, err := http.NewRequest(method, url, body) 217 if err != nil { 218 panic(fmt.Sprintf("NewRequest(%q, %q, %p) err = %v", method, url, body, err)) 219 } 220 return req 221 } 222 223 func mustReadRequest(s string) *http.Request { 224 req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(s))) 225 if err != nil { 226 panic(err) 227 } 228 return req 229 } 230 231 var dumpResTests = []struct { 232 res *http.Response 233 body bool 234 want string 235 }{ 236 { 237 res: &http.Response{ 238 Status: "200 OK", 239 StatusCode: 200, 240 Proto: "HTTP/1.1", 241 ProtoMajor: 1, 242 ProtoMinor: 1, 243 ContentLength: 50, 244 Header: http.Header{ 245 "Foo": []string{"Bar"}, 246 }, 247 Body: ioutil.NopCloser(strings.NewReader("foo")), // shouldn't be used 248 }, 249 body: false, // to verify we see 50, not empty or 3. 250 want: `HTTP/1.1 200 OK 251 Content-Length: 50 252 Foo: Bar`, 253 }, 254 255 { 256 res: &http.Response{ 257 Status: "200 OK", 258 StatusCode: 200, 259 Proto: "HTTP/1.1", 260 ProtoMajor: 1, 261 ProtoMinor: 1, 262 ContentLength: 3, 263 Body: ioutil.NopCloser(strings.NewReader("foo")), 264 }, 265 body: true, 266 want: `HTTP/1.1 200 OK 267 Content-Length: 3 268 269 foo`, 270 }, 271 272 { 273 res: &http.Response{ 274 Status: "200 OK", 275 StatusCode: 200, 276 Proto: "HTTP/1.1", 277 ProtoMajor: 1, 278 ProtoMinor: 1, 279 ContentLength: -1, 280 Body: ioutil.NopCloser(strings.NewReader("foo")), 281 TransferEncoding: []string{"chunked"}, 282 }, 283 body: true, 284 want: `HTTP/1.1 200 OK 285 Transfer-Encoding: chunked 286 287 3 288 foo 289 0`, 290 }, 291 { 292 res: &http.Response{ 293 Status: "200 OK", 294 StatusCode: 200, 295 Proto: "HTTP/1.1", 296 ProtoMajor: 1, 297 ProtoMinor: 1, 298 ContentLength: 0, 299 Header: http.Header{ 300 // To verify if headers are not filtered out. 301 "Foo1": []string{"Bar1"}, 302 "Foo2": []string{"Bar2"}, 303 }, 304 Body: nil, 305 }, 306 body: false, // to verify we see 0, not empty. 307 want: `HTTP/1.1 200 OK 308 Foo1: Bar1 309 Foo2: Bar2 310 Content-Length: 0`, 311 }, 312 } 313 314 func TestDumpResponse(t *testing.T) { 315 for i, tt := range dumpResTests { 316 gotb, err := DumpResponse(tt.res, tt.body) 317 if err != nil { 318 t.Errorf("%d. DumpResponse = %v", i, err) 319 continue 320 } 321 got := string(gotb) 322 got = strings.TrimSpace(got) 323 got = strings.Replace(got, "\r", "", -1) 324 325 if got != tt.want { 326 t.Errorf("%d.\nDumpResponse got:\n%s\n\nWant:\n%s\n", i, got, tt.want) 327 } 328 } 329 }