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