gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/gmhttp/request_test.go (about) 1 // Copyright 2009 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 gmhttp_test 6 7 import ( 8 "bufio" 9 "bytes" 10 "context" 11 "crypto/rand" 12 "encoding/base64" 13 "fmt" 14 "io" 15 "math" 16 "mime/multipart" 17 "net/url" 18 "os" 19 "reflect" 20 "regexp" 21 "strings" 22 "testing" 23 24 . "gitee.com/ks-custle/core-gm/gmhttp" 25 "gitee.com/ks-custle/core-gm/gmhttp/httptest" 26 ) 27 28 func TestQuery(t *testing.T) { 29 req := &Request{Method: "GET"} 30 //goland:noinspection HttpUrlsUsage 31 req.URL, _ = url.Parse("http://www.google.com/search?q=foo&q=bar") 32 if q := req.FormValue("q"); q != "foo" { 33 t.Errorf(`req.FormValue("q") = %q, want "foo"`, q) 34 } 35 } 36 37 // Issue #25192: Test that ParseForm fails but still parses the form when an URL 38 // containing a semicolon is provided. 39 func TestParseFormSemicolonSeparator(t *testing.T) { 40 //goland:noinspection HttpUrlsUsage 41 for _, method := range []string{"POST", "PATCH", "PUT", "GET"} { 42 req, _ := NewRequest(method, "http://www.google.com/search?q=foo;q=bar&a=1", 43 strings.NewReader("q")) 44 err := req.ParseForm() 45 if err == nil { 46 t.Fatalf(`for method %s, ParseForm expected an error, got success`, method) 47 } 48 wantForm := url.Values{"a": []string{"1"}} 49 if !reflect.DeepEqual(req.Form, wantForm) { 50 t.Fatalf("for method %s, ParseForm expected req.Form = %v, want %v", method, req.Form, wantForm) 51 } 52 } 53 } 54 55 func TestParseFormQuery(t *testing.T) { 56 //goland:noinspection HttpUrlsUsage 57 req, _ := NewRequest("POST", "http://www.google.com/search?q=foo&q=bar&both=x&prio=1&orphan=nope&empty=not", 58 strings.NewReader("z=post&both=y&prio=2&=nokey&orphan&empty=&")) 59 req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") 60 61 if q := req.FormValue("q"); q != "foo" { 62 t.Errorf(`req.FormValue("q") = %q, want "foo"`, q) 63 } 64 if z := req.FormValue("z"); z != "post" { 65 t.Errorf(`req.FormValue("z") = %q, want "post"`, z) 66 } 67 if bq, found := req.PostForm["q"]; found { 68 t.Errorf(`req.PostForm["q"] = %q, want no entry in map`, bq) 69 } 70 if bz := req.PostFormValue("z"); bz != "post" { 71 t.Errorf(`req.PostFormValue("z") = %q, want "post"`, bz) 72 } 73 if qs := req.Form["q"]; !reflect.DeepEqual(qs, []string{"foo", "bar"}) { 74 t.Errorf(`req.Form["q"] = %q, want ["foo", "bar"]`, qs) 75 } 76 if both := req.Form["both"]; !reflect.DeepEqual(both, []string{"y", "x"}) { 77 t.Errorf(`req.Form["both"] = %q, want ["y", "x"]`, both) 78 } 79 if prio := req.FormValue("prio"); prio != "2" { 80 t.Errorf(`req.FormValue("prio") = %q, want "2" (from body)`, prio) 81 } 82 if orphan := req.Form["orphan"]; !reflect.DeepEqual(orphan, []string{"", "nope"}) { 83 t.Errorf(`req.FormValue("orphan") = %q, want "" (from body)`, orphan) 84 } 85 if empty := req.Form["empty"]; !reflect.DeepEqual(empty, []string{"", "not"}) { 86 t.Errorf(`req.FormValue("empty") = %q, want "" (from body)`, empty) 87 } 88 if nokey := req.Form[""]; !reflect.DeepEqual(nokey, []string{"nokey"}) { 89 t.Errorf(`req.FormValue("nokey") = %q, want "nokey" (from body)`, nokey) 90 } 91 } 92 93 // Tests that we only parse the form automatically for certain methods. 94 func TestParseFormQueryMethods(t *testing.T) { 95 //goland:noinspection HttpUrlsUsage 96 for _, method := range []string{"POST", "PATCH", "PUT", "FOO"} { 97 req, _ := NewRequest(method, "http://www.google.com/search", 98 strings.NewReader("foo=bar")) 99 req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") 100 want := "bar" 101 if method == "FOO" { 102 want = "" 103 } 104 if got := req.FormValue("foo"); got != want { 105 t.Errorf(`for method %s, FormValue("foo") = %q; want %q`, method, got, want) 106 } 107 } 108 } 109 110 func TestParseFormUnknownContentType(t *testing.T) { 111 for _, test := range []struct { 112 name string 113 wantErr string 114 contentType Header 115 }{ 116 {"text", "", Header{"Content-Type": {"text/plain"}}}, 117 // Empty content type is legal - may be treated as 118 // application/octet-stream (RFC 7231, section 3.1.1.5) 119 {"empty", "", Header{}}, 120 {"boundary", "mime: invalid media parameter", Header{"Content-Type": {"text/plain; boundary="}}}, 121 {"unknown", "", Header{"Content-Type": {"application/unknown"}}}, 122 } { 123 t.Run(test.name, 124 func(t *testing.T) { 125 req := &Request{ 126 Method: "POST", 127 Header: test.contentType, 128 Body: io.NopCloser(strings.NewReader("body")), 129 } 130 err := req.ParseForm() 131 switch { 132 case err == nil && test.wantErr != "": 133 t.Errorf("unexpected success; want error %q", test.wantErr) 134 case err != nil && test.wantErr == "": 135 t.Errorf("want success, got error: %v", err) 136 case test.wantErr != "" && test.wantErr != fmt.Sprint(err): 137 t.Errorf("got error %q; want %q", err, test.wantErr) 138 } 139 }, 140 ) 141 } 142 } 143 144 func TestParseFormInitializeOnError(t *testing.T) { 145 //goland:noinspection HttpUrlsUsage 146 nilBody, _ := NewRequest("POST", "http://www.google.com/search?q=foo", nil) 147 tests := []*Request{ 148 nilBody, 149 {Method: "GET", URL: nil}, 150 } 151 for i, req := range tests { 152 err := req.ParseForm() 153 if req.Form == nil { 154 t.Errorf("%d. Form not initialized, error %v", i, err) 155 } 156 if req.PostForm == nil { 157 t.Errorf("%d. PostForm not initialized, error %v", i, err) 158 } 159 } 160 } 161 162 func TestMultipartReader(t *testing.T) { 163 tests := []struct { 164 shouldError bool 165 contentType string 166 }{ 167 {false, `multipart/form-data; boundary="foo123"`}, 168 {false, `multipart/mixed; boundary="foo123"`}, 169 {true, `text/plain`}, 170 } 171 172 for i, test := range tests { 173 req := &Request{ 174 Method: "POST", 175 Header: Header{"Content-Type": {test.contentType}}, 176 Body: io.NopCloser(new(bytes.Buffer)), 177 } 178 multipartReader, err := req.MultipartReader() 179 if test.shouldError { 180 if err == nil || multipartReader != nil { 181 t.Errorf("test %d: unexpectedly got nil-error (%v) or non-nil-multipart (%v)", i, err, multipartReader) 182 } 183 continue 184 } 185 if err != nil || multipartReader == nil { 186 t.Errorf("test %d: unexpectedly got error (%v) or nil-multipart (%v)", i, err, multipartReader) 187 } 188 } 189 } 190 191 // Issue 9305: ParseMultipartForm should populate PostForm too 192 func TestParseMultipartFormPopulatesPostForm(t *testing.T) { 193 postData := 194 `--xxx 195 Content-Disposition: form-data; name="field1" 196 197 value1 198 --xxx 199 Content-Disposition: form-data; name="field2" 200 201 value2 202 --xxx 203 Content-Disposition: form-data; name="file"; filename="file" 204 Content-Type: application/octet-stream 205 Content-Transfer-Encoding: binary 206 207 binary data 208 --xxx-- 209 ` 210 req := &Request{ 211 Method: "POST", 212 Header: Header{"Content-Type": {`multipart/form-data; boundary=xxx`}}, 213 Body: io.NopCloser(strings.NewReader(postData)), 214 } 215 216 initialFormItems := map[string]string{ 217 "language": "Go", 218 "name": "gopher", 219 "skill": "go-ing", 220 "field2": "initial-value2", 221 } 222 223 req.Form = make(url.Values) 224 for k, v := range initialFormItems { 225 req.Form.Add(k, v) 226 } 227 228 err := req.ParseMultipartForm(10000) 229 if err != nil { 230 t.Fatalf("unexpected multipart error %v", err) 231 } 232 233 wantForm := url.Values{ 234 "language": []string{"Go"}, 235 "name": []string{"gopher"}, 236 "skill": []string{"go-ing"}, 237 "field1": []string{"value1"}, 238 "field2": []string{"initial-value2", "value2"}, 239 } 240 if !reflect.DeepEqual(req.Form, wantForm) { 241 t.Fatalf("req.Form = %v, want %v", req.Form, wantForm) 242 } 243 244 wantPostForm := url.Values{ 245 "field1": []string{"value1"}, 246 "field2": []string{"value2"}, 247 } 248 if !reflect.DeepEqual(req.PostForm, wantPostForm) { 249 t.Fatalf("req.PostForm = %v, want %v", req.PostForm, wantPostForm) 250 } 251 } 252 253 func TestParseMultipartForm(t *testing.T) { 254 req := &Request{ 255 Method: "POST", 256 Header: Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}}, 257 Body: io.NopCloser(new(bytes.Buffer)), 258 } 259 err := req.ParseMultipartForm(25) 260 if err == nil { 261 t.Error("expected multipart EOF, got nil") 262 } 263 264 req.Header = Header{"Content-Type": {"text/plain"}} 265 err = req.ParseMultipartForm(25) 266 if err != ErrNotMultipart { 267 t.Error("expected ErrNotMultipart for text/plain") 268 } 269 } 270 271 // Issue 45789: multipart form should not include directory path in filename 272 func TestParseMultipartFormFilename(t *testing.T) { 273 postData := 274 `--xxx 275 Content-Disposition: form-data; name="file"; filename="../usr/foobar.txt/" 276 Content-Type: text/plain 277 278 --xxx-- 279 ` 280 req := &Request{ 281 Method: "POST", 282 Header: Header{"Content-Type": {`multipart/form-data; boundary=xxx`}}, 283 Body: io.NopCloser(strings.NewReader(postData)), 284 } 285 _, hdr, err := req.FormFile("file") 286 if err != nil { 287 t.Fatal(err) 288 } 289 if hdr.Filename != "foobar.txt" { 290 t.Errorf("expected only the last element of the path, got %q", hdr.Filename) 291 } 292 } 293 294 // Issue #40430: Test that if maxMemory for ParseMultipartForm when combined with 295 // the payload size and the internal leeway buffer size of 10MiB overflows, that we 296 // correctly return an error. 297 func TestMaxInt64ForMultipartFormMaxMemoryOverflow(t *testing.T) { 298 defer afterTest(t) 299 300 payloadSize := 1 << 10 301 cst := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { 302 // The combination of: 303 // MaxInt64 + payloadSize + (internal spare of 10MiB) 304 // triggers the overflow. See issue https://golang.org/issue/40430/ 305 if err := req.ParseMultipartForm(math.MaxInt64); err != nil { 306 Error(rw, err.Error(), StatusBadRequest) 307 return 308 } 309 })) 310 defer cst.Close() 311 fBuf := new(bytes.Buffer) 312 mw := multipart.NewWriter(fBuf) 313 mf, err := mw.CreateFormFile("file", "myfile.txt") 314 if err != nil { 315 t.Fatal(err) 316 } 317 if _, err := mf.Write(bytes.Repeat([]byte("abc"), payloadSize)); err != nil { 318 t.Fatal(err) 319 } 320 if err := mw.Close(); err != nil { 321 t.Fatal(err) 322 } 323 req, err := NewRequest("POST", cst.URL, fBuf) 324 if err != nil { 325 t.Fatal(err) 326 } 327 req.Header.Set("Content-Type", mw.FormDataContentType()) 328 res, err := cst.Client().Do(req) 329 if err != nil { 330 t.Fatal(err) 331 } 332 _ = res.Body.Close() 333 if g, w := res.StatusCode, StatusOK; g != w { 334 t.Fatalf("Status code mismatch: got %d, want %d", g, w) 335 } 336 } 337 338 func TestRedirect_h1(t *testing.T) { testRedirect(t, h1Mode) } 339 func TestRedirect_h2(t *testing.T) { testRedirect(t, h2Mode) } 340 func testRedirect(t *testing.T, h2 bool) { 341 defer afterTest(t) 342 cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) { 343 switch r.URL.Path { 344 case "/": 345 w.Header().Set("Location", "/foo/") 346 w.WriteHeader(StatusSeeOther) 347 case "/foo/": 348 _, _ = fmt.Fprintf(w, "foo") 349 default: 350 w.WriteHeader(StatusBadRequest) 351 } 352 })) 353 defer cst.close() 354 355 var end = regexp.MustCompile("/foo/$") 356 r, err := cst.c.Get(cst.ts.URL) 357 if err != nil { 358 t.Fatal(err) 359 } 360 _ = r.Body.Close() 361 urlStr := r.Request.URL.String() 362 if r.StatusCode != 200 || !end.MatchString(urlStr) { 363 t.Fatalf("Get got status %d at %q, want 200 matching /foo/$", r.StatusCode, urlStr) 364 } 365 } 366 367 func TestSetBasicAuth(t *testing.T) { 368 //goland:noinspection HttpUrlsUsage 369 r, _ := NewRequest("GET", "http://example.com/", nil) 370 r.SetBasicAuth("Aladdin", "open sesame") 371 if g, e := r.Header.Get("Authorization"), "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="; g != e { 372 t.Errorf("got header %q, want %q", g, e) 373 } 374 } 375 376 func TestMultipartRequest(t *testing.T) { 377 // Test that we can read the values and files of a 378 // multipart request with FormValue and FormFile, 379 // and that ParseMultipartForm can be called multiple times. 380 req := newTestMultipartRequest(t) 381 if err := req.ParseMultipartForm(25); err != nil { 382 t.Fatal("ParseMultipartForm first call:", err) 383 } 384 defer func(MultipartForm *multipart.Form) { 385 _ = MultipartForm.RemoveAll() 386 }(req.MultipartForm) 387 validateTestMultipartContents(t, req, false) 388 if err := req.ParseMultipartForm(25); err != nil { 389 t.Fatal("ParseMultipartForm second call:", err) 390 } 391 validateTestMultipartContents(t, req, false) 392 } 393 394 // Issue #25192: Test that ParseMultipartForm fails but still parses the 395 // multi-part form when an URL containing a semicolon is provided. 396 func TestParseMultipartFormSemicolonSeparator(t *testing.T) { 397 req := newTestMultipartRequest(t) 398 req.URL = &url.URL{RawQuery: "q=foo;q=bar"} 399 if err := req.ParseMultipartForm(25); err == nil { 400 t.Fatal("ParseMultipartForm expected error due to invalid semicolon, got nil") 401 } 402 defer func(MultipartForm *multipart.Form) { 403 _ = MultipartForm.RemoveAll() 404 }(req.MultipartForm) 405 validateTestMultipartContents(t, req, false) 406 } 407 408 func TestMultipartRequestAuto(t *testing.T) { 409 // Test that FormValue and FormFile automatically invoke 410 // ParseMultipartForm and return the right values. 411 req := newTestMultipartRequest(t) 412 defer func() { 413 if req.MultipartForm != nil { 414 _ = req.MultipartForm.RemoveAll() 415 } 416 }() 417 validateTestMultipartContents(t, req, true) 418 } 419 420 func TestMissingFileMultipartRequest(t *testing.T) { 421 // Test that FormFile returns an error if 422 // the named file is missing. 423 req := newTestMultipartRequest(t) 424 testMissingFile(t, req) 425 } 426 427 // Test that FormValue invokes ParseMultipartForm. 428 func TestFormValueCallsParseMultipartForm(t *testing.T) { 429 //goland:noinspection HttpUrlsUsage 430 req, _ := NewRequest("POST", "http://www.google.com/", strings.NewReader("z=post")) 431 req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") 432 if req.Form != nil { 433 t.Fatal("Unexpected request Form, want nil") 434 } 435 req.FormValue("z") 436 if req.Form == nil { 437 t.Fatal("ParseMultipartForm not called by FormValue") 438 } 439 } 440 441 // Test that FormFile invokes ParseMultipartForm. 442 func TestFormFileCallsParseMultipartForm(t *testing.T) { 443 req := newTestMultipartRequest(t) 444 if req.Form != nil { 445 t.Fatal("Unexpected request Form, want nil") 446 } 447 _, _, _ = req.FormFile("") 448 if req.Form == nil { 449 t.Fatal("ParseMultipartForm not called by FormFile") 450 } 451 } 452 453 // Test that ParseMultipartForm errors if called 454 // after MultipartReader on the same request. 455 func TestParseMultipartFormOrder(t *testing.T) { 456 req := newTestMultipartRequest(t) 457 if _, err := req.MultipartReader(); err != nil { 458 t.Fatalf("MultipartReader: %v", err) 459 } 460 if err := req.ParseMultipartForm(1024); err == nil { 461 t.Fatal("expected an error from ParseMultipartForm after call to MultipartReader") 462 } 463 } 464 465 // Test that MultipartReader errors if called 466 // after ParseMultipartForm on the same request. 467 func TestMultipartReaderOrder(t *testing.T) { 468 req := newTestMultipartRequest(t) 469 if err := req.ParseMultipartForm(25); err != nil { 470 t.Fatalf("ParseMultipartForm: %v", err) 471 } 472 defer func(MultipartForm *multipart.Form) { 473 _ = MultipartForm.RemoveAll() 474 }(req.MultipartForm) 475 if _, err := req.MultipartReader(); err == nil { 476 t.Fatal("expected an error from MultipartReader after call to ParseMultipartForm") 477 } 478 } 479 480 // Test that FormFile errors if called after 481 // MultipartReader on the same request. 482 func TestFormFileOrder(t *testing.T) { 483 req := newTestMultipartRequest(t) 484 if _, err := req.MultipartReader(); err != nil { 485 t.Fatalf("MultipartReader: %v", err) 486 } 487 if _, _, err := req.FormFile(""); err == nil { 488 t.Fatal("expected an error from FormFile after call to MultipartReader") 489 } 490 } 491 492 var readRequestErrorTests = []struct { 493 in string 494 err string 495 496 header Header 497 }{ 498 0: {"GET / HTTP/1.1\r\nheader:foo\r\n\r\n", "", Header{"Header": {"foo"}}}, 499 1: {"GET / HTTP/1.1\r\nheader:foo\r\n", io.ErrUnexpectedEOF.Error(), nil}, 500 2: {"", io.EOF.Error(), nil}, 501 3: { 502 in: "HEAD / HTTP/1.1\r\nContent-Length:4\r\n\r\n", 503 err: "http: method cannot contain a Content-Length", 504 }, 505 4: { 506 in: "HEAD / HTTP/1.1\r\n\r\n", 507 header: Header{}, 508 }, 509 510 // Multiple Content-Length values should either be 511 // deduplicated if same or reject otherwise 512 // See Issue 16490. 513 5: { 514 in: "POST / HTTP/1.1\r\nContent-Length: 10\r\nContent-Length: 0\r\n\r\nGopher hey\r\n", 515 err: "cannot contain multiple Content-Length headers", 516 }, 517 6: { 518 in: "POST / HTTP/1.1\r\nContent-Length: 10\r\nContent-Length: 6\r\n\r\nGopher\r\n", 519 err: "cannot contain multiple Content-Length headers", 520 }, 521 7: { 522 in: "PUT / HTTP/1.1\r\nContent-Length: 6 \r\nContent-Length: 6\r\nContent-Length:6\r\n\r\nGopher\r\n", 523 err: "", 524 header: Header{"Content-Length": {"6"}}, 525 }, 526 8: { 527 in: "PUT / HTTP/1.1\r\nContent-Length: 1\r\nContent-Length: 6 \r\n\r\n", 528 err: "cannot contain multiple Content-Length headers", 529 }, 530 9: { 531 in: "POST / HTTP/1.1\r\nContent-Length:\r\nContent-Length: 3\r\n\r\n", 532 err: "cannot contain multiple Content-Length headers", 533 }, 534 10: { 535 in: "HEAD / HTTP/1.1\r\nContent-Length:0\r\nContent-Length: 0\r\n\r\n", 536 header: Header{"Content-Length": {"0"}}, 537 }, 538 11: { 539 in: "HEAD / HTTP/1.1\r\nHost: foo\r\nHost: bar\r\n\r\n\r\n\r\n", 540 err: "too many Host headers", 541 }, 542 } 543 544 func TestReadRequestErrors(t *testing.T) { 545 for i, tt := range readRequestErrorTests { 546 req, err := ReadRequest(bufio.NewReader(strings.NewReader(tt.in))) 547 if err == nil { 548 if tt.err != "" { 549 t.Errorf("#%d: got nil err; want %q", i, tt.err) 550 } 551 552 if !reflect.DeepEqual(tt.header, req.Header) { 553 t.Errorf("#%d: gotHeader: %q wantHeader: %q", i, req.Header, tt.header) 554 } 555 continue 556 } 557 558 if tt.err == "" || !strings.Contains(err.Error(), tt.err) { 559 t.Errorf("%d: got error = %v; want %v", i, err, tt.err) 560 } 561 } 562 } 563 564 //goland:noinspection HttpUrlsUsage 565 var newRequestHostTests = []struct { 566 in, out string 567 }{ 568 {"http://www.example.com/", "www.example.com"}, 569 {"http://www.example.com:8080/", "www.example.com:8080"}, 570 571 {"http://192.168.0.1/", "192.168.0.1"}, 572 {"http://192.168.0.1:8080/", "192.168.0.1:8080"}, 573 {"http://192.168.0.1:/", "192.168.0.1"}, 574 575 {"http://[fe80::1]/", "[fe80::1]"}, 576 {"http://[fe80::1]:8080/", "[fe80::1]:8080"}, 577 {"http://[fe80::1%25en0]/", "[fe80::1%en0]"}, 578 {"http://[fe80::1%25en0]:8080/", "[fe80::1%en0]:8080"}, 579 {"http://[fe80::1%25en0]:/", "[fe80::1%en0]"}, 580 } 581 582 func TestNewRequestHost(t *testing.T) { 583 for i, tt := range newRequestHostTests { 584 req, err := NewRequest("GET", tt.in, nil) 585 if err != nil { 586 t.Errorf("#%v: %v", i, err) 587 continue 588 } 589 if req.Host != tt.out { 590 t.Errorf("got %q; want %q", req.Host, tt.out) 591 } 592 } 593 } 594 595 func TestRequestInvalidMethod(t *testing.T) { 596 //goland:noinspection HttpUrlsUsage 597 _, err := NewRequest("bad method", "http://foo.com/", nil) 598 if err == nil { 599 t.Error("expected error from NewRequest with invalid method") 600 } 601 req, err := NewRequest("GET", "http://foo.example/", nil) 602 if err != nil { 603 t.Fatal(err) 604 } 605 req.Method = "bad method" 606 _, err = DefaultClient.Do(req) 607 if err == nil || !strings.Contains(err.Error(), "invalid method") { 608 t.Errorf("Transport error = %v; want invalid method", err) 609 } 610 611 //goland:noinspection HttpUrlsUsage 612 req, err = NewRequest("", "http://foo.com/", nil) 613 if err != nil { 614 t.Errorf("NewRequest(empty method) = %v; want nil", err) 615 } else if req.Method != "GET" { 616 t.Errorf("NewRequest(empty method) has method %q; want GET", req.Method) 617 } 618 } 619 620 func TestNewRequestContentLength(t *testing.T) { 621 readByte := func(r io.Reader) io.Reader { 622 var b [1]byte 623 _, _ = r.Read(b[:]) 624 return r 625 } 626 tests := []struct { 627 r io.Reader 628 want int64 629 }{ 630 {bytes.NewReader([]byte("123")), 3}, 631 {bytes.NewBuffer([]byte("1234")), 4}, 632 {strings.NewReader("12345"), 5}, 633 {strings.NewReader(""), 0}, 634 {NoBody, 0}, 635 636 // Not detected. During Go 1.8 we tried to make these set to -1, but 637 // due to Issue 18117, we keep these returning 0, even though they're 638 // unknown. 639 {struct{ io.Reader }{strings.NewReader("xyz")}, 0}, 640 {io.NewSectionReader(strings.NewReader("x"), 0, 6), 0}, 641 {readByte(io.NewSectionReader(strings.NewReader("xy"), 0, 6)), 0}, 642 } 643 for i, tt := range tests { 644 req, err := NewRequest("POST", "http://localhost/", tt.r) 645 if err != nil { 646 t.Fatal(err) 647 } 648 if req.ContentLength != tt.want { 649 t.Errorf("test[%d]: ContentLength(%T) = %d; want %d", i, tt.r, req.ContentLength, tt.want) 650 } 651 } 652 } 653 654 var parseHTTPVersionTests = []struct { 655 vers string 656 major, minor int 657 ok bool 658 }{ 659 {"HTTP/0.9", 0, 9, true}, 660 {"HTTP/1.0", 1, 0, true}, 661 {"HTTP/1.1", 1, 1, true}, 662 {"HTTP/3.14", 3, 14, true}, 663 664 {"HTTP", 0, 0, false}, 665 {"HTTP/one.one", 0, 0, false}, 666 {"HTTP/1.1/", 0, 0, false}, 667 {"HTTP/-1,0", 0, 0, false}, 668 {"HTTP/0,-1", 0, 0, false}, 669 {"HTTP/", 0, 0, false}, 670 {"HTTP/1,1", 0, 0, false}, 671 } 672 673 func TestParseHTTPVersion(t *testing.T) { 674 for _, tt := range parseHTTPVersionTests { 675 major, minor, ok := ParseHTTPVersion(tt.vers) 676 if ok != tt.ok || major != tt.major || minor != tt.minor { 677 type version struct { 678 major, minor int 679 ok bool 680 } 681 t.Errorf("failed to parse %q, expected: %#v, got %#v", tt.vers, version{tt.major, tt.minor, tt.ok}, version{major, minor, ok}) 682 } 683 } 684 } 685 686 type getBasicAuthTest struct { 687 username, password string 688 ok bool 689 } 690 691 type basicAuthCredentialsTest struct { 692 username, password string 693 } 694 695 var getBasicAuthTests = []struct { 696 username, password string 697 ok bool 698 }{ 699 {"Aladdin", "open sesame", true}, 700 {"Aladdin", "open:sesame", true}, 701 {"", "", true}, 702 } 703 704 func TestGetBasicAuth(t *testing.T) { 705 //goland:noinspection HttpUrlsUsage 706 for _, tt := range getBasicAuthTests { 707 r, _ := NewRequest("GET", "http://example.com/", nil) 708 r.SetBasicAuth(tt.username, tt.password) 709 username, password, ok := r.BasicAuth() 710 if ok != tt.ok || username != tt.username || password != tt.password { 711 t.Errorf("BasicAuth() = %#v, want %#v", getBasicAuthTest{username, password, ok}, 712 getBasicAuthTest{tt.username, tt.password, tt.ok}) 713 } 714 } 715 // Unauthenticated request. 716 //goland:noinspection HttpUrlsUsage 717 r, _ := NewRequest("GET", "http://example.com/", nil) 718 username, password, ok := r.BasicAuth() 719 if ok { 720 t.Errorf("expected false from BasicAuth when the request is unauthenticated") 721 } 722 want := basicAuthCredentialsTest{"", ""} 723 if username != want.username || password != want.password { 724 t.Errorf("expected credentials: %#v when the request is unauthenticated, got %#v", 725 want, basicAuthCredentialsTest{username, password}) 726 } 727 } 728 729 var parseBasicAuthTests = []struct { 730 header, username, password string 731 ok bool 732 }{ 733 {"Basic " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")), "Aladdin", "open sesame", true}, 734 735 // Case doesn't matter: 736 {"BASIC " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")), "Aladdin", "open sesame", true}, 737 {"basic " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")), "Aladdin", "open sesame", true}, 738 739 {"Basic " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open:sesame")), "Aladdin", "open:sesame", true}, 740 {"Basic " + base64.StdEncoding.EncodeToString([]byte(":")), "", "", true}, 741 {"Basic" + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")), "", "", false}, 742 {base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")), "", "", false}, 743 {"Basic ", "", "", false}, 744 {"Basic Aladdin:open sesame", "", "", false}, 745 {`Digest username="Aladdin"`, "", "", false}, 746 } 747 748 func TestParseBasicAuth(t *testing.T) { 749 //goland:noinspection HttpUrlsUsage 750 for _, tt := range parseBasicAuthTests { 751 r, _ := NewRequest("GET", "http://example.com/", nil) 752 r.Header.Set("Authorization", tt.header) 753 username, password, ok := r.BasicAuth() 754 if ok != tt.ok || username != tt.username || password != tt.password { 755 t.Errorf("BasicAuth() = %#v, want %#v", getBasicAuthTest{username, password, ok}, 756 getBasicAuthTest{tt.username, tt.password, tt.ok}) 757 } 758 } 759 } 760 761 type logWrites struct { 762 t *testing.T 763 dst *[]string 764 } 765 766 //goland:noinspection GoUnusedParameter 767 func (l logWrites) WriteByte(c byte) error { 768 l.t.Fatalf("unexpected WriteByte call") 769 return nil 770 } 771 772 func (l logWrites) Write(p []byte) (n int, err error) { 773 *l.dst = append(*l.dst, string(p)) 774 return len(p), nil 775 } 776 777 func TestRequestWriteBufferedWriter(t *testing.T) { 778 var got []string 779 //goland:noinspection HttpUrlsUsage 780 req, _ := NewRequest("GET", "http://foo.com/", nil) 781 _ = req.Write(logWrites{t, &got}) 782 want := []string{ 783 "GET / HTTP/1.1\r\n", 784 "Host: foo.com\r\n", 785 "User-Agent: " + DefaultUserAgent + "\r\n", 786 "\r\n", 787 } 788 if !reflect.DeepEqual(got, want) { 789 t.Errorf("Writes = %q\n Want = %q", got, want) 790 } 791 } 792 793 func TestRequestBadHost(t *testing.T) { 794 var got []string 795 //goland:noinspection HttpUrlsUsage 796 req, err := NewRequest("GET", "http://foo/after", nil) 797 if err != nil { 798 t.Fatal(err) 799 } 800 req.Host = "foo.com with spaces" 801 req.URL.Host = "foo.com with spaces" 802 _ = req.Write(logWrites{t, &got}) 803 want := []string{ 804 "GET /after HTTP/1.1\r\n", 805 "Host: foo.com\r\n", 806 "User-Agent: " + DefaultUserAgent + "\r\n", 807 "\r\n", 808 } 809 if !reflect.DeepEqual(got, want) { 810 t.Errorf("Writes = %q\n Want = %q", got, want) 811 } 812 } 813 814 func TestStarRequest(t *testing.T) { 815 req, err := ReadRequest(bufio.NewReader(strings.NewReader("M-SEARCH * HTTP/1.1\r\n\r\n"))) 816 if err != nil { 817 return 818 } 819 if req.ContentLength != 0 { 820 t.Errorf("ContentLength = %d; want 0", req.ContentLength) 821 } 822 if req.Body == nil { 823 t.Errorf("Body = nil; want non-nil") 824 } 825 826 // Request.Write has Client semantics for Body/ContentLength, 827 // where ContentLength 0 means unknown if Body is non-nil, and 828 // thus chunking will happen unless we change semantics and 829 // signal that we want to serialize it as exactly zero. The 830 // only way to do that for outbound requests is with a nil 831 // Body: 832 clientReq := *req 833 clientReq.Body = nil 834 835 var out bytes.Buffer 836 if err := clientReq.Write(&out); err != nil { 837 t.Fatal(err) 838 } 839 840 if strings.Contains(out.String(), "chunked") { 841 t.Error("wrote chunked request; want no body") 842 } 843 back, err := ReadRequest(bufio.NewReader(bytes.NewReader(out.Bytes()))) 844 if err != nil { 845 t.Fatal(err) 846 } 847 // Ignore the Headers (the User-Agent breaks the deep equal, 848 // but we don't care about it) 849 req.Header = nil 850 back.Header = nil 851 if !reflect.DeepEqual(req, back) { 852 t.Errorf("Original request doesn't match Request read back.") 853 t.Logf("Original: %#v", req) 854 t.Logf("Original.URL: %#v", req.URL) 855 t.Logf("Wrote: %s", out.Bytes()) 856 t.Logf("Read back (doesn't match Original): %#v", back) 857 } 858 } 859 860 type responseWriterJustWriter struct { 861 io.Writer 862 } 863 864 func (responseWriterJustWriter) Header() Header { panic("should not be called") } 865 func (responseWriterJustWriter) WriteHeader(int) { panic("should not be called") } 866 867 // delayedEOFReader never returns (n > 0, io.EOF), instead putting 868 // off the io.EOF until a subsequent Read call. 869 type delayedEOFReader struct { 870 r io.Reader 871 } 872 873 func (dr delayedEOFReader) Read(p []byte) (n int, err error) { 874 n, err = dr.r.Read(p) 875 if n > 0 && err == io.EOF { 876 err = nil 877 } 878 return 879 } 880 881 func TestIssue10884_MaxBytesEOF(t *testing.T) { 882 dst := io.Discard 883 _, err := io.Copy(dst, MaxBytesReader( 884 responseWriterJustWriter{dst}, 885 io.NopCloser(delayedEOFReader{strings.NewReader("12345")}), 886 5)) 887 if err != nil { 888 t.Fatal(err) 889 } 890 } 891 892 // Issue 14981: MaxBytesReader's return error wasn't sticky. It 893 // doesn't technically need to be, but people expected it to be. 894 func TestMaxBytesReaderStickyError(t *testing.T) { 895 isSticky := func(r io.Reader) error { 896 var log bytes.Buffer 897 buf := make([]byte, 1000) 898 var firstErr error 899 for { 900 n, err := r.Read(buf) 901 _, _ = fmt.Fprintf(&log, "Read(%d) = %d, %v\n", len(buf), n, err) 902 if err == nil { 903 continue 904 } 905 if firstErr == nil { 906 firstErr = err 907 continue 908 } 909 if !reflect.DeepEqual(err, firstErr) { 910 return fmt.Errorf("non-sticky error. got log:\n%s", log.Bytes()) 911 } 912 t.Logf("Got log: %s", log.Bytes()) 913 return nil 914 } 915 } 916 tests := [...]struct { 917 readable int 918 limit int64 919 }{ 920 0: {99, 100}, 921 1: {100, 100}, 922 2: {101, 100}, 923 } 924 for i, tt := range tests { 925 rc := MaxBytesReader(nil, io.NopCloser(bytes.NewReader(make([]byte, tt.readable))), tt.limit) 926 if err := isSticky(rc); err != nil { 927 t.Errorf("%d. error: %v", i, err) 928 } 929 } 930 } 931 932 // Issue 45101: maxBytesReader's Read panicked when n < -1. This test 933 // also ensures that Read treats negative limits as equivalent to 0. 934 func TestMaxBytesReaderDifferentLimits(t *testing.T) { 935 const testStr = "1234" 936 tests := [...]struct { 937 limit int64 938 lenP int 939 wantN int 940 wantErr bool 941 }{ 942 0: { 943 limit: -123, 944 lenP: 0, 945 wantN: 0, 946 wantErr: false, // Ensure we won't return an error when the limit is negative, but we don't need to read. 947 }, 948 1: { 949 limit: -100, 950 lenP: 32 * 1024, 951 wantN: 0, 952 wantErr: true, 953 }, 954 2: { 955 limit: -2, 956 lenP: 1, 957 wantN: 0, 958 wantErr: true, 959 }, 960 3: { 961 limit: -1, 962 lenP: 2, 963 wantN: 0, 964 wantErr: true, 965 }, 966 4: { 967 limit: 0, 968 lenP: 3, 969 wantN: 0, 970 wantErr: true, 971 }, 972 5: { 973 limit: 1, 974 lenP: 4, 975 wantN: 1, 976 wantErr: true, 977 }, 978 6: { 979 limit: 2, 980 lenP: 5, 981 wantN: 2, 982 wantErr: true, 983 }, 984 7: { 985 limit: 3, 986 lenP: 2, 987 wantN: 2, 988 wantErr: false, 989 }, 990 8: { 991 limit: int64(len(testStr)), 992 lenP: len(testStr), 993 wantN: len(testStr), 994 wantErr: false, 995 }, 996 9: { 997 limit: 100, 998 lenP: 6, 999 wantN: len(testStr), 1000 wantErr: false, 1001 }, 1002 } 1003 for i, tt := range tests { 1004 rc := MaxBytesReader(nil, io.NopCloser(strings.NewReader(testStr)), tt.limit) 1005 1006 n, err := rc.Read(make([]byte, tt.lenP)) 1007 1008 if n != tt.wantN { 1009 t.Errorf("%d. n: %d, want n: %d", i, n, tt.wantN) 1010 } 1011 1012 if (err != nil) != tt.wantErr { 1013 t.Errorf("%d. error: %v", i, err) 1014 } 1015 } 1016 } 1017 1018 func TestWithContextDeepCopiesURL(t *testing.T) { 1019 req, err := NewRequest("POST", "https://golang.org/", nil) 1020 if err != nil { 1021 t.Fatal(err) 1022 } 1023 1024 reqCopy := req.WithContext(context.Background()) 1025 reqCopy.URL.Scheme = "http" 1026 1027 firstURL, secondURL := req.URL.String(), reqCopy.URL.String() 1028 if firstURL == secondURL { 1029 t.Errorf("unexpected change to original request's URL") 1030 } 1031 1032 // And also check we don't crash on nil (Issue 20601) 1033 req.URL = nil 1034 reqCopy = req.WithContext(context.Background()) 1035 if reqCopy.URL != nil { 1036 t.Error("expected nil URL in cloned request") 1037 } 1038 } 1039 1040 // Ensure that Request.Clone creates a deep copy of TransferEncoding. 1041 // See issue 41907. 1042 func TestRequestCloneTransferEncoding(t *testing.T) { 1043 body := strings.NewReader("body") 1044 req, _ := NewRequest("POST", "https://example.org/", body) 1045 req.TransferEncoding = []string{ 1046 "encoding1", 1047 } 1048 1049 clonedReq := req.Clone(context.Background()) 1050 // modify original after deep copy 1051 req.TransferEncoding[0] = "encoding2" 1052 1053 if req.TransferEncoding[0] != "encoding2" { 1054 t.Error("expected req.TransferEncoding to be changed") 1055 } 1056 if clonedReq.TransferEncoding[0] != "encoding1" { 1057 t.Error("expected clonedReq.TransferEncoding to be unchanged") 1058 } 1059 } 1060 1061 func TestNoPanicOnRoundTripWithBasicAuth_h1(t *testing.T) { 1062 testNoPanicWithBasicAuth(t, h1Mode) 1063 } 1064 1065 func TestNoPanicOnRoundTripWithBasicAuth_h2(t *testing.T) { 1066 testNoPanicWithBasicAuth(t, h2Mode) 1067 } 1068 1069 // Issue 34878: verify we don't panic when including basic auth (Go 1.13 regression) 1070 func testNoPanicWithBasicAuth(t *testing.T, h2 bool) { 1071 defer afterTest(t) 1072 cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {})) 1073 defer cst.close() 1074 1075 u, err := url.Parse(cst.ts.URL) 1076 if err != nil { 1077 t.Fatal(err) 1078 } 1079 u.User = url.UserPassword("foo", "bar") 1080 req := &Request{ 1081 URL: u, 1082 Method: "GET", 1083 } 1084 if _, err := cst.c.Do(req); err != nil { 1085 t.Fatalf("Unexpected error: %v", err) 1086 } 1087 } 1088 1089 // verify that NewRequest sets Request.GetBody and that it works 1090 func TestNewRequestGetBody(t *testing.T) { 1091 tests := []struct { 1092 r io.Reader 1093 }{ 1094 {r: strings.NewReader("hello")}, 1095 {r: bytes.NewReader([]byte("hello"))}, 1096 {r: bytes.NewBuffer([]byte("hello"))}, 1097 } 1098 for i, tt := range tests { 1099 req, err := NewRequest("POST", "http://foo.tld/", tt.r) 1100 if err != nil { 1101 t.Errorf("test[%d]: %v", i, err) 1102 continue 1103 } 1104 if req.Body == nil { 1105 t.Errorf("test[%d]: Body = nil", i) 1106 continue 1107 } 1108 if req.GetBody == nil { 1109 t.Errorf("test[%d]: GetBody = nil", i) 1110 continue 1111 } 1112 slurp1, err := io.ReadAll(req.Body) 1113 if err != nil { 1114 t.Errorf("test[%d]: ReadAll(Body) = %v", i, err) 1115 } 1116 newBody, err := req.GetBody() 1117 if err != nil { 1118 t.Errorf("test[%d]: GetBody = %v", i, err) 1119 } 1120 slurp2, err := io.ReadAll(newBody) 1121 if err != nil { 1122 t.Errorf("test[%d]: ReadAll(GetBody()) = %v", i, err) 1123 } 1124 if string(slurp1) != string(slurp2) { 1125 t.Errorf("test[%d]: Body %q != GetBody %q", i, slurp1, slurp2) 1126 } 1127 } 1128 } 1129 1130 func testMissingFile(t *testing.T, req *Request) { 1131 f, fh, err := req.FormFile("missing") 1132 if f != nil { 1133 t.Errorf("FormFile file = %v, want nil", f) 1134 } 1135 if fh != nil { 1136 t.Errorf("FormFile file header = %q, want nil", fh) 1137 } 1138 if err != ErrMissingFile { 1139 t.Errorf("FormFile err = %q, want ErrMissingFile", err) 1140 } 1141 } 1142 1143 func newTestMultipartRequest(t *testing.T) *Request { 1144 b := strings.NewReader(strings.ReplaceAll(message, "\n", "\r\n")) 1145 req, err := NewRequest("POST", "/", b) 1146 if err != nil { 1147 t.Fatal("NewRequest:", err) 1148 } 1149 ctype := fmt.Sprintf(`multipart/form-data; boundary="%s"`, boundary) 1150 req.Header.Set("Content-type", ctype) 1151 return req 1152 } 1153 1154 func validateTestMultipartContents(t *testing.T, req *Request, allMem bool) { 1155 if g, e := req.FormValue("texta"), textaValue; g != e { 1156 t.Errorf("texta value = %q, want %q", g, e) 1157 } 1158 if g, e := req.FormValue("textb"), textbValue; g != e { 1159 t.Errorf("textb value = %q, want %q", g, e) 1160 } 1161 if g := req.FormValue("missing"); g != "" { 1162 t.Errorf("missing value = %q, want empty string", g) 1163 } 1164 1165 assertMem := func(n string, fd multipart.File) { 1166 if _, ok := fd.(*os.File); ok { 1167 t.Error(n, " is *os.File, should not be") 1168 } 1169 } 1170 fda := testMultipartFile(t, req, "filea", "filea.txt", fileaContents) 1171 defer func(fda multipart.File) { 1172 _ = fda.Close() 1173 }(fda) 1174 assertMem("filea", fda) 1175 fdb := testMultipartFile(t, req, "fileb", "fileb.txt", filebContents) 1176 defer func(fdb multipart.File) { 1177 _ = fdb.Close() 1178 }(fdb) 1179 if allMem { 1180 assertMem("fileb", fdb) 1181 } else { 1182 if _, ok := fdb.(*os.File); !ok { 1183 t.Errorf("fileb has unexpected underlying type %T", fdb) 1184 } 1185 } 1186 1187 testMissingFile(t, req) 1188 } 1189 1190 func testMultipartFile(t *testing.T, req *Request, key, expectFilename, expectContent string) multipart.File { 1191 f, fh, err := req.FormFile(key) 1192 if err != nil { 1193 t.Fatalf("FormFile(%q): %q", key, err) 1194 } 1195 if fh.Filename != expectFilename { 1196 t.Errorf("filename = %q, want %q", fh.Filename, expectFilename) 1197 } 1198 var b bytes.Buffer 1199 _, err = io.Copy(&b, f) 1200 if err != nil { 1201 t.Fatal("copying contents:", err) 1202 } 1203 if g := b.String(); g != expectContent { 1204 t.Errorf("contents = %q, want %q", g, expectContent) 1205 } 1206 return f 1207 } 1208 1209 const ( 1210 fileaContents = "This is a test file." 1211 filebContents = "Another test file." 1212 textaValue = "foo" 1213 textbValue = "bar" 1214 boundary = `MyBoundary` 1215 ) 1216 1217 const message = ` 1218 --MyBoundary 1219 Content-Disposition: form-data; name="filea"; filename="filea.txt" 1220 Content-Type: text/plain 1221 1222 ` + fileaContents + ` 1223 --MyBoundary 1224 Content-Disposition: form-data; name="fileb"; filename="fileb.txt" 1225 Content-Type: text/plain 1226 1227 ` + filebContents + ` 1228 --MyBoundary 1229 Content-Disposition: form-data; name="texta" 1230 1231 ` + textaValue + ` 1232 --MyBoundary 1233 Content-Disposition: form-data; name="textb" 1234 1235 ` + textbValue + ` 1236 --MyBoundary-- 1237 ` 1238 1239 func benchmarkReadRequest(b *testing.B, request string) { 1240 request = request + "\n" // final \n 1241 request = strings.ReplaceAll(request, "\n", "\r\n") // expand \n to \r\n 1242 b.SetBytes(int64(len(request))) 1243 r := bufio.NewReader(&infiniteReader{buf: []byte(request)}) 1244 b.ReportAllocs() 1245 b.ResetTimer() 1246 for i := 0; i < b.N; i++ { 1247 _, err := ReadRequest(r) 1248 if err != nil { 1249 b.Fatalf("failed to read request: %v", err) 1250 } 1251 } 1252 } 1253 1254 // infiniteReader satisfies Read requests as if the contents of buf 1255 // loop indefinitely. 1256 type infiniteReader struct { 1257 buf []byte 1258 offset int 1259 } 1260 1261 func (r *infiniteReader) Read(b []byte) (int, error) { 1262 n := copy(b, r.buf[r.offset:]) 1263 r.offset = (r.offset + n) % len(r.buf) 1264 return n, nil 1265 } 1266 1267 func BenchmarkReadRequestChrome(b *testing.B) { 1268 // https://github.com/felixge/node-http-perf/blob/master/fixtures/get.http 1269 benchmarkReadRequest(b, `GET / HTTP/1.1 1270 Host: localhost:8080 1271 Connection: keep-alive 1272 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 1273 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17 1274 Accept-Encoding: gzip,deflate,sdch 1275 Accept-Language: en-US,en;q=0.8 1276 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 1277 Cookie: __utma=1.1978842379.1323102373.1323102373.1323102373.1; EPi:NumberOfVisits=1,2012-02-28T13:42:18; CrmSession=5b707226b9563e1bc69084d07a107c98; plushContainerWidth=100%25; plushNoTopMenu=0; hudson_auto_refresh=false 1278 `) 1279 } 1280 1281 func BenchmarkReadRequestCurl(b *testing.B) { 1282 // curl http://localhost:8080/ 1283 benchmarkReadRequest(b, `GET / HTTP/1.1 1284 User-Agent: curl/7.27.0 1285 Host: localhost:8080 1286 Accept: */* 1287 `) 1288 } 1289 1290 func BenchmarkReadRequestApachebench(b *testing.B) { 1291 // ab -n 1 -c 1 http://localhost:8080/ 1292 benchmarkReadRequest(b, `GET / HTTP/1.0 1293 Host: localhost:8080 1294 User-Agent: ApacheBench/2.3 1295 Accept: */* 1296 `) 1297 } 1298 1299 func BenchmarkReadRequestSiege(b *testing.B) { 1300 // siege -r 1 -c 1 http://localhost:8080/ 1301 benchmarkReadRequest(b, `GET / HTTP/1.1 1302 Host: localhost:8080 1303 Accept: */* 1304 Accept-Encoding: gzip 1305 User-Agent: JoeDog/1.00 [en] (X11; I; Siege 2.70) 1306 Connection: keep-alive 1307 `) 1308 } 1309 1310 func BenchmarkReadRequestWrk(b *testing.B) { 1311 // wrk -t 1 -r 1 -c 1 http://localhost:8080/ 1312 benchmarkReadRequest(b, `GET / HTTP/1.1 1313 Host: localhost:8080 1314 `) 1315 } 1316 1317 const ( 1318 withTLS = true 1319 noTLS = false 1320 ) 1321 1322 func BenchmarkFileAndServer_1KB(b *testing.B) { 1323 benchmarkFileAndServer(b, 1<<10) 1324 } 1325 1326 func BenchmarkFileAndServer_16MB(b *testing.B) { 1327 benchmarkFileAndServer(b, 1<<24) 1328 } 1329 1330 func BenchmarkFileAndServer_64MB(b *testing.B) { 1331 benchmarkFileAndServer(b, 1<<26) 1332 } 1333 1334 func benchmarkFileAndServer(b *testing.B, n int64) { 1335 f, err := os.CreateTemp(os.TempDir(), "go-bench-http-file-and-server") 1336 if err != nil { 1337 b.Fatalf("Failed to create temp file: %v", err) 1338 } 1339 1340 defer func() { 1341 _ = f.Close() 1342 _ = os.RemoveAll(f.Name()) 1343 }() 1344 1345 if _, err := io.CopyN(f, rand.Reader, n); err != nil { 1346 b.Fatalf("Failed to copy %d bytes: %v", n, err) 1347 } 1348 1349 b.Run("NoTLS", func(b *testing.B) { 1350 runFileAndServerBenchmarks(b, noTLS, f, n) 1351 }) 1352 1353 b.Run("TLS", func(b *testing.B) { 1354 runFileAndServerBenchmarks(b, withTLS, f, n) 1355 }) 1356 } 1357 1358 func runFileAndServerBenchmarks(b *testing.B, tlsOption bool, f *os.File, n int64) { 1359 handler := HandlerFunc(func(rw ResponseWriter, req *Request) { 1360 defer func(Body io.ReadCloser) { 1361 _ = Body.Close() 1362 }(req.Body) 1363 nc, err := io.Copy(io.Discard, req.Body) 1364 if err != nil { 1365 panic(err) 1366 } 1367 1368 if nc != n { 1369 //goland:noinspection GoErrorStringFormat 1370 panic(fmt.Errorf("Copied %d Wanted %d bytes", nc, n)) 1371 } 1372 }) 1373 1374 var cst *httptest.Server 1375 if tlsOption == withTLS { 1376 cst = httptest.NewTLSServer(handler) 1377 } else { 1378 cst = httptest.NewServer(handler) 1379 } 1380 1381 defer cst.Close() 1382 b.ResetTimer() 1383 for i := 0; i < b.N; i++ { 1384 // Perform some setup. 1385 b.StopTimer() 1386 if _, err := f.Seek(0, 0); err != nil { 1387 b.Fatalf("Failed to seek back to file: %v", err) 1388 } 1389 1390 b.StartTimer() 1391 req, err := NewRequest("PUT", cst.URL, io.NopCloser(f)) 1392 if err != nil { 1393 b.Fatal(err) 1394 } 1395 1396 req.ContentLength = n 1397 // Prevent mime sniffing by setting the Content-Type. 1398 req.Header.Set("Content-Type", "application/octet-stream") 1399 res, err := cst.Client().Do(req) 1400 if err != nil { 1401 b.Fatalf("Failed to make request to backend: %v", err) 1402 } 1403 1404 _ = res.Body.Close() 1405 b.SetBytes(n) 1406 } 1407 }