package fasthttp import ( "bufio" "bytes" "errors" "fmt" "io" "mime/multipart" "net/http" "net/http/httptest" "reflect" "strconv" "strings" "testing" "time" "github.com/valyala/bytebufferpool" ) func TestInvalidTrailers(t *testing.T) { t.Parallel() if err := (&Response{}).Read(bufio.NewReader(strings.NewReader(" 0\nTransfer-Encoding:\xff\n\n0\r\n0"))); !errors.Is(err, io.EOF) { t.Fatalf("%#v", err) } if err := (&Response{}).Read(bufio.NewReader(strings.NewReader("\xff \nTRaILeR:,\n\n"))); !errors.Is(err, errEmptyInt) { t.Fatal(err) } if err := (&Response{}).Read(bufio.NewReader(strings.NewReader("TRaILeR:,\n\n"))); !strings.Contains(err.Error(), "cannot find whitespace in the first line of response") { t.Fatal(err) } } func TestResponseEmptyTransferEncoding(t *testing.T) { t.Parallel() var r Response body := "Some body" br := bufio.NewReader(bytes.NewBufferString("HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nTransfer-Encoding: \r\nContent-Length: 9\r\n\r\n" + body)) err := r.Read(br) if err != nil { t.Fatal(err) } if got := string(r.Body()); got != body { t.Fatalf("expected %q got %q", body, got) } } // Don't send the fragment/hash/# part of a URL to the server. func TestFragmentInURIRequest(t *testing.T) { t.Parallel() var req Request req.SetRequestURI("https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#events") var b bytes.Buffer req.WriteTo(&b) //nolint:errcheck got := b.String() expected := "GET /ee/user/project/integrations/webhooks.html HTTP/1.1\r\nHost: docs.gitlab.com\r\n\r\n" if got != expected { t.Errorf("got %q expected %q", got, expected) } } func TestIssue875(t *testing.T) { t.Parallel() type testcase struct { uri string expectedRedirect string expectedLocation string } testcases := []testcase{ { uri: `http://localhost:3000/?redirect=foo%0d%0aSet-Cookie:%20SESSIONID=MaliciousValue%0d%0a`, expectedRedirect: "foo\r\nSet-Cookie: SESSIONID=MaliciousValue\r\n", expectedLocation: "Location: foo Set-Cookie: SESSIONID=MaliciousValue", }, { uri: `http://localhost:3000/?redirect=foo%0dSet-Cookie:%20SESSIONID=MaliciousValue%0d%0a`, expectedRedirect: "foo\rSet-Cookie: SESSIONID=MaliciousValue\r\n", expectedLocation: "Location: foo Set-Cookie: SESSIONID=MaliciousValue", }, { uri: `http://localhost:3000/?redirect=foo%0aSet-Cookie:%20SESSIONID=MaliciousValue%0d%0a`, expectedRedirect: "foo\nSet-Cookie: SESSIONID=MaliciousValue\r\n", expectedLocation: "Location: foo Set-Cookie: SESSIONID=MaliciousValue", }, } for i, tcase := range testcases { caseName := strconv.FormatInt(int64(i), 10) t.Run(caseName, func(subT *testing.T) { ctx := &RequestCtx{ Request: Request{}, Response: Response{}, } ctx.Request.SetRequestURI(tcase.uri) q := string(ctx.QueryArgs().Peek("redirect")) if q != tcase.expectedRedirect { subT.Errorf("unexpected redirect query value, got: %+v", q) } ctx.Response.Header.Set("Location", q) if !strings.Contains(ctx.Response.String(), tcase.expectedLocation) { subT.Errorf("invalid escaping, got\n%q", ctx.Response.String()) } }) } } func TestRequestCopyTo(t *testing.T) { t.Parallel() var req Request // empty copy testRequestCopyTo(t, &req) // init expectedContentType := "application/x-www-form-urlencoded; charset=UTF-8" expectedHost := "test.com" expectedBody := "0123=56789" s := fmt.Sprintf("POST / HTTP/1.1\r\nHost: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n%s", expectedHost, expectedContentType, len(expectedBody), expectedBody) br := bufio.NewReader(bytes.NewBufferString(s)) if err := req.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } testRequestCopyTo(t, &req) } func TestResponseCopyTo(t *testing.T) { t.Parallel() var resp Response // empty copy testResponseCopyTo(t, &resp) // init resp resp.laddr = zeroTCPAddr resp.SkipBody = true resp.Header.SetStatusCode(200) resp.SetBodyString("test") testResponseCopyTo(t, &resp) } func testRequestCopyTo(t *testing.T, src *Request) { var dst Request src.CopyTo(&dst) if !reflect.DeepEqual(src, &dst) { t.Fatalf("RequestCopyTo fail, src: \n%+v\ndst: \n%+v\n", src, &dst) } } func testResponseCopyTo(t *testing.T, src *Response) { var dst Response src.CopyTo(&dst) if !reflect.DeepEqual(src, &dst) { t.Fatalf("ResponseCopyTo fail, src: \n%+v\ndst: \n%+v\n", src, &dst) } } func TestRequestBodyStreamWithTrailer(t *testing.T) { t.Parallel() testRequestBodyStreamWithTrailer(t, nil, false) body := createFixedBody(1e5) testRequestBodyStreamWithTrailer(t, body, false) testRequestBodyStreamWithTrailer(t, body, true) } func testRequestBodyStreamWithTrailer(t *testing.T, body []byte, disableNormalizing bool) { expectedTrailer := map[string]string{ "foo": "testfoo", "bar": "testbar", } var req1 Request req1.Header.disableNormalizing = disableNormalizing req1.SetHost("google.com") req1.SetBodyStream(bytes.NewBuffer(body), -1) for k, v := range expectedTrailer { err := req1.Header.AddTrailer(k) if err != nil { t.Fatalf("unexpected error: %v", err) } req1.Header.Set(k, v) } w := &bytes.Buffer{} bw := bufio.NewWriter(w) if err := req1.Write(bw); err != nil { t.Fatalf("unexpected error: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } var req2 Request req2.Header.disableNormalizing = disableNormalizing br := bufio.NewReader(w) if err := req2.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } reqBody := req2.Body() if !bytes.Equal(reqBody, body) { t.Fatalf("unexpected body: %q. Expecting %q", reqBody, body) } for k, v := range expectedTrailer { kBytes := []byte(k) normalizeHeaderKey(kBytes, disableNormalizing) r := req2.Header.Peek(k) if string(r) != v { t.Fatalf("unexpected trailer header %q: %q. Expecting %q", kBytes, r, v) } } } func TestResponseBodyStreamWithTrailer(t *testing.T) { t.Parallel() testResponseBodyStreamWithTrailer(t, nil, false) body := createFixedBody(1e5) testResponseBodyStreamWithTrailer(t, body, false) testResponseBodyStreamWithTrailer(t, body, true) } func testResponseBodyStreamWithTrailer(t *testing.T, body []byte, disableNormalizing bool) { expectedTrailer := map[string]string{ "foo": "testfoo", "bar": "testbar", } var resp1 Response resp1.Header.disableNormalizing = disableNormalizing resp1.SetBodyStream(bytes.NewReader(body), -1) for k, v := range expectedTrailer { err := resp1.Header.AddTrailer(k) if err != nil { t.Fatalf("unexpected error: %v", err) } resp1.Header.Set(k, v) } w := &bytes.Buffer{} bw := bufio.NewWriter(w) if err := resp1.Write(bw); err != nil { t.Fatalf("unexpected error: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } var resp2 Response resp2.Header.disableNormalizing = disableNormalizing br := bufio.NewReader(w) if err := resp2.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } respBody := resp2.Body() if !bytes.Equal(respBody, body) { t.Fatalf("unexpected body: %q. Expecting %q", respBody, body) } for k, v := range expectedTrailer { kBytes := []byte(k) normalizeHeaderKey(kBytes, disableNormalizing) r := resp2.Header.Peek(k) if string(r) != v { t.Fatalf("unexpected trailer header %q: %q. Expecting %q", kBytes, r, v) } } } func TestResponseBodyStreamDeflate(t *testing.T) { t.Parallel() body := createFixedBody(1e5) // Verifies https://github.com/valyala/fasthttp/issues/176 // when Content-Length is explicitly set. testResponseBodyStreamDeflate(t, body, len(body)) // Verifies that 'transfer-encoding: chunked' works as expected. testResponseBodyStreamDeflate(t, body, -1) } func TestResponseBodyStreamGzip(t *testing.T) { t.Parallel() body := createFixedBody(1e5) // Verifies https://github.com/valyala/fasthttp/issues/176 // when Content-Length is explicitly set. testResponseBodyStreamGzip(t, body, len(body)) // Verifies that 'transfer-encoding: chunked' works as expected. testResponseBodyStreamGzip(t, body, -1) } func testResponseBodyStreamDeflate(t *testing.T, body []byte, bodySize int) { var r Response r.SetBodyStream(bytes.NewReader(body), bodySize) w := &bytes.Buffer{} bw := bufio.NewWriter(w) if err := r.WriteDeflate(bw); err != nil { t.Fatalf("unexpected error: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } var resp Response br := bufio.NewReader(w) if err := resp.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } respBody, err := resp.BodyInflate() if err != nil { t.Fatalf("unexpected error: %v", err) } if !bytes.Equal(respBody, body) { t.Fatalf("unexpected body: %q. Expecting %q", respBody, body) } // check for invalid resp.SetBodyRaw([]byte("invalid")) _, errDeflate := resp.BodyInflate() if errDeflate == nil || errDeflate.Error() != "zlib: invalid header" { t.Fatalf("expected error: 'zlib: invalid header' but was %v", errDeflate) } } func testResponseBodyStreamGzip(t *testing.T, body []byte, bodySize int) { var r Response r.SetBodyStream(bytes.NewReader(body), bodySize) w := &bytes.Buffer{} bw := bufio.NewWriter(w) if err := r.WriteGzip(bw); err != nil { t.Fatalf("unexpected error: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } var resp Response br := bufio.NewReader(w) if err := resp.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } respBody, err := resp.BodyGunzip() if err != nil { t.Fatalf("unexpected error: %v", err) } if !bytes.Equal(respBody, body) { t.Fatalf("unexpected body: %q. Expecting %q", respBody, body) } // check for invalid resp.SetBodyRaw([]byte("invalid")) _, errUnzip := resp.BodyGunzip() if errUnzip == nil || errUnzip.Error() != "unexpected EOF" { t.Fatalf("expected error: 'unexpected EOF' but was %v", errUnzip) } } func TestResponseWriteGzipNilBody(t *testing.T) { t.Parallel() var r Response w := &bytes.Buffer{} bw := bufio.NewWriter(w) if err := r.WriteGzip(bw); err != nil { t.Fatalf("unexpected error: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } } func TestResponseWriteDeflateNilBody(t *testing.T) { t.Parallel() var r Response w := &bytes.Buffer{} bw := bufio.NewWriter(w) if err := r.WriteDeflate(bw); err != nil { t.Fatalf("unexpected error: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } } func TestResponseBodyUncompressed(t *testing.T) { body := "body" var r Response r.SetBodyStream(bytes.NewReader([]byte(body)), len(body)) w := &bytes.Buffer{} bw := bufio.NewWriter(w) if err := r.WriteDeflate(bw); err != nil { t.Fatalf("unexpected error: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } var resp Response br := bufio.NewReader(w) if err := resp.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } ce := resp.Header.ContentEncoding() if string(ce) != "deflate" { t.Fatalf("unexpected Content-Encoding: %s", ce) } respBody, err := resp.BodyUncompressed() if err != nil { t.Fatalf("unexpected error: %v", err) } if string(respBody) != body { t.Fatalf("unexpected body: %q. Expecting %q", respBody, body) } // check for invalid encoding resp.Header.SetContentEncoding("invalid") _, decodeErr := resp.BodyUncompressed() if decodeErr != ErrContentEncodingUnsupported { t.Fatalf("unexpected error: %v", decodeErr) } } func TestResponseSwapBodySerial(t *testing.T) { t.Parallel() testResponseSwapBody(t) } func TestResponseSwapBodyConcurrent(t *testing.T) { t.Parallel() ch := make(chan struct{}) for i := 0; i < 10; i++ { go func() { testResponseSwapBody(t) ch <- struct{}{} }() } for i := 0; i < 10; i++ { select { case <-ch: case <-time.After(time.Second): t.Fatalf("timeout") } } } func testResponseSwapBody(t *testing.T) { var b []byte r := AcquireResponse() for i := 0; i < 20; i++ { bOrig := r.Body() b = r.SwapBody(b) if !bytes.Equal(bOrig, b) { t.Fatalf("unexpected body returned: %q. Expecting %q", b, bOrig) } r.AppendBodyString("foobar") } s := "aaaabbbbcccc" b = b[:0] for i := 0; i < 10; i++ { r.SetBodyStream(bytes.NewBufferString(s), len(s)) b = r.SwapBody(b) if string(b) != s { t.Fatalf("unexpected body returned: %q. Expecting %q", b, s) } b = r.SwapBody(b) if len(b) > 0 { t.Fatalf("unexpected body with non-zero size returned: %q", b) } } ReleaseResponse(r) } func TestRequestSwapBodySerial(t *testing.T) { t.Parallel() testRequestSwapBody(t) } func TestRequestSwapBodyConcurrent(t *testing.T) { t.Parallel() ch := make(chan struct{}) for i := 0; i < 10; i++ { go func() { testRequestSwapBody(t) ch <- struct{}{} }() } for i := 0; i < 10; i++ { select { case <-ch: case <-time.After(time.Second): t.Fatalf("timeout") } } } func testRequestSwapBody(t *testing.T) { var b []byte r := AcquireRequest() for i := 0; i < 20; i++ { bOrig := r.Body() b = r.SwapBody(b) if !bytes.Equal(bOrig, b) { t.Fatalf("unexpected body returned: %q. Expecting %q", b, bOrig) } r.AppendBodyString("foobar") } s := "aaaabbbbcccc" b = b[:0] for i := 0; i < 10; i++ { r.SetBodyStream(bytes.NewBufferString(s), len(s)) b = r.SwapBody(b) if string(b) != s { t.Fatalf("unexpected body returned: %q. Expecting %q", b, s) } b = r.SwapBody(b) if len(b) > 0 { t.Fatalf("unexpected body with non-zero size returned: %q", b) } } ReleaseRequest(r) } func TestRequestHostFromRequestURI(t *testing.T) { t.Parallel() hExpected := "foobar.com" var req Request req.SetRequestURI("http://proxy-host:123/foobar?baz") req.SetHost(hExpected) h := req.Host() if string(h) != hExpected { t.Fatalf("unexpected host set: %q. Expecting %q", h, hExpected) } } func TestRequestHostFromHeader(t *testing.T) { t.Parallel() hExpected := "foobar.com" var req Request req.Header.SetHost(hExpected) h := req.Host() if string(h) != hExpected { t.Fatalf("unexpected host set: %q. Expecting %q", h, hExpected) } } func TestRequestContentTypeWithCharsetIssue100(t *testing.T) { t.Parallel() expectedContentType := "application/x-www-form-urlencoded; charset=UTF-8" expectedBody := "0123=56789" s := fmt.Sprintf("POST / HTTP/1.1\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n%s", expectedContentType, len(expectedBody), expectedBody) br := bufio.NewReader(bytes.NewBufferString(s)) var r Request if err := r.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } body := r.Body() if string(body) != expectedBody { t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody) } ct := r.Header.ContentType() if string(ct) != expectedContentType { t.Fatalf("unexpected content-type %q. Expecting %q", ct, expectedContentType) } args := r.PostArgs() if args.Len() != 1 { t.Fatalf("unexpected number of POST args: %d. Expecting 1", args.Len()) } av := args.Peek("0123") if string(av) != "56789" { t.Fatalf("unexpected POST arg value: %q. Expecting %q", av, "56789") } } func TestRequestReadMultipartFormWithFile(t *testing.T) { t.Parallel() s := `POST /upload HTTP/1.1 Host: localhost:10000 Content-Length: 521 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg ------WebKitFormBoundaryJwfATyF8tmxSJnLg Content-Disposition: form-data; name="f1" value1 ------WebKitFormBoundaryJwfATyF8tmxSJnLg Content-Disposition: form-data; name="fileaaa"; filename="TODO" Content-Type: application/octet-stream - SessionClient with referer and cookies support. - Client with requests' pipelining support. - ProxyHandler similar to FSHandler. - WebSockets. See https://tools.ietf.org/html/rfc6455 . - HTTP/2.0. See https://tools.ietf.org/html/rfc7540 . ------WebKitFormBoundaryJwfATyF8tmxSJnLg-- tailfoobar` br := bufio.NewReader(bytes.NewBufferString(s)) var r Request if err := r.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } tail, err := io.ReadAll(br) if err != nil { t.Fatalf("unexpected error: %v", err) } if string(tail) != "tailfoobar" { t.Fatalf("unexpected tail %q. Expecting %q", tail, "tailfoobar") } f, err := r.MultipartForm() if err != nil { t.Fatalf("unexpected error: %v", err) } defer r.RemoveMultipartFormFiles() // verify values if len(f.Value) != 1 { t.Fatalf("unexpected number of values in multipart form: %d. Expecting 1", len(f.Value)) } for k, vv := range f.Value { if k != "f1" { t.Fatalf("unexpected value name %q. Expecting %q", k, "f1") } if len(vv) != 1 { t.Fatalf("unexpected number of values %d. Expecting 1", len(vv)) } v := vv[0] if v != "value1" { t.Fatalf("unexpected value %q. Expecting %q", v, "value1") } } // verify files if len(f.File) != 1 { t.Fatalf("unexpected number of file values in multipart form: %d. Expecting 1", len(f.File)) } for k, vv := range f.File { if k != "fileaaa" { t.Fatalf("unexpected file value name %q. Expecting %q", k, "fileaaa") } if len(vv) != 1 { t.Fatalf("unexpected number of file values %d. Expecting 1", len(vv)) } v := vv[0] if v.Filename != "TODO" { t.Fatalf("unexpected filename %q. Expecting %q", v.Filename, "TODO") } ct := v.Header.Get("Content-Type") if ct != "application/octet-stream" { t.Fatalf("unexpected content-type %q. Expecting %q", ct, "application/octet-stream") } } } func TestRequestSetURI(t *testing.T) { t.Parallel() var r Request uri := "/foo/bar?baz" u := &URI{} u.Parse(nil, []byte(uri)) //nolint:errcheck // Set request uri via SetURI() r.SetURI(u) // copies URI // modifying an original URI struct doesn't affect stored URI inside of request u.SetPath("newPath") if string(r.RequestURI()) != uri { t.Fatalf("unexpected request uri %q. Expecting %q", r.RequestURI(), uri) } // Set request uri to nil just resets the URI r.Reset() uri = "/" r.SetURI(nil) if string(r.RequestURI()) != uri { t.Fatalf("unexpected request uri %q. Expecting %q", r.RequestURI(), uri) } } func TestRequestRequestURI(t *testing.T) { t.Parallel() var r Request // Set request uri via SetRequestURI() uri := "/foo/bar?baz" r.SetRequestURI(uri) if string(r.RequestURI()) != uri { t.Fatalf("unexpected request uri %q. Expecting %q", r.RequestURI(), uri) } // Set request uri via Request.URI().Update() r.Reset() uri = "/aa/bbb?ccc=sdfsdf" r.URI().Update(uri) if string(r.RequestURI()) != uri { t.Fatalf("unexpected request uri %q. Expecting %q", r.RequestURI(), uri) } // update query args in the request uri qa := r.URI().QueryArgs() qa.Reset() qa.Set("foo", "bar") uri = "/aa/bbb?foo=bar" if string(r.RequestURI()) != uri { t.Fatalf("unexpected request uri %q. Expecting %q", r.RequestURI(), uri) } } func TestRequestUpdateURI(t *testing.T) { t.Parallel() var r Request r.Header.SetHost("aaa.bbb") r.SetRequestURI("/lkjkl/kjl") // Modify request uri and host via URI() object and make sure // the requestURI and Host header are properly updated u := r.URI() u.SetPath("/123/432.html") u.SetHost("foobar.com") a := u.QueryArgs() a.Set("aaa", "bcse") s := r.String() if !strings.HasPrefix(s, "GET /123/432.html?aaa=bcse") { t.Fatalf("cannot find %q in %q", "GET /123/432.html?aaa=bcse", s) } if !strings.Contains(s, "\r\nHost: foobar.com\r\n") { t.Fatalf("cannot find %q in %q", "\r\nHost: foobar.com\r\n", s) } } func TestUseHostHeader(t *testing.T) { t.Parallel() var r Request r.UseHostHeader = true r.Header.SetHost("aaa.bbb") r.SetRequestURI("/lkjkl/kjl") // Modify request uri and host via URI() object and make sure // the requestURI and Host header are properly updated u := r.URI() u.SetPath("/123/432.html") u.SetHost("foobar.com") a := u.QueryArgs() a.Set("aaa", "bcse") s := r.String() if !strings.HasPrefix(s, "GET /123/432.html?aaa=bcse") { t.Fatalf("cannot find %q in %q", "GET /123/432.html?aaa=bcse", s) } if !strings.Contains(s, "\r\nHost: aaa.bbb\r\n") { t.Fatalf("cannot find %q in %q", "\r\nHost: aaa.bbb\r\n", s) } } func TestUseHostHeader2(t *testing.T) { t.Parallel() testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Host != "SomeHost" { http.Error(w, fmt.Sprintf("Expected Host header to be '%q', but got '%q'", "SomeHost", r.Host), http.StatusBadRequest) } else { w.WriteHeader(http.StatusOK) } })) defer testServer.Close() client := &Client{} req := AcquireRequest() defer ReleaseRequest(req) resp := AcquireResponse() defer ReleaseResponse(resp) req.SetRequestURI(testServer.URL) req.UseHostHeader = true req.Header.SetHost("SomeHost") if err := client.DoTimeout(req, resp, 1*time.Second); err != nil { t.Fatalf("DoTimeout returned an error '%v'", err) } if resp.StatusCode() != http.StatusOK { t.Fatalf("DoTimeout: %v", resp.body) } if err := client.Do(req, resp); err != nil { t.Fatalf("DoTimeout returned an error '%v'", err) } if resp.StatusCode() != http.StatusOK { t.Fatalf("Do: %q", resp.body) } } func TestUseHostHeaderAfterRelease(t *testing.T) { t.Parallel() req := AcquireRequest() req.UseHostHeader = true ReleaseRequest(req) req = AcquireRequest() defer ReleaseRequest(req) if req.UseHostHeader { t.Fatalf("UseHostHeader was not released in ReleaseRequest()") } } func TestRequestBodyStreamMultipleBodyCalls(t *testing.T) { t.Parallel() var r Request s := "foobar baz abc" if r.IsBodyStream() { t.Fatalf("IsBodyStream must return false") } r.SetBodyStream(bytes.NewBufferString(s), len(s)) if !r.IsBodyStream() { t.Fatalf("IsBodyStream must return true") } for i := 0; i < 10; i++ { body := r.Body() if string(body) != s { t.Fatalf("unexpected body %q. Expecting %q. iteration %d", body, s, i) } } } func TestResponseBodyStreamMultipleBodyCalls(t *testing.T) { t.Parallel() var r Response s := "foobar baz abc" if r.IsBodyStream() { t.Fatalf("IsBodyStream must return false") } r.SetBodyStream(bytes.NewBufferString(s), len(s)) if !r.IsBodyStream() { t.Fatalf("IsBodyStream must return true") } for i := 0; i < 10; i++ { body := r.Body() if string(body) != s { t.Fatalf("unexpected body %q. Expecting %q. iteration %d", body, s, i) } } } func TestRequestBodyWriteToPlain(t *testing.T) { t.Parallel() var r Request expectedS := "foobarbaz" r.AppendBodyString(expectedS) testBodyWriteTo(t, &r, expectedS, true) } func TestResponseBodyWriteToPlain(t *testing.T) { t.Parallel() var r Response expectedS := "foobarbaz" r.AppendBodyString(expectedS) testBodyWriteTo(t, &r, expectedS, true) } func TestResponseBodyWriteToStream(t *testing.T) { t.Parallel() var r Response expectedS := "aaabbbccc" buf := bytes.NewBufferString(expectedS) if r.IsBodyStream() { t.Fatalf("IsBodyStream must return false") } r.SetBodyStream(buf, len(expectedS)) if !r.IsBodyStream() { t.Fatalf("IsBodyStream must return true") } testBodyWriteTo(t, &r, expectedS, false) } func TestRequestBodyWriteToMultipart(t *testing.T) { t.Parallel() expectedS := "--foobar\r\nContent-Disposition: form-data; name=\"key_0\"\r\n\r\nvalue_0\r\n--foobar--\r\n" s := fmt.Sprintf("POST / HTTP/1.1\r\nHost: aaa\r\nContent-Type: multipart/form-data; boundary=foobar\r\nContent-Length: %d\r\n\r\n%s", len(expectedS), expectedS) var r Request br := bufio.NewReader(bytes.NewBufferString(s)) if err := r.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } testBodyWriteTo(t, &r, expectedS, true) } type bodyWriterTo interface { BodyWriteTo(io.Writer) error Body() []byte } func testBodyWriteTo(t *testing.T, bw bodyWriterTo, expectedS string, isRetainedBody bool) { var buf bytebufferpool.ByteBuffer if err := bw.BodyWriteTo(&buf); err != nil { t.Fatalf("unexpected error: %v", err) } s := buf.B if string(s) != expectedS { t.Fatalf("unexpected result %q. Expecting %q", s, expectedS) } body := bw.Body() if isRetainedBody { if string(body) != expectedS { t.Fatalf("unexpected body %q. Expecting %q", body, expectedS) } } else { if len(body) > 0 { t.Fatalf("unexpected non-zero body after BodyWriteTo: %q", body) } } } func TestRequestReadEOF(t *testing.T) { t.Parallel() var r Request br := bufio.NewReader(&bytes.Buffer{}) err := r.Read(br) if err == nil { t.Fatalf("expecting error") } if err != io.EOF { t.Fatalf("unexpected error: %v. Expecting %v", err, io.EOF) } // incomplete request mustn't return io.EOF br = bufio.NewReader(bytes.NewBufferString("POST / HTTP/1.1\r\nContent-Type: aa\r\nContent-Length: 1234\r\n\r\nIncomplete body")) err = r.Read(br) if err == nil { t.Fatalf("expecting error") } if err == io.EOF { t.Fatalf("expecting non-EOF error") } } func TestResponseReadEOF(t *testing.T) { t.Parallel() var r Response br := bufio.NewReader(&bytes.Buffer{}) err := r.Read(br) if err == nil { t.Fatalf("expecting error") } if err != io.EOF { t.Fatalf("unexpected error: %v. Expecting %v", err, io.EOF) } // incomplete response mustn't return io.EOF br = bufio.NewReader(bytes.NewBufferString("HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nContent-Length: 123\r\n\r\nIncomplete body")) err = r.Read(br) if err == nil { t.Fatalf("expecting error") } if err == io.EOF { t.Fatalf("expecting non-EOF error") } } func TestRequestReadNoBody(t *testing.T) { t.Parallel() var r Request br := bufio.NewReader(bytes.NewBufferString("GET / HTTP/1.1\r\n\r\n")) err := r.Read(br) r.SetHost("foobar") if err != nil { t.Fatalf("unexpected error: %v", err) } s := r.String() if strings.Contains(s, "Content-Length: ") { t.Fatalf("unexpected Content-Length") } } func TestRequestReadNoBodyStreaming(t *testing.T) { t.Parallel() var r Request r.Header.contentLength = -2 br := bufio.NewReader(bytes.NewBufferString("GET / HTTP/1.1\r\n\r\n")) err := r.ContinueReadBodyStream(br, 0) r.SetHost("foobar") if err != nil { t.Fatalf("unexpected error: %v", err) } s := r.String() if strings.Contains(s, "Content-Length: ") { t.Fatalf("unexpected Content-Length") } } func TestResponseWriteTo(t *testing.T) { t.Parallel() var r Response r.SetBodyString("foobar") s := r.String() var buf bytebufferpool.ByteBuffer n, err := r.WriteTo(&buf) if err != nil { t.Fatalf("unexpected error: %v", err) } if n != int64(len(s)) { t.Fatalf("unexpected response length %d. Expecting %d", n, len(s)) } if string(buf.B) != s { t.Fatalf("unexpected response %q. Expecting %q", buf.B, s) } } func TestRequestWriteTo(t *testing.T) { t.Parallel() var r Request r.SetRequestURI("http://foobar.com/aaa/bbb") s := r.String() var buf bytebufferpool.ByteBuffer n, err := r.WriteTo(&buf) if err != nil { t.Fatalf("unexpected error: %v", err) } if n != int64(len(s)) { t.Fatalf("unexpected request length %d. Expecting %d", n, len(s)) } if string(buf.B) != s { t.Fatalf("unexpected request %q. Expecting %q", buf.B, s) } } func TestResponseSkipBody(t *testing.T) { t.Parallel() var r Response // set StatusNotModified r.Header.SetStatusCode(StatusNotModified) r.SetBodyString("foobar") s := r.String() if strings.Contains(s, "\r\n\r\nfoobar") { t.Fatalf("unexpected non-zero body in response %q", s) } if strings.Contains(s, "Content-Length: ") { t.Fatalf("unexpected content-length in response %q", s) } if strings.Contains(s, "Content-Type: ") { t.Fatalf("unexpected content-type in response %q", s) } // set StatusNoContent r.Header.SetStatusCode(StatusNoContent) r.SetBodyString("foobar") s = r.String() if strings.Contains(s, "\r\n\r\nfoobar") { t.Fatalf("unexpected non-zero body in response %q", s) } if strings.Contains(s, "Content-Length: ") { t.Fatalf("unexpected content-length in response %q", s) } if strings.Contains(s, "Content-Type: ") { t.Fatalf("unexpected content-type in response %q", s) } // set StatusNoContent with statusMessage r.Header.SetStatusCode(StatusNoContent) r.Header.SetStatusMessage([]byte("NC")) r.SetBodyString("foobar") s = r.String() if strings.Contains(s, "\r\n\r\nfoobar") { t.Fatalf("unexpected non-zero body in response %q", s) } if strings.Contains(s, "Content-Length: ") { t.Fatalf("unexpected content-length in response %q", s) } if strings.Contains(s, "Content-Type: ") { t.Fatalf("unexpected content-type in response %q", s) } if !strings.HasPrefix(s, "HTTP/1.1 204 NC\r\n") { t.Fatalf("expecting non-default status line in response %q", s) } // explicitly skip body r.Header.SetStatusCode(StatusOK) r.SkipBody = true r.SetBodyString("foobar") s = r.String() if strings.Contains(s, "\r\n\r\nfoobar") { t.Fatalf("unexpected non-zero body in response %q", s) } if !strings.Contains(s, "Content-Length: 6\r\n") { t.Fatalf("expecting content-length in response %q", s) } if !strings.Contains(s, "Content-Type: ") { t.Fatalf("expecting content-type in response %q", s) } } func TestRequestNoContentLength(t *testing.T) { t.Parallel() var r Request r.Header.SetMethod(MethodHead) r.Header.SetHost("foobar") s := r.String() if strings.Contains(s, "Content-Length: ") { t.Fatalf("unexpected content-length in HEAD request %q", s) } r.Header.SetMethod(MethodPost) fmt.Fprintf(r.BodyWriter(), "foobar body") s = r.String() if !strings.Contains(s, "Content-Length: ") { t.Fatalf("missing content-length header in non-GET request %q", s) } } func TestRequestReadGzippedBody(t *testing.T) { t.Parallel() var r Request bodyOriginal := "foo bar baz compress me better!" body := AppendGzipBytes(nil, []byte(bodyOriginal)) s := fmt.Sprintf("POST /foobar HTTP/1.1\r\nContent-Type: foo/bar\r\nContent-Encoding: gzip\r\nContent-Length: %d\r\n\r\n%s", len(body), body) br := bufio.NewReader(bytes.NewBufferString(s)) if err := r.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } if string(r.Header.ContentEncoding()) != "gzip" { t.Fatalf("unexpected content-encoding: %q. Expecting %q", r.Header.ContentEncoding(), "gzip") } if r.Header.ContentLength() != len(body) { t.Fatalf("unexpected content-length: %d. Expecting %d", r.Header.ContentLength(), len(body)) } if !bytes.Equal(r.Body(), body) { t.Fatalf("unexpected body: %q. Expecting %q", r.Body(), body) } bodyGunzipped, err := AppendGunzipBytes(nil, r.Body()) if err != nil { t.Fatalf("unexpected error when uncompressing data: %v", err) } if string(bodyGunzipped) != bodyOriginal { t.Fatalf("unexpected uncompressed body %q. Expecting %q", bodyGunzipped, bodyOriginal) } } func TestRequestReadPostNoBody(t *testing.T) { t.Parallel() var r Request s := "POST /foo/bar HTTP/1.1\r\nContent-Type: aaa/bbb\r\n\r\naaaa" br := bufio.NewReader(bytes.NewBufferString(s)) if err := r.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } if string(r.Header.RequestURI()) != "/foo/bar" { t.Fatalf("unexpected request uri %q. Expecting %q", r.Header.RequestURI(), "/foo/bar") } if string(r.Header.ContentType()) != "aaa/bbb" { t.Fatalf("unexpected content-type %q. Expecting %q", r.Header.ContentType(), "aaa/bbb") } if len(r.Body()) != 0 { t.Fatalf("unexpected body found %q. Expecting empty body", r.Body()) } if r.Header.ContentLength() != 0 { t.Fatalf("unexpected content-length: %d. Expecting 0", r.Header.ContentLength()) } tail, err := io.ReadAll(br) if err != nil { t.Fatalf("unexpected error: %v", err) } if string(tail) != "aaaa" { t.Fatalf("unexpected tail %q. Expecting %q", tail, "aaaa") } } func TestRequestContinueReadBody(t *testing.T) { t.Parallel() s := "PUT /foo/bar HTTP/1.1\r\nExpect: 100-continue\r\nContent-Length: 5\r\nContent-Type: foo/bar\r\n\r\nabcdef4343" br := bufio.NewReader(bytes.NewBufferString(s)) var r Request if err := r.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } if !r.MayContinue() { t.Fatalf("MayContinue must return true") } if err := r.ContinueReadBody(br, 0, true); err != nil { t.Fatalf("error when reading request body: %v", err) } body := r.Body() if string(body) != "abcde" { t.Fatalf("unexpected body %q. Expecting %q", body, "abcde") } tail, err := io.ReadAll(br) if err != nil { t.Fatalf("unexpected error: %v", err) } if string(tail) != "f4343" { t.Fatalf("unexpected tail %q. Expecting %q", tail, "f4343") } } func TestRequestContinueReadBodyDisablePrereadMultipartForm(t *testing.T) { t.Parallel() var w bytes.Buffer mw := multipart.NewWriter(&w) for i := 0; i < 10; i++ { k := fmt.Sprintf("key_%d", i) v := fmt.Sprintf("value_%d", i) if err := mw.WriteField(k, v); err != nil { t.Fatalf("unexpected error: %v", err) } } boundary := mw.Boundary() if err := mw.Close(); err != nil { t.Fatalf("unexpected error: %v", err) } formData := w.Bytes() s := fmt.Sprintf("POST / HTTP/1.1\r\nHost: aaa\r\nContent-Type: multipart/form-data; boundary=%s\r\nContent-Length: %d\r\n\r\n%s", boundary, len(formData), formData) br := bufio.NewReader(bytes.NewBufferString(s)) var r Request if err := r.Header.Read(br); err != nil { t.Fatalf("unexpected error reading headers: %v", err) } if err := r.readLimitBody(br, 10000, false, false); err != nil { t.Fatalf("unexpected error reading body: %v", err) } if r.multipartForm != nil { t.Fatalf("The multipartForm of the Request must be nil") } if !bytes.Equal(formData, r.Body()) { t.Fatalf("The body given must equal the body in the Request") } } func TestRequestMayContinue(t *testing.T) { t.Parallel() var r Request if r.MayContinue() { t.Fatalf("MayContinue on empty request must return false") } r.Header.Set("Expect", "123sdfds") if r.MayContinue() { t.Fatalf("MayContinue on invalid Expect header must return false") } r.Header.Set("Expect", "100-continue") if !r.MayContinue() { t.Fatalf("MayContinue on 'Expect: 100-continue' header must return true") } } func TestResponseGzipStream(t *testing.T) { t.Parallel() var r Response if r.IsBodyStream() { t.Fatalf("IsBodyStream must return false") } r.SetBodyStreamWriter(func(w *bufio.Writer) { fmt.Fprintf(w, "foo") w.Flush() time.Sleep(time.Millisecond) _, _ = w.WriteString("barbaz") _ = w.Flush() time.Sleep(time.Millisecond) _, _ = fmt.Fprintf(w, "1234") if err := w.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } }) if !r.IsBodyStream() { t.Fatalf("IsBodyStream must return true") } testResponseGzipExt(t, &r, "foobarbaz1234") } func TestResponseDeflateStream(t *testing.T) { t.Parallel() var r Response if r.IsBodyStream() { t.Fatalf("IsBodyStream must return false") } r.SetBodyStreamWriter(func(w *bufio.Writer) { _, _ = w.WriteString("foo") _ = w.Flush() _, _ = fmt.Fprintf(w, "barbaz") _ = w.Flush() _, _ = w.WriteString("1234") if err := w.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } }) if !r.IsBodyStream() { t.Fatalf("IsBodyStream must return true") } testResponseDeflateExt(t, &r, "foobarbaz1234") } func TestResponseDeflate(t *testing.T) { t.Parallel() for _, s := range compressTestcases { testResponseDeflate(t, s) } } func TestResponseGzip(t *testing.T) { t.Parallel() for _, s := range compressTestcases { testResponseGzip(t, s) } } func testResponseDeflate(t *testing.T, s string) { var r Response r.SetBodyString(s) testResponseDeflateExt(t, &r, s) // make sure the uncompressible Content-Type isn't compressed r.Reset() r.Header.SetContentType("image/jpeg") r.SetBodyString(s) testResponseDeflateExt(t, &r, s) } func testResponseDeflateExt(t *testing.T, r *Response, s string) { isCompressible := isCompressibleResponse(r, s) var buf bytes.Buffer var err error bw := bufio.NewWriter(&buf) if err = r.WriteDeflate(bw); err != nil { t.Fatalf("unexpected error: %v", err) } if err = bw.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } var r1 Response br := bufio.NewReader(&buf) if err = r1.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } ce := r1.Header.ContentEncoding() var body []byte if isCompressible { if string(ce) != "deflate" { t.Fatalf("unexpected Content-Encoding %q. Expecting %q. len(s)=%d, Content-Type: %q", ce, "deflate", len(s), r.Header.ContentType()) } body, err = r1.BodyInflate() if err != nil { t.Fatalf("unexpected error: %v", err) } } else { if len(ce) > 0 { t.Fatalf("expecting empty Content-Encoding. Got %q", ce) } body = r1.Body() } if string(body) != s { t.Fatalf("unexpected body %q. Expecting %q", body, s) } } func testResponseGzip(t *testing.T, s string) { var r Response r.SetBodyString(s) testResponseGzipExt(t, &r, s) // make sure the uncompressible Content-Type isn't compressed r.Reset() r.Header.SetContentType("image/jpeg") r.SetBodyString(s) testResponseGzipExt(t, &r, s) } func testResponseGzipExt(t *testing.T, r *Response, s string) { isCompressible := isCompressibleResponse(r, s) var buf bytes.Buffer var err error bw := bufio.NewWriter(&buf) if err = r.WriteGzip(bw); err != nil { t.Fatalf("unexpected error: %v", err) } if err = bw.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } var r1 Response br := bufio.NewReader(&buf) if err = r1.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } ce := r1.Header.ContentEncoding() var body []byte if isCompressible { if string(ce) != "gzip" { t.Fatalf("unexpected Content-Encoding %q. Expecting %q. len(s)=%d, Content-Type: %q", ce, "gzip", len(s), r.Header.ContentType()) } body, err = r1.BodyGunzip() if err != nil { t.Fatalf("unexpected error: %v", err) } } else { if len(ce) > 0 { t.Fatalf("Expecting empty Content-Encoding. Got %q", ce) } body = r1.Body() } if string(body) != s { t.Fatalf("unexpected body %q. Expecting %q", body, s) } } func isCompressibleResponse(r *Response, s string) bool { isCompressible := r.Header.isCompressibleContentType() if isCompressible && len(s) < minCompressLen && !r.IsBodyStream() { isCompressible = false } return isCompressible } func TestRequestMultipartForm(t *testing.T) { t.Parallel() var w bytes.Buffer mw := multipart.NewWriter(&w) for i := 0; i < 10; i++ { k := fmt.Sprintf("key_%d", i) v := fmt.Sprintf("value_%d", i) if err := mw.WriteField(k, v); err != nil { t.Fatalf("unexpected error: %v", err) } } boundary := mw.Boundary() if err := mw.Close(); err != nil { t.Fatalf("unexpected error: %v", err) } formData := w.Bytes() for i := 0; i < 5; i++ { formData = testRequestMultipartForm(t, boundary, formData, 10) } // verify request unmarshaling / marshaling s := "POST / HTTP/1.1\r\nHost: aaa\r\nContent-Type: multipart/form-data; boundary=foobar\r\nContent-Length: 213\r\n\r\n--foobar\r\nContent-Disposition: form-data; name=\"key_0\"\r\n\r\nvalue_0\r\n--foobar\r\nContent-Disposition: form-data; name=\"key_1\"\r\n\r\nvalue_1\r\n--foobar\r\nContent-Disposition: form-data; name=\"key_2\"\r\n\r\nvalue_2\r\n--foobar--\r\n" var req Request br := bufio.NewReader(bytes.NewBufferString(s)) if err := req.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } s = req.String() br = bufio.NewReader(bytes.NewBufferString(s)) if err := req.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } testRequestMultipartForm(t, "foobar", req.Body(), 3) } func testRequestMultipartForm(t *testing.T, boundary string, formData []byte, partsCount int) []byte { s := fmt.Sprintf("POST / HTTP/1.1\r\nHost: aaa\r\nContent-Type: multipart/form-data; boundary=%s\r\nContent-Length: %d\r\n\r\n%s", boundary, len(formData), formData) var req Request r := bytes.NewBufferString(s) br := bufio.NewReader(r) if err := req.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } f, err := req.MultipartForm() if err != nil { t.Fatalf("unexpected error: %v", err) } defer req.RemoveMultipartFormFiles() if len(f.File) > 0 { t.Fatalf("unexpected files found in the multipart form: %d", len(f.File)) } if len(f.Value) != partsCount { t.Fatalf("unexpected number of values found: %d. Expecting %d", len(f.Value), partsCount) } for k, vv := range f.Value { if len(vv) != 1 { t.Fatalf("unexpected number of values found for key=%q: %d. Expecting 1", k, len(vv)) } if !strings.HasPrefix(k, "key_") { t.Fatalf("unexpected key prefix=%q. Expecting %q", k, "key_") } v := vv[0] if !strings.HasPrefix(v, "value_") { t.Fatalf("unexpected value prefix=%q. expecting %q", v, "value_") } if k[len("key_"):] != v[len("value_"):] { t.Fatalf("key and value suffixes don't match: %q vs %q", k, v) } } return req.Body() } func TestResponseReadLimitBody(t *testing.T) { t.Parallel() // response with content-length testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 10\r\n\r\n9876543210", 10) testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 10\r\n\r\n9876543210", 100) testResponseReadLimitBodyError(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 10\r\n\r\n9876543210", 9, ErrBodyTooLarge) // chunked response testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 9) testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\nFoo: bar\r\n\r\n", 9) testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 100) testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\nfoobar\r\n\r\n", 100) testResponseReadLimitBodyError(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 2, ErrBodyTooLarge) // identity response testResponseReadLimitBodySuccess(t, "HTTP/1.1 400 OK\r\nContent-Type: aa\r\n\r\n123456", 6) testResponseReadLimitBodySuccess(t, "HTTP/1.1 400 OK\r\nContent-Type: aa\r\n\r\n123456", 106) testResponseReadLimitBodyError(t, "HTTP/1.1 400 OK\r\nContent-Type: aa\r\n\r\n123456", 5, ErrBodyTooLarge) } func TestRequestReadLimitBody(t *testing.T) { t.Parallel() // request with content-length testRequestReadLimitBodySuccess(t, "POST /foo HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 9\r\nContent-Type: aaa\r\n\r\n123456789", 9) testRequestReadLimitBodySuccess(t, "POST /foo HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 9\r\nContent-Type: aaa\r\n\r\n123456789", 92) testRequestReadLimitBodyError(t, "POST /foo HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 9\r\nContent-Type: aaa\r\n\r\n123456789", 5, ErrBodyTooLarge) // chunked request testRequestReadLimitBodySuccess(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 9) testRequestReadLimitBodySuccess(t, "POST /a HTTP/1.1\nHost: a.com\nTransfer-Encoding: chunked\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\nFoo: bar\r\n\r\n", 9) testRequestReadLimitBodySuccess(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 999) testRequestReadLimitBodySuccess(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\nfoobar\r\n\r\n", 999) testRequestReadLimitBodyError(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 8, ErrBodyTooLarge) } func testResponseReadLimitBodyError(t *testing.T, s string, maxBodySize int, expectedErr error) { var resp Response r := bytes.NewBufferString(s) br := bufio.NewReader(r) err := resp.ReadLimitBody(br, maxBodySize) if err == nil { t.Fatalf("expecting error. s=%q, maxBodySize=%d", s, maxBodySize) } if err != expectedErr { t.Fatalf("unexpected error: %v. Expecting %v. s=%q, maxBodySize=%d", err, expectedErr, s, maxBodySize) } } func testResponseReadLimitBodySuccess(t *testing.T, s string, maxBodySize int) { var resp Response r := bytes.NewBufferString(s) br := bufio.NewReader(r) if err := resp.ReadLimitBody(br, maxBodySize); err != nil { t.Fatalf("unexpected error: %v. s=%q, maxBodySize=%d", err, s, maxBodySize) } } func testRequestReadLimitBodyError(t *testing.T, s string, maxBodySize int, expectedErr error) { var req Request r := bytes.NewBufferString(s) br := bufio.NewReader(r) err := req.ReadLimitBody(br, maxBodySize) if err == nil { t.Fatalf("expecting error. s=%q, maxBodySize=%d", s, maxBodySize) } if err != expectedErr { t.Fatalf("unexpected error: %v. Expecting %v. s=%q, maxBodySize=%d", err, expectedErr, s, maxBodySize) } } func testRequestReadLimitBodySuccess(t *testing.T, s string, maxBodySize int) { var req Request r := bytes.NewBufferString(s) br := bufio.NewReader(r) if err := req.ReadLimitBody(br, maxBodySize); err != nil { t.Fatalf("unexpected error: %v. s=%q, maxBodySize=%d", err, s, maxBodySize) } } func TestRequestString(t *testing.T) { t.Parallel() var r Request r.SetRequestURI("http://foobar.com/aaa") s := r.String() expectedS := "GET /aaa HTTP/1.1\r\nHost: foobar.com\r\n\r\n" if s != expectedS { t.Fatalf("unexpected request: %q. Expecting %q", s, expectedS) } } func TestRequestBodyWriter(t *testing.T) { var r Request w := r.BodyWriter() for i := 0; i < 10; i++ { fmt.Fprintf(w, "%d", i) } if string(r.Body()) != "0123456789" { t.Fatalf("unexpected body %q. Expecting %q", r.Body(), "0123456789") } } func TestResponseBodyWriter(t *testing.T) { t.Parallel() var r Response w := r.BodyWriter() for i := 0; i < 10; i++ { fmt.Fprintf(w, "%d", i) } if string(r.Body()) != "0123456789" { t.Fatalf("unexpected body %q. Expecting %q", r.Body(), "0123456789") } } func TestRequestWriteRequestURINoHost(t *testing.T) { t.Parallel() var req Request req.Header.SetRequestURI("http://google.com/foo/bar?baz=aaa") var w bytes.Buffer bw := bufio.NewWriter(&w) if err := req.Write(bw); err != nil { t.Fatalf("unexpected error: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } var req1 Request br := bufio.NewReader(&w) if err := req1.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } if string(req1.Header.Host()) != "google.com" { t.Fatalf("unexpected host: %q. Expecting %q", req1.Header.Host(), "google.com") } if string(req.Header.RequestURI()) != "/foo/bar?baz=aaa" { t.Fatalf("unexpected requestURI: %q. Expecting %q", req.Header.RequestURI(), "/foo/bar?baz=aaa") } // verify that Request.Write returns error on non-absolute RequestURI req.Reset() req.Header.SetRequestURI("/foo/bar") w.Reset() bw.Reset(&w) if err := req.Write(bw); err == nil { t.Fatalf("expecting error") } } func TestSetRequestBodyStreamFixedSize(t *testing.T) { t.Parallel() testSetRequestBodyStream(t, "a") testSetRequestBodyStream(t, string(createFixedBody(4097))) testSetRequestBodyStream(t, string(createFixedBody(100500))) } func TestSetResponseBodyStreamFixedSize(t *testing.T) { t.Parallel() testSetResponseBodyStream(t, "a") testSetResponseBodyStream(t, string(createFixedBody(4097))) testSetResponseBodyStream(t, string(createFixedBody(100500))) } func TestSetRequestBodyStreamChunked(t *testing.T) { t.Parallel() testSetRequestBodyStreamChunked(t, "", map[string]string{"Foo": "bar"}) body := "foobar baz aaa bbb ccc" testSetRequestBodyStreamChunked(t, body, nil) body = string(createFixedBody(10001)) testSetRequestBodyStreamChunked(t, body, map[string]string{"Foo": "test", "Bar": "test"}) } func TestSetResponseBodyStreamChunked(t *testing.T) { t.Parallel() testSetResponseBodyStreamChunked(t, "", map[string]string{"Foo": "bar"}) body := "foobar baz aaa bbb ccc" testSetResponseBodyStreamChunked(t, body, nil) body = string(createFixedBody(10001)) testSetResponseBodyStreamChunked(t, body, map[string]string{"Foo": "test", "Bar": "test"}) } func testSetRequestBodyStream(t *testing.T, body string) { var req Request req.Header.SetHost("foobar.com") req.Header.SetMethod(MethodPost) bodySize := len(body) if req.IsBodyStream() { t.Fatalf("IsBodyStream must return false") } req.SetBodyStream(bytes.NewBufferString(body), bodySize) if !req.IsBodyStream() { t.Fatalf("IsBodyStream must return true") } var w bytes.Buffer bw := bufio.NewWriter(&w) if err := req.Write(bw); err != nil { t.Fatalf("unexpected error when writing request: %v. body=%q", err, body) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error when flushing request: %v. body=%q", err, body) } var req1 Request br := bufio.NewReader(&w) if err := req1.Read(br); err != nil { t.Fatalf("unexpected error when reading request: %v. body=%q", err, body) } if string(req1.Body()) != body { t.Fatalf("unexpected body %q. Expecting %q", req1.Body(), body) } } func testSetRequestBodyStreamChunked(t *testing.T, body string, trailer map[string]string) { var req Request req.Header.SetHost("foobar.com") req.Header.SetMethod(MethodPost) if req.IsBodyStream() { t.Fatalf("IsBodyStream must return false") } req.SetBodyStream(bytes.NewBufferString(body), -1) if !req.IsBodyStream() { t.Fatalf("IsBodyStream must return true") } var w bytes.Buffer bw := bufio.NewWriter(&w) for k := range trailer { err := req.Header.AddTrailer(k) if err != nil { t.Fatalf("unexpected error: %v", err) } } if err := req.Write(bw); err != nil { t.Fatalf("unexpected error when writing request: %v. body=%q", err, body) } for k, v := range trailer { req.Header.Set(k, v) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error when flushing request: %v. body=%q", err, body) } var req1 Request br := bufio.NewReader(&w) if err := req1.Read(br); err != nil { t.Fatalf("unexpected error when reading request: %v. body=%q", err, body) } if string(req1.Body()) != body { t.Fatalf("unexpected body %q. Expecting %q", req1.Body(), body) } for k, v := range trailer { r := req.Header.Peek(k) if string(r) != v { t.Fatalf("unexpected trailer %q. Expecting %q. Got %q", k, v, r) } } } func testSetResponseBodyStream(t *testing.T, body string) { var resp Response bodySize := len(body) if resp.IsBodyStream() { t.Fatalf("IsBodyStream must return false") } resp.SetBodyStream(bytes.NewBufferString(body), bodySize) if !resp.IsBodyStream() { t.Fatalf("IsBodyStream must return true") } var w bytes.Buffer bw := bufio.NewWriter(&w) if err := resp.Write(bw); err != nil { t.Fatalf("unexpected error when writing response: %v. body=%q", err, body) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error when flushing response: %v. body=%q", err, body) } var resp1 Response br := bufio.NewReader(&w) if err := resp1.Read(br); err != nil { t.Fatalf("unexpected error when reading response: %v. body=%q", err, body) } if string(resp1.Body()) != body { t.Fatalf("unexpected body %q. Expecting %q", resp1.Body(), body) } } func testSetResponseBodyStreamChunked(t *testing.T, body string, trailer map[string]string) { var resp Response if resp.IsBodyStream() { t.Fatalf("IsBodyStream must return false") } resp.SetBodyStream(bytes.NewBufferString(body), -1) if !resp.IsBodyStream() { t.Fatalf("IsBodyStream must return true") } var w bytes.Buffer bw := bufio.NewWriter(&w) for k := range trailer { err := resp.Header.AddTrailer(k) if err != nil { t.Fatalf("unexpected error: %v", err) } } if err := resp.Write(bw); err != nil { t.Fatalf("unexpected error when writing response: %v. body=%q", err, body) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error when flushing response: %v. body=%q", err, body) } for k, v := range trailer { resp.Header.Set(k, v) } var resp1 Response br := bufio.NewReader(&w) if err := resp1.Read(br); err != nil { t.Fatalf("unexpected error when reading response: %v. body=%q", err, body) } if string(resp1.Body()) != body { t.Fatalf("unexpected body %q. Expecting %q", resp1.Body(), body) } for k, v := range trailer { r := resp.Header.Peek(k) if string(r) != v { t.Fatalf("unexpected trailer %q. Expecting %q. Got %q", k, v, r) } } } func TestRequestReadChunked(t *testing.T) { t.Parallel() var req Request s := "POST /foo HTTP/1.1\r\nHost: google.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa/bb\r\n\r\n3\r\nabc\r\n5\r\n12345\r\n0\r\n\r\nTrail: test\r\n\r\n" r := bytes.NewBufferString(s) rb := bufio.NewReader(r) err := req.Read(rb) if err != nil { t.Fatalf("Unexpected error when reading chunked request: %v", err) } expectedBody := "abc12345" if string(req.Body()) != expectedBody { t.Fatalf("Unexpected body %q. Expected %q", req.Body(), expectedBody) } verifyRequestHeader(t, &req.Header, -1, "/foo", "google.com", "", "aa/bb") verifyTrailer(t, rb, map[string]string{"Trail": "test"}, true) } func TestRequestChunkedEmpty(t *testing.T) { t.Parallel() var req Request s := "POST /foo HTTP/1.1\r\nHost: google.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa/bb\r\n\r\n0\r\n\r\n" r := bytes.NewBufferString(s) rb := bufio.NewReader(r) err := req.Read(rb) if err != nil { t.Fatalf("Unexpected error when reading chunked request: %v", err) } expectedBody := "" if string(req.Body()) != expectedBody { t.Fatalf("Unexpected body %q. Expected %q", req.Body(), expectedBody) } expectRequestHeaderGet(t, &req.Header, HeaderTransferEncoding, "") } // See: https://github.com/erikdubbelboer/fasthttp/issues/34 func TestRequestChunkedWhitespace(t *testing.T) { t.Parallel() var req Request s := "POST /foo HTTP/1.1\r\nHost: google.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa/bb\r\n\r\n3 \r\nabc\r\n0\r\n\r\n" r := bytes.NewBufferString(s) rb := bufio.NewReader(r) err := req.Read(rb) if err != nil { t.Fatalf("Unexpected error when reading chunked request: %v", err) } expectedBody := "abc" if string(req.Body()) != expectedBody { t.Fatalf("Unexpected body %q. Expected %q", req.Body(), expectedBody) } } func TestResponseReadWithoutBody(t *testing.T) { t.Parallel() var resp Response testResponseReadWithoutBody(t, &resp, "HTTP/1.1 304 Not Modified\r\nContent-Type: aa\r\nContent-Length: 1235\r\n\r\n", false, 304, 1235, "aa") testResponseReadWithoutBody(t, &resp, "HTTP/1.1 204 Foo Bar\r\nContent-Type: aab\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", false, 204, -1, "aab") testResponseReadWithoutBody(t, &resp, "HTTP/1.1 123 AAA\r\nContent-Type: xxx\r\nContent-Length: 3434\r\n\r\n", false, 123, 3434, "xxx") testResponseReadWithoutBody(t, &resp, "HTTP 200 OK\r\nContent-Type: text/xml\r\nContent-Length: 123\r\n\r\nfoobar\r\n", true, 200, 123, "text/xml") // '100 Continue' must be skipped. testResponseReadWithoutBody(t, &resp, "HTTP/1.1 100 Continue\r\nFoo-bar: baz\r\n\r\nHTTP/1.1 329 aaa\r\nContent-Type: qwe\r\nContent-Length: 894\r\n\r\n", true, 329, 894, "qwe") } func testResponseReadWithoutBody(t *testing.T, resp *Response, s string, skipBody bool, expectedStatusCode, expectedContentLength int, expectedContentType string, ) { t.Helper() r := bytes.NewBufferString(s) rb := bufio.NewReader(r) resp.SkipBody = skipBody err := resp.Read(rb) if err != nil { t.Fatalf("Unexpected error when reading response without body: %v. response=%q", err, s) } if len(resp.Body()) != 0 { t.Fatalf("Unexpected response body %q. Expected %q. response=%q", resp.Body(), "", s) } verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, "") // verify that ordinal response is read after null-body response resp.SkipBody = false testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nContent-Length: 5\r\nContent-Type: bar\r\n\r\n56789aaa", 300, 5, "bar", "56789", nil) } func TestRequestSuccess(t *testing.T) { t.Parallel() // empty method, user-agent and body testRequestSuccess(t, "", "/foo/bar", "google.com", "", "", MethodGet) // non-empty user-agent testRequestSuccess(t, MethodGet, "/foo/bar", "google.com", "MSIE", "", MethodGet) // non-empty method testRequestSuccess(t, MethodHead, "/aaa", "fobar", "", "", MethodHead) // POST method with body testRequestSuccess(t, MethodPost, "/bbb", "aaa.com", "Chrome aaa", "post body", MethodPost) // PUT method with body testRequestSuccess(t, MethodPut, "/aa/bb", "a.com", "ome aaa", "put body", MethodPut) // only host is set testRequestSuccess(t, "", "", "gooble.com", "", "", MethodGet) // get with body testRequestSuccess(t, MethodGet, "/foo/bar", "aaa.com", "", "foobar", MethodGet) } func TestResponseSuccess(t *testing.T) { t.Parallel() // 200 response testResponseSuccess(t, 200, "test/plain", "server", "foobar", 200, "test/plain", "server") // response with missing statusCode testResponseSuccess(t, 0, "text/plain", "server", "foobar", 200, "text/plain", "server") // response with missing server testResponseSuccess(t, 500, "aaa", "", "aaadfsd", 500, "aaa", "") // empty body testResponseSuccess(t, 200, "bbb", "qwer", "", 200, "bbb", "qwer") // missing content-type testResponseSuccess(t, 200, "", "asdfsd", "asdf", 200, string(defaultContentType), "asdfsd") } func testResponseSuccess(t *testing.T, statusCode int, contentType, serverName, body string, expectedStatusCode int, expectedContentType, expectedServerName string, ) { var resp Response resp.SetStatusCode(statusCode) resp.Header.Set("Content-Type", contentType) resp.Header.Set("Server", serverName) resp.SetBody([]byte(body)) w := &bytes.Buffer{} bw := bufio.NewWriter(w) err := resp.Write(bw) if err != nil { t.Fatalf("Unexpected error when calling Response.Write(): %v", err) } if err = bw.Flush(); err != nil { t.Fatalf("Unexpected error when flushing bufio.Writer: %v", err) } var resp1 Response br := bufio.NewReader(w) if err = resp1.Read(br); err != nil { t.Fatalf("Unexpected error when calling Response.Read(): %v", err) } if resp1.StatusCode() != expectedStatusCode { t.Fatalf("Unexpected status code: %d. Expected %d", resp1.StatusCode(), expectedStatusCode) } if resp1.Header.ContentLength() != len(body) { t.Fatalf("Unexpected content-length: %d. Expected %d", resp1.Header.ContentLength(), len(body)) } if string(resp1.Header.Peek(HeaderContentType)) != expectedContentType { t.Fatalf("Unexpected content-type: %q. Expected %q", resp1.Header.Peek(HeaderContentType), expectedContentType) } if string(resp1.Header.Peek(HeaderServer)) != expectedServerName { t.Fatalf("Unexpected server: %q. Expected %q", resp1.Header.Peek(HeaderServer), expectedServerName) } if !bytes.Equal(resp1.Body(), []byte(body)) { t.Fatalf("Unexpected body: %q. Expected %q", resp1.Body(), body) } } func TestRequestWriteError(t *testing.T) { t.Parallel() // no host testRequestWriteError(t, "", "/foo/bar", "", "", "") } func testRequestWriteError(t *testing.T, method, requestURI, host, userAgent, body string) { var req Request req.Header.SetMethod(method) req.Header.SetRequestURI(requestURI) req.Header.Set(HeaderHost, host) req.Header.Set(HeaderUserAgent, userAgent) req.SetBody([]byte(body)) w := &bytebufferpool.ByteBuffer{} bw := bufio.NewWriter(w) err := req.Write(bw) if err == nil { t.Fatalf("Expecting error when writing request=%#v", &req) } } func testRequestSuccess(t *testing.T, method, requestURI, host, userAgent, body, expectedMethod string) { var req Request req.Header.SetMethod(method) req.Header.SetRequestURI(requestURI) req.Header.Set(HeaderHost, host) req.Header.Set(HeaderUserAgent, userAgent) req.SetBody([]byte(body)) contentType := "foobar" if method == MethodPost { req.Header.Set(HeaderContentType, contentType) } w := &bytes.Buffer{} bw := bufio.NewWriter(w) err := req.Write(bw) if err != nil { t.Fatalf("Unexpected error when calling Request.Write(): %v", err) } if err = bw.Flush(); err != nil { t.Fatalf("Unexpected error when flushing bufio.Writer: %v", err) } var req1 Request br := bufio.NewReader(w) if err = req1.Read(br); err != nil { t.Fatalf("Unexpected error when calling Request.Read(): %v", err) } if string(req1.Header.Method()) != expectedMethod { t.Fatalf("Unexpected method: %q. Expected %q", req1.Header.Method(), expectedMethod) } if requestURI == "" { requestURI = "/" } if string(req1.Header.RequestURI()) != requestURI { t.Fatalf("Unexpected RequestURI: %q. Expected %q", req1.Header.RequestURI(), requestURI) } if string(req1.Header.Peek(HeaderHost)) != host { t.Fatalf("Unexpected host: %q. Expected %q", req1.Header.Peek(HeaderHost), host) } if string(req1.Header.Peek(HeaderUserAgent)) != userAgent { t.Fatalf("Unexpected user-agent: %q. Expected %q", req1.Header.Peek(HeaderUserAgent), userAgent) } if !bytes.Equal(req1.Body(), []byte(body)) { t.Fatalf("Unexpected body: %q. Expected %q", req1.Body(), body) } if method == MethodPost && string(req1.Header.Peek(HeaderContentType)) != contentType { t.Fatalf("Unexpected content-type: %q. Expected %q", req1.Header.Peek(HeaderContentType), contentType) } } func TestResponseReadSuccess(t *testing.T) { t.Parallel() resp := &Response{} // usual response testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: foo/bar\r\n\r\n0123456789", 200, 10, "foo/bar", "0123456789", nil) // zero response testResponseReadSuccess(t, resp, "HTTP/1.1 500 OK\r\nContent-Length: 0\r\nContent-Type: foo/bar\r\n\r\n", 500, 0, "foo/bar", "", nil) // response with trailer testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n", 300, -1, "bar", "56789", map[string]string{"Foo": "bar"}) // response with trailer disableNormalizing resp.Header.DisableNormalizing() testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n", 300, -1, "bar", "56789", map[string]string{"foo": "bar"}) // no content-length ('identity' transfer-encoding) testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: foobar\r\n\r\nzxxxx", 200, 5, "foobar", "zxxxx", nil) // explicitly stated 'Transfer-Encoding: identity' testResponseReadSuccess(t, resp, "HTTP/1.1 234 ss\r\nContent-Type: xxx\r\n\r\nxag", 234, 3, "xxx", "xag", nil) // big 'identity' response body := string(createFixedBody(100500)) testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\n\r\n"+body, 200, 100500, "aa", body, nil) // chunked response testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nqwer\r\n2\r\nty\r\n0\r\nFoo2: bar2\r\n\r\n", 200, -1, "text/html", "qwerty", map[string]string{"Foo2": "bar2"}) // chunked response with non-chunked Transfer-Encoding. testResponseReadSuccess(t, resp, "HTTP/1.1 230 OK\r\nContent-Type: text\r\nTransfer-Encoding: aaabbb\r\n\r\n2\r\ner\r\n2\r\nty\r\n0\r\nFoo3: bar3\r\n\r\n", 230, -1, "text", "erty", map[string]string{"Foo3": "bar3"}) // chunked response with content-length testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\nContent-Length: 123\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ntest\r\n0\r\nFoo4:bar4\r\n\r\n", 200, -1, "foo/bar", "test", map[string]string{"Foo4": "bar4"}) // chunked response with empty body testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nFoo5: bar5\r\n\r\n", 200, -1, "text/html", "", map[string]string{"Foo5": "bar5"}) // chunked response with chunk extension testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n3;ext\r\naaa\r\n0\r\nFoo6: bar6\r\n\r\n", 200, -1, "text/html", "aaa", map[string]string{"Foo6": "bar6"}) } func TestResponseReadError(t *testing.T) { t.Parallel() resp := &Response{} // empty response testResponseReadError(t, resp, "") // invalid header testResponseReadError(t, resp, "foobar") // empty body testResponseReadError(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nContent-Length: 1234\r\n\r\n") // invalid chunked body testResponseReadError(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nContent-Length: 1234\r\n\r\nshort") // chunked body without end chunk testResponseReadError(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nTransfer-Encoding: chunked\r\n\r\nfoo") testResponseReadError(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nfoo") } func testResponseReadError(t *testing.T, resp *Response, response string) { r := bytes.NewBufferString(response) rb := bufio.NewReader(r) err := resp.Read(rb) if err == nil { t.Fatalf("Expecting error for response=%q", response) } testResponseReadSuccess(t, resp, "HTTP/1.1 303 Redisred sedfs sdf\r\nContent-Type: aaa\r\nContent-Length: 5\r\n\r\nHELLO", 303, 5, "aaa", "HELLO", nil) } func testResponseReadSuccess(t *testing.T, resp *Response, response string, expectedStatusCode, expectedContentLength int, expectedContentType, expectedBody string, expectedTrailer map[string]string, ) { r := bytes.NewBufferString(response) rb := bufio.NewReader(r) err := resp.Read(rb) if err != nil { t.Fatalf("Unexpected error: %v", err) } verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, "") if !bytes.Equal(resp.Body(), []byte(expectedBody)) { t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), []byte(expectedBody)) } verifyResponseTrailer(t, &resp.Header, expectedTrailer) } func TestReadBodyFixedSize(t *testing.T) { t.Parallel() // zero-size body testReadBodyFixedSize(t, 0) // small-size body testReadBodyFixedSize(t, 3) // medium-size body testReadBodyFixedSize(t, 1024) // large-size body testReadBodyFixedSize(t, 1024*1024) // smaller body after big one testReadBodyFixedSize(t, 34345) } func TestReadBodyChunked(t *testing.T) { t.Parallel() // zero-size body testReadBodyChunked(t, 0) // small-size body testReadBodyChunked(t, 5) // medium-size body testReadBodyChunked(t, 43488) // big body testReadBodyChunked(t, 3*1024*1024) // smaller body after big one testReadBodyChunked(t, 12343) } func TestRequestURITLS(t *testing.T) { t.Parallel() uriNoScheme := "//foobar.com/baz/aa?bb=dd&dd#sdf" requestURI := "http:" + uriNoScheme requestURITLS := "https:" + uriNoScheme var req Request req.isTLS = true req.SetRequestURI(requestURI) uri := req.URI().String() if uri != requestURITLS { t.Fatalf("unexpected request uri: %q. Expecting %q", uri, requestURITLS) } req.Reset() req.SetRequestURI(requestURI) uri = req.URI().String() if uri != requestURI { t.Fatalf("unexpected request uri: %q. Expecting %q", uri, requestURI) } } func TestRequestURI(t *testing.T) { t.Parallel() host := "foobar.com" requestURI := "/aaa/bb+b%20d?ccc=ddd&qqq#1334dfds&=d" expectedPathOriginal := "/aaa/bb+b%20d" expectedPath := "/aaa/bb+b d" expectedQueryString := "ccc=ddd&qqq" expectedHash := "1334dfds&=d" var req Request req.Header.Set(HeaderHost, host) req.Header.SetRequestURI(requestURI) uri := req.URI() if string(uri.Host()) != host { t.Fatalf("Unexpected host %q. Expected %q", uri.Host(), host) } if string(uri.PathOriginal()) != expectedPathOriginal { t.Fatalf("Unexpected source path %q. Expected %q", uri.PathOriginal(), expectedPathOriginal) } if string(uri.Path()) != expectedPath { t.Fatalf("Unexpected path %q. Expected %q", uri.Path(), expectedPath) } if string(uri.QueryString()) != expectedQueryString { t.Fatalf("Unexpected query string %q. Expected %q", uri.QueryString(), expectedQueryString) } if string(uri.Hash()) != expectedHash { t.Fatalf("Unexpected hash %q. Expected %q", uri.Hash(), expectedHash) } } func TestRequestPostArgsSuccess(t *testing.T) { t.Parallel() var req Request testRequestPostArgsSuccess(t, &req, "POST / HTTP/1.1\r\nHost: aaa.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 0\r\n\r\n", 0, "foo=", "=") testRequestPostArgsSuccess(t, &req, "POST / HTTP/1.1\r\nHost: aaa.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 18\r\n\r\nfoo&b%20r=b+z=&qwe", 3, "foo=", "b r=b z=", "qwe=") } func TestRequestPostArgsError(t *testing.T) { t.Parallel() var req Request // non-post testRequestPostArgsError(t, &req, "GET /aa HTTP/1.1\r\nHost: aaa\r\n\r\n") // invalid content-type testRequestPostArgsError(t, &req, "POST /aa HTTP/1.1\r\nHost: aaa\r\nContent-Type: text/html\r\nContent-Length: 5\r\n\r\nabcde") } func testRequestPostArgsError(t *testing.T, req *Request, s string) { r := bytes.NewBufferString(s) br := bufio.NewReader(r) err := req.Read(br) if err != nil { t.Fatalf("Unexpected error when reading %q: %v", s, err) } ss := req.PostArgs().String() if ss != "" { t.Fatalf("unexpected post args: %q. Expecting empty post args", ss) } } func testRequestPostArgsSuccess(t *testing.T, req *Request, s string, expectedArgsLen int, expectedArgs ...string) { r := bytes.NewBufferString(s) br := bufio.NewReader(r) err := req.Read(br) if err != nil { t.Fatalf("Unexpected error when reading %q: %v", s, err) } args := req.PostArgs() if args.Len() != expectedArgsLen { t.Fatalf("Unexpected args len %d. Expected %d for %q", args.Len(), expectedArgsLen, s) } for _, x := range expectedArgs { tmp := strings.SplitN(x, "=", 2) k := tmp[0] v := tmp[1] vv := string(args.Peek(k)) if vv != v { t.Fatalf("Unexpected value for key %q: %q. Expected %q for %q", k, vv, v, s) } } } func testReadBodyChunked(t *testing.T, bodySize int) { body := createFixedBody(bodySize) expectedTrailer := map[string]string{"Foo": "bar"} chunkedBody := createChunkedBody(body, expectedTrailer, true) r := bytes.NewBuffer(chunkedBody) br := bufio.NewReader(r) b, err := readBodyChunked(br, 0, nil) if err != nil { t.Fatalf("Unexpected error for bodySize=%d: %v. body=%q, chunkedBody=%q", bodySize, err, body, chunkedBody) } if !bytes.Equal(b, body) { t.Fatalf("Unexpected response read for bodySize=%d: %q. Expected %q. chunkedBody=%q", bodySize, b, body, chunkedBody) } verifyTrailer(t, br, expectedTrailer, false) } func testReadBodyFixedSize(t *testing.T, bodySize int) { body := createFixedBody(bodySize) r := bytes.NewBuffer(body) br := bufio.NewReader(r) b, err := readBody(br, bodySize, 0, nil) if err != nil { t.Fatalf("Unexpected error in ReadResponseBody(%d): %v", bodySize, err) } if !bytes.Equal(b, body) { t.Fatalf("Unexpected response read for bodySize=%d: %q. Expected %q", bodySize, b, body) } verifyTrailer(t, br, nil, false) } func createFixedBody(bodySize int) []byte { var b []byte for i := 0; i < bodySize; i++ { b = append(b, byte(i%10)+'0') } return b } func createChunkedBody(body []byte, trailer map[string]string, withEnd bool) []byte { var b []byte chunkSize := 1 for len(body) > 0 { if chunkSize > len(body) { chunkSize = len(body) } b = append(b, []byte(fmt.Sprintf("%x\r\n", chunkSize))...) b = append(b, body[:chunkSize]...) b = append(b, []byte("\r\n")...) body = body[chunkSize:] chunkSize++ } if withEnd { b = append(b, "0\r\n"...) for k, v := range trailer { b = append(b, k...) b = append(b, ": "...) b = append(b, v...) b = append(b, "\r\n"...) } b = append(b, "\r\n"...) } return b } func TestWriteMultipartForm(t *testing.T) { t.Parallel() var w bytes.Buffer s := strings.ReplaceAll(`--foo Content-Disposition: form-data; name="key" value --foo Content-Disposition: form-data; name="file"; filename="test.json" Content-Type: application/json {"foo": "bar"} --foo-- `, "\n", "\r\n") mr := multipart.NewReader(strings.NewReader(s), "foo") form, err := mr.ReadForm(1024) if err != nil { t.Fatalf("unexpected error: %v", err) } if err := WriteMultipartForm(&w, form, "foo"); err != nil { t.Fatalf("unexpected error: %v", err) } if w.String() != s { t.Fatalf("unexpected output %q", w.Bytes()) } } func TestResponseRawBodySet(t *testing.T) { t.Parallel() var resp Response expectedS := "test" body := []byte(expectedS) resp.SetBodyRaw(body) testBodyWriteTo(t, &resp, expectedS, true) } func TestRequestRawBodySet(t *testing.T) { t.Parallel() var r Request expectedS := "test" body := []byte(expectedS) r.SetBodyRaw(body) testBodyWriteTo(t, &r, expectedS, true) } func TestResponseRawBodyReset(t *testing.T) { t.Parallel() var resp Response body := []byte("test") resp.SetBodyRaw(body) resp.ResetBody() testBodyWriteTo(t, &resp, "", true) } func TestRequestRawBodyReset(t *testing.T) { t.Parallel() var r Request body := []byte("test") r.SetBodyRaw(body) r.ResetBody() testBodyWriteTo(t, &r, "", true) } func TestResponseRawBodyCopyTo(t *testing.T) { t.Parallel() var resp Response expectedS := "test" body := []byte(expectedS) resp.SetBodyRaw(body) testResponseCopyTo(t, &resp) } func TestRequestRawBodyCopyTo(t *testing.T) { t.Parallel() var a Request body := []byte("test") a.SetBodyRaw(body) var b Request a.CopyTo(&b) testBodyWriteTo(t, &a, "test", true) testBodyWriteTo(t, &b, "test", true) } type testReader struct { read chan int cb chan struct{} onClose func() error } func (r *testReader) Read(b []byte) (int, error) { read := <-r.read if read == -1 { return 0, io.EOF } r.cb <- struct{}{} for i := 0; i < read; i++ { b[i] = 'x' } return read, nil } func (r *testReader) Close() error { if r.onClose != nil { return r.onClose() } return nil } func TestResponseImmediateHeaderFlushRegressionFixedLength(t *testing.T) { t.Parallel() var r Response expectedS := "aaabbbccc" buf := bytes.NewBufferString(expectedS) r.SetBodyStream(buf, len(expectedS)) r.ImmediateHeaderFlush = true testBodyWriteTo(t, &r, expectedS, false) } func TestResponseImmediateHeaderFlushRegressionChunked(t *testing.T) { t.Parallel() var r Response expectedS := "aaabbbccc" buf := bytes.NewBufferString(expectedS) r.SetBodyStream(buf, -1) r.ImmediateHeaderFlush = true testBodyWriteTo(t, &r, expectedS, false) } func TestResponseImmediateHeaderFlushFixedLength(t *testing.T) { t.Parallel() var r Response r.ImmediateHeaderFlush = true ch := make(chan int) cb := make(chan struct{}) buf := &testReader{read: ch, cb: cb} r.SetBodyStream(buf, 3) w := &bytes.Buffer{} bb := bufio.NewWriter(w) bw := &r waitForIt := make(chan struct{}) go func() { if err := bw.Write(bb); err != nil { t.Errorf("unexpected error: %v", err) } waitForIt <- struct{}{} }() ch <- 3 if !strings.Contains(w.String(), "Content-Length: 3") { t.Fatalf("Expected headers to be flushed") } if strings.Contains(w.String(), "xxx") { t.Fatalf("Did not expect body to be written yet") } <-cb ch <- -1 <-waitForIt } func TestResponseImmediateHeaderFlushFixedLengthSkipBody(t *testing.T) { t.Parallel() var r Response r.ImmediateHeaderFlush = true r.SkipBody = true ch := make(chan int) cb := make(chan struct{}) buf := &testReader{read: ch, cb: cb} r.SetBodyStream(buf, 0) w := &bytes.Buffer{} bb := bufio.NewWriter(w) var headersOnClose string buf.onClose = func() error { headersOnClose = w.String() return nil } bw := &r if err := bw.Write(bb); err != nil { t.Errorf("unexpected error: %v", err) } if !strings.Contains(headersOnClose, "Content-Length: 0") { t.Fatalf("Expected headers to be eagerly flushed") } } func TestResponseImmediateHeaderFlushChunked(t *testing.T) { t.Parallel() var r Response r.ImmediateHeaderFlush = true ch := make(chan int) cb := make(chan struct{}) buf := &testReader{read: ch, cb: cb} r.SetBodyStream(buf, -1) w := &bytes.Buffer{} bb := bufio.NewWriter(w) bw := &r waitForIt := make(chan struct{}) go func() { if err := bw.Write(bb); err != nil { t.Errorf("unexpected error: %v", err) } waitForIt <- struct{}{} }() ch <- 3 if !strings.Contains(w.String(), "Transfer-Encoding: chunked") { t.Fatalf("Expected headers to be flushed") } if strings.Contains(w.String(), "xxx") { t.Fatalf("Did not expect body to be written yet") } <-cb ch <- -1 <-waitForIt } func TestResponseImmediateHeaderFlushChunkedNoBody(t *testing.T) { t.Parallel() var r Response r.ImmediateHeaderFlush = true r.SkipBody = true ch := make(chan int) cb := make(chan struct{}) buf := &testReader{read: ch, cb: cb} r.SetBodyStream(buf, -1) w := &bytes.Buffer{} bb := bufio.NewWriter(w) var headersOnClose string buf.onClose = func() error { headersOnClose = w.String() return nil } bw := &r if err := bw.Write(bb); err != nil { t.Errorf("unexpected error: %v", err) } if !strings.Contains(headersOnClose, "Transfer-Encoding: chunked") { t.Fatalf("Expected headers to be eagerly flushed") } } type ErroneousBodyStream struct { errOnRead bool errOnClose bool } func (ebs *ErroneousBodyStream) Read(p []byte) (n int, err error) { if ebs.errOnRead { panic("reading erroneous body stream") } return 0, io.EOF } func (ebs *ErroneousBodyStream) Close() error { if ebs.errOnClose { panic("closing erroneous body stream") } return nil } func TestResponseBodyStreamErrorOnPanicDuringRead(t *testing.T) { t.Parallel() var resp Response var w bytes.Buffer bw := bufio.NewWriter(&w) ebs := &ErroneousBodyStream{errOnRead: true, errOnClose: false} resp.SetBodyStream(ebs, 42) err := resp.Write(bw) if err == nil { t.Fatalf("expected error when writing response.") } e, ok := err.(*ErrBodyStreamWritePanic) if !ok { t.Fatalf("expected error struct to be *ErrBodyStreamWritePanic, got: %+v.", e) } if e.Error() != "panic while writing body stream: reading erroneous body stream" { t.Fatalf("unexpected error value, got: %+v.", e.Error()) } } func TestResponseBodyStreamErrorOnPanicDuringClose(t *testing.T) { t.Parallel() var resp Response var w bytes.Buffer bw := bufio.NewWriter(&w) ebs := &ErroneousBodyStream{errOnRead: false, errOnClose: true} resp.SetBodyStream(ebs, 42) err := resp.Write(bw) if err == nil { t.Fatalf("expected error when writing response.") } e, ok := err.(*ErrBodyStreamWritePanic) if !ok { t.Fatalf("expected error struct to be *ErrBodyStreamWritePanic, got: %+v.", e) } if e.Error() != "panic while writing body stream: closing erroneous body stream" { t.Fatalf("unexpected error value, got: %+v.", e.Error()) } } func TestResponseBodyStream(t *testing.T) { t.Parallel() chunkedResp := "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "6\r\n123456\r\n" + "7\r\n1234567\r\n" + "0\r\n\r\n" simpleResp := "HTTP/1.1 200 OK\r\n" + "Content-Length: 9\r\n" + "\r\n" + "123456789" t.Run("read chunked response", func(t *testing.T) { response := AcquireResponse() response.StreamBody = true if err := response.Read(bufio.NewReader(bytes.NewBufferString(chunkedResp))); err != nil { t.Fatalf("parse response find err: %v", err) } defer func() { if err := response.closeBodyStream(nil); err != nil { t.Fatalf("close body stream err: %v", err) } }() body, err := io.ReadAll(response.bodyStream) if err != nil { t.Fatalf("read body stream err: %v", err) } if string(body) != "1234561234567" { t.Fatalf("unexpected body content, got: %#v, want: %#v", string(body), "1234561234567") } }) t.Run("read simple response", func(t *testing.T) { resp := AcquireResponse() resp.StreamBody = true err := resp.ReadLimitBody(bufio.NewReader(bytes.NewBufferString(simpleResp)), 8) if err != nil { t.Fatalf("read limit body err: %v", err) } body := resp.BodyStream() defer func() { if err := resp.CloseBodyStream(); err != nil { t.Fatalf("close body stream err: %v", err) } }() content, err := io.ReadAll(body) if err != nil { t.Fatalf("read limit body err: %v", err) } if string(content) != "123456789" { t.Fatalf("unexpected body content, got: %#v, want: %#v", string(content), "123456789") } }) t.Run("http client", func(t *testing.T) { t.Parallel() server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { if request.URL.Query().Get("chunked") == "true" { for x := 0; x < 10; x++ { time.Sleep(time.Millisecond) writer.Write([]byte(strconv.Itoa(x))) //nolint:errcheck writer.(http.Flusher).Flush() } return } writer.Write([]byte(`hello world`)) //nolint:errcheck })) t.Cleanup(server.Close) t.Run("normal request", func(t *testing.T) { t.Parallel() client := Client{StreamResponseBody: true} resp := AcquireResponse() request := AcquireRequest() request.SetRequestURI(server.URL) if err := client.Do(request, resp); err != nil { t.Fatal(err) } stream := resp.BodyStream() defer func() { ReleaseResponse(resp) }() content, _ := io.ReadAll(stream) if string(content) != "hello world" { t.Fatalf("unexpected body content, got: %#v, want: %#v", string(content), "hello world") } }) t.Run("limit response body size", func(t *testing.T) { t.Parallel() client := Client{StreamResponseBody: true, MaxResponseBodySize: 20} resp := AcquireResponse() request := AcquireRequest() request.SetRequestURI(server.URL) if err := client.Do(request, resp); err != nil { t.Fatal(err) } stream := resp.BodyStream() defer func() { if err := resp.CloseBodyStream(); err != nil { t.Fatalf("close stream err: %v", err) } }() content, _ := io.ReadAll(stream) if string(content) != "hello world" { t.Fatalf("unexpected body content, got: %#v, want: %#v", string(content), "hello world") } }) t.Run("chunked", func(t *testing.T) { t.Parallel() client := Client{StreamResponseBody: true} resp := AcquireResponse() request := AcquireRequest() request.SetRequestURI(server.URL + "?chunked=true") if err := client.Do(request, resp); err != nil { t.Fatal(err) } stream := resp.BodyStream() defer func() { if err := resp.CloseBodyStream(); err != nil { t.Fatalf("close stream err: %v", err) } }() content, _ := io.ReadAll(stream) if string(content) != "0123456789" { t.Fatalf("unexpected body content, got: %#v, want: %#v", string(content), "0123456789") } }) }) } func TestRequestMultipartFormPipeEmptyFormField(t *testing.T) { t.Parallel() pr, pw := io.Pipe() mw := multipart.NewWriter(pw) errs := make(chan error, 1) go func() { defer func() { err := mw.Close() if err != nil { errs <- err } err = pw.Close() if err != nil { errs <- err } close(errs) }() if err := mw.WriteField("emptyField", ""); err != nil { errs <- err } }() var b bytes.Buffer bw := bufio.NewWriter(&b) err := writeBodyChunked(bw, pr) if err != nil { t.Fatalf("unexpected error: %v", err) } for e := range errs { t.Fatalf("unexpected error in goroutine multiwriter: %v", e) } testRequestMultipartFormPipeEmptyFormField(t, mw.Boundary(), b.Bytes(), 1) } func testRequestMultipartFormPipeEmptyFormField(t *testing.T, boundary string, formData []byte, partsCount int) []byte { s := fmt.Sprintf("POST / HTTP/1.1\r\nHost: aaa\r\nContent-Type: multipart/form-data; boundary=%s\r\nTransfer-Encoding: chunked\r\n\r\n%s", boundary, formData) var req Request r := bytes.NewBufferString(s) br := bufio.NewReader(r) if err := req.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } f, err := req.MultipartForm() if err != nil { t.Fatalf("unexpected error: %v", err) } defer req.RemoveMultipartFormFiles() if len(f.File) > 0 { t.Fatalf("unexpected files found in the multipart form: %d", len(f.File)) } if len(f.Value) != partsCount { t.Fatalf("unexpected number of values found: %d. Expecting %d", len(f.Value), partsCount) } for k, vv := range f.Value { if len(vv) != 1 { t.Fatalf("unexpected number of values found for key=%q: %d. Expecting 1", k, len(vv)) } if k != "emptyField" { t.Fatalf("unexpected key=%q. Expecting %q", k, "emptyField") } v := vv[0] if v != "" { t.Fatalf("unexpected value=%q. expecting %q", v, "") } } return req.Body() } func TestReqCopeToRace(t *testing.T) { req := AcquireRequest() reqs := make([]*Request, 1000) for i := 0; i < 1000; i++ { req.SetBodyRaw([]byte(strconv.Itoa(i))) tmpReq := AcquireRequest() req.CopyTo(tmpReq) reqs[i] = tmpReq } for i := 0; i < 1000; i++ { if strconv.Itoa(i) != string(reqs[i].Body()) { t.Fatalf("Unexpected req body %s. Expected %s", string(reqs[i].Body()), strconv.Itoa(i)) } } } func TestRespCopeToRace(t *testing.T) { resp := AcquireResponse() resps := make([]*Response, 1000) for i := 0; i < 1000; i++ { resp.SetBodyRaw([]byte(strconv.Itoa(i))) tmpResq := AcquireResponse() resp.CopyTo(tmpResq) resps[i] = tmpResq } for i := 0; i < 1000; i++ { if strconv.Itoa(i) != string(resps[i].Body()) { t.Fatalf("Unexpected resp body %s. Expected %s", string(resps[i].Body()), strconv.Itoa(i)) } } } func TestRequestGetTimeOut(t *testing.T) { tests := []struct { name string timeout time.Duration expected time.Duration }{ {"Timeout set to 0", 0, 0}, {"Timeout set to 5s", 5 * time.Second, 5 * time.Second}, {"Timeout set to 1m", 1 * time.Minute, 1 * time.Minute}, {"Timeout set to 500ms", 500 * time.Millisecond, 500 * time.Millisecond}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { req := &Request{timeout: test.timeout} if got := req.GetTimeOut(); got != test.expected { t.Errorf("GetTimeOut() = %v, want %v", got, test.expected) } }) } }