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