mirror of
https://github.com/valyala/fasthttp.git
synced 2026-06-14 15:56:44 +03:00
fa2b76590b
Clear the response body stream when the client streaming wrapper is closed directly via CloseWithError. This prevents ReleaseResponse from closing the same underlying requestStream again.
3755 lines
98 KiB
Go
3755 lines
98 KiB
Go
package fasthttp
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/valyala/bytebufferpool"
|
|
)
|
|
|
|
func TestInvalidTrailers(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if err := (&Response{}).Read(bufio.NewReader(strings.NewReader("HTTP/1.1 200\r\nTransfer-Encoding:\xff\n\n0\r\n0"))); !errors.Is(err, io.EOF) {
|
|
t.Errorf("%#v", err)
|
|
}
|
|
if err := (&Response{}).Read(bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\nTRaILeR:,\r\n\r\n"))); !errors.Is(err, ErrBadTrailer) {
|
|
t.Error(err)
|
|
}
|
|
if err := (&Response{}).Read(bufio.NewReader(strings.NewReader("TRaILeR:,\r\n\r\n"))); !strings.Contains(err.Error(), "cannot find whitespace in the first line of response") {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
func TestResponseEmptyTransferEncodingError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var r Response
|
|
|
|
response := "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nTransfer-Encoding: \r\nContent-Length: 9\r\n\r\nSome body"
|
|
br := bufio.NewReader(bytes.NewBufferString(response))
|
|
if err := r.Read(br); err == nil {
|
|
t.Fatal("expected an error")
|
|
}
|
|
expectNetHTTPReadResponseError(t, response, "net/http accepted response with empty Transfer-Encoding")
|
|
}
|
|
|
|
func expectNetHTTPReadResponseError(t *testing.T, response, msg string) {
|
|
t.Helper()
|
|
|
|
resp, err := http.ReadResponse(bufio.NewReader(strings.NewReader(response)), nil)
|
|
if resp != nil && resp.Body != nil {
|
|
if closeErr := resp.Body.Close(); closeErr != nil {
|
|
t.Fatalf("cannot close response body: %v", closeErr)
|
|
}
|
|
}
|
|
if err == nil {
|
|
t.Fatal(msg)
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
|
|
// Compare serialized representations.
|
|
if src.String() != dst.String() || !bytes.Equal(src.Body(), dst.Body()) {
|
|
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 TestBodyDecodeWithLimitTooLarge(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
body := bytes.Repeat([]byte("a"), 2*1024)
|
|
maxBodySize := 1024
|
|
|
|
testCases := []struct {
|
|
name string
|
|
encoding string
|
|
encode func([]byte) []byte
|
|
}{
|
|
{
|
|
name: "gzip",
|
|
encoding: "gzip",
|
|
encode: func(src []byte) []byte {
|
|
return AppendGzipBytes(nil, src)
|
|
},
|
|
},
|
|
{
|
|
name: "deflate",
|
|
encoding: "deflate",
|
|
encode: func(src []byte) []byte {
|
|
return AppendDeflateBytes(nil, src)
|
|
},
|
|
},
|
|
{
|
|
name: "brotli",
|
|
encoding: "br",
|
|
encode: func(src []byte) []byte {
|
|
return AppendBrotliBytes(nil, src)
|
|
},
|
|
},
|
|
{
|
|
name: "zstd",
|
|
encoding: "zstd",
|
|
encode: func(src []byte) []byte {
|
|
return AppendZstdBytes(nil, src)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.name+"_request_uncompressed", func(t *testing.T) {
|
|
var req Request
|
|
req.Header.SetContentEncoding(testCase.encoding)
|
|
req.SetBodyRaw(testCase.encode(body))
|
|
_, err := req.BodyUncompressedWithLimit(maxBodySize)
|
|
if !errors.Is(err, ErrBodyTooLarge) {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run(testCase.name+"_response_uncompressed", func(t *testing.T) {
|
|
var resp Response
|
|
resp.Header.SetContentEncoding(testCase.encoding)
|
|
resp.SetBodyRaw(testCase.encode(body))
|
|
_, err := resp.BodyUncompressedWithLimit(maxBodySize)
|
|
if !errors.Is(err, ErrBodyTooLarge) {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRequestMultipartFormWithLimitGzip(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var formBodyBuffer bytes.Buffer
|
|
mw := multipart.NewWriter(&formBodyBuffer)
|
|
if err := mw.WriteField("foo", strings.Repeat("a", 8*1024)); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
boundary := mw.Boundary()
|
|
if err := mw.Close(); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
formBody := formBodyBuffer.Bytes()
|
|
gzippedBody := AppendGzipBytes(nil, formBody)
|
|
|
|
t.Run("buffered_too_large", func(t *testing.T) {
|
|
var req Request
|
|
req.Header.SetMultipartFormBoundary(boundary)
|
|
req.Header.SetContentEncoding("gzip")
|
|
req.SetBodyRaw(gzippedBody)
|
|
|
|
_, err := req.MultipartFormWithLimit(len(formBody) - 1)
|
|
if !errors.Is(err, ErrBodyTooLarge) {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("streamed_too_large", func(t *testing.T) {
|
|
var req Request
|
|
req.Header.SetMultipartFormBoundary(boundary)
|
|
req.Header.SetContentEncoding("gzip")
|
|
req.SetBodyStream(bytes.NewReader(gzippedBody), len(gzippedBody))
|
|
|
|
_, err := req.MultipartFormWithLimit(len(formBody) - 1)
|
|
if !errors.Is(err, ErrBodyTooLarge) {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("buffered_success", func(t *testing.T) {
|
|
var req Request
|
|
req.Header.SetMultipartFormBoundary(boundary)
|
|
req.Header.SetContentEncoding("gzip")
|
|
req.SetBodyRaw(gzippedBody)
|
|
|
|
f, err := req.MultipartFormWithLimit(len(formBody))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
defer req.RemoveMultipartFormFiles()
|
|
|
|
vv := f.Value["foo"]
|
|
if len(vv) != 1 {
|
|
t.Fatalf("unexpected values count: %d", len(vv))
|
|
}
|
|
if vv[0] != strings.Repeat("a", 8*1024) {
|
|
t.Fatalf("unexpected value length: %d", len(vv[0]))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestResponseSwapBodySerial(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testResponseSwapBody(t)
|
|
}
|
|
|
|
func TestResponseSwapBodyConcurrent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ch := make(chan struct{})
|
|
for range 10 {
|
|
go func() {
|
|
testResponseSwapBody(t)
|
|
ch <- struct{}{}
|
|
}()
|
|
}
|
|
|
|
for range 10 {
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
}
|
|
|
|
func testResponseSwapBody(t *testing.T) {
|
|
var b []byte
|
|
r := AcquireResponse()
|
|
for range 20 {
|
|
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 range 10 {
|
|
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 range 10 {
|
|
go func() {
|
|
testRequestSwapBody(t)
|
|
ch <- struct{}{}
|
|
}()
|
|
}
|
|
|
|
for range 10 {
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
}
|
|
|
|
func testRequestSwapBody(t *testing.T) {
|
|
var b []byte
|
|
r := AcquireRequest()
|
|
for range 20 {
|
|
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 range 10 {
|
|
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\nHost: example.com\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()
|
|
|
|
b := strings.ReplaceAll(`------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--
|
|
`, "\n", "\r\n")
|
|
|
|
s := fmt.Sprintf(strings.ReplaceAll(`POST /upload HTTP/1.1
|
|
Host: localhost:10000
|
|
Content-Length: %d
|
|
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg
|
|
|
|
%stailfoobar`, "\n", "\r\n"), len(b), b)
|
|
|
|
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 := range 10 {
|
|
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 := range 10 {
|
|
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\nHost: foobar\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\nHost: example.com\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\nHost: example.com\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\nHost: example.com\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 := range 10 {
|
|
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 := range 10 {
|
|
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 range 5 {
|
|
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)
|
|
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)
|
|
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)
|
|
|
|
// missing Host header is invalid in HTTP/1.1, but still allowed in HTTP/1.0
|
|
testRequestReadLimitBodyError(t, "GET /foo HTTP/1.1\r\n\r\n", 0, errRequestHostRequired)
|
|
testRequestReadLimitBodySuccess(t, "GET /foo HTTP/1.0\r\n\r\n", 0)
|
|
}
|
|
|
|
func TestRequestReadLimitBodyContentLengthAndTransferEncoding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []string{
|
|
"POST /foo HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 1\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ntest\r\n0\r\n\r\nNEXT",
|
|
"POST /foo HTTP/1.1\r\nHost: aaa.com\r\nTransfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n4\r\ntest\r\n0\r\n\r\nNEXT",
|
|
}
|
|
|
|
for _, s := range tests {
|
|
var req Request
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := req.ReadLimitBody(br, 0); err != nil {
|
|
t.Fatalf("unexpected error: %v. s=%q", err, s)
|
|
}
|
|
if body := string(req.Body()); body != "test" {
|
|
t.Fatalf("unexpected body %q. Expecting %q. s=%q", body, "test", s)
|
|
}
|
|
b, err := br.Peek(4)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error reading remaining bytes: %v. s=%q", err, s)
|
|
}
|
|
if string(b) != "NEXT" {
|
|
t.Fatalf("unexpected remaining bytes %q. Expecting %q. s=%q", b, "NEXT", s)
|
|
}
|
|
}
|
|
|
|
testRequestReadLimitBodyError(t, "POST /foo HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 1nope\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", 0, ErrNonNumericChars)
|
|
testRequestReadLimitBodyError(t, "POST /foo HTTP/1.1\r\nHost: aaa.com\r\nTransfer-Encoding: chunked\r\nContent-Length: 1nope\r\n\r\n0\r\n\r\n", 0, ErrNonNumericChars)
|
|
}
|
|
|
|
func TestRequestReadLimitBodyRejectWhitespaceBeforeColonFramingHeaders(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []string{
|
|
"POST /foo HTTP/1.1\r\nHost: a.com\r\nContent-Length : 4\r\n\r\ntestNEXT",
|
|
"POST /foo HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding : chunked\r\n\r\n4\r\ntest\r\n0\r\n\r\n",
|
|
}
|
|
|
|
for _, s := range tests {
|
|
var req Request
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := req.ReadLimitBody(br, 10); err == nil {
|
|
t.Fatalf("expecting error for %q", s)
|
|
}
|
|
if body := req.Body(); len(body) != 0 {
|
|
t.Fatalf("unexpected body %q for %q", body, s)
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
t.Helper()
|
|
|
|
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) {
|
|
t.Helper()
|
|
|
|
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 !errors.Is(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) {
|
|
t.Helper()
|
|
|
|
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 := range 10 {
|
|
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 := range 10 {
|
|
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\nTrailer: Trail\r\n\r\n3\r\nabc\r\n5\r\n12345\r\n0\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, &req.Header, map[string]string{"Trail": "test"})
|
|
}
|
|
|
|
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 TestParseChunkSize(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
line string
|
|
size int
|
|
}{
|
|
{
|
|
name: "without extension",
|
|
line: "3\r\n",
|
|
size: 3,
|
|
},
|
|
{
|
|
name: "with extension",
|
|
line: "3;ext\r\n",
|
|
size: 3,
|
|
},
|
|
{
|
|
name: "trailing space",
|
|
line: "3 \r\n",
|
|
size: 3,
|
|
},
|
|
{
|
|
name: "trailing tab",
|
|
line: "3\t\r\n",
|
|
size: 3,
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rb := bufio.NewReader(bytes.NewBufferString(test.line))
|
|
size, err := parseChunkSize(rb)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error when reading chunk size %q: %v", test.line, err)
|
|
}
|
|
if size != test.size {
|
|
t.Fatalf("unexpected chunk size %d. Expected %d", size, test.size)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseChunkSizeWhitespaceError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, test := range []string{
|
|
"3 ;ext\r\n",
|
|
"3\t;ext\r\n",
|
|
} {
|
|
t.Run(fmt.Sprintf("%q", test), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rb := bufio.NewReader(bytes.NewBufferString(test))
|
|
if _, err := parseChunkSize(rb); err == nil {
|
|
t.Fatalf("expecting error when reading chunk size %q", test)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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/1.1 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 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 TestResponseReadUnsupportedTransferEncoding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
response string
|
|
}{
|
|
{
|
|
name: "deflate",
|
|
response: "HTTP/1.1 200 OK\r\nTransfer-Encoding: deflate\r\nContent-Length: 5\r\n\r\nABCDE",
|
|
},
|
|
{
|
|
name: "identity",
|
|
response: "HTTP/1.1 200 OK\r\nTransfer-Encoding: identity\r\nContent-Length: 5\r\n\r\nABCDE",
|
|
},
|
|
{
|
|
name: "compound",
|
|
response: "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked, gzip\r\n\r\n0\r\n\r\n",
|
|
},
|
|
{
|
|
name: "duplicate",
|
|
response: "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var resp Response
|
|
if err := resp.Read(bufio.NewReader(strings.NewReader(tt.response))); err == nil {
|
|
t.Fatal("expected an error")
|
|
}
|
|
expectNetHTTPReadResponseError(t, tt.response, "net/http accepted response")
|
|
})
|
|
}
|
|
}
|
|
|
|
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")
|
|
|
|
testResponseReadError(t, resp, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Length: nope\r\n\r\n0\r\n\r\n")
|
|
}
|
|
|
|
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))
|
|
}
|
|
verifyTrailer(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)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
func createFixedBody(bodySize int) []byte {
|
|
b := make([]byte, 0, bodySize)
|
|
for i := range bodySize {
|
|
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, fmt.Appendf(nil, "%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 := range read {
|
|
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 := range 10 {
|
|
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")
|
|
}
|
|
})
|
|
|
|
t.Run("direct close with error releases client stream", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := Client{StreamResponseBody: true, MaxConnsPerHost: 1}
|
|
resp := AcquireResponse()
|
|
defer ReleaseResponse(resp)
|
|
request := AcquireRequest()
|
|
defer ReleaseRequest(request)
|
|
request.SetRequestURI(server.URL + "?chunked=true")
|
|
if err := client.Do(request, resp); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stream, ok := resp.BodyStream().(ReadCloserWithError)
|
|
if !ok {
|
|
t.Fatalf("unexpected body stream type %T", resp.BodyStream())
|
|
}
|
|
if err := stream.CloseWithError(errors.New("client closed")); err != nil {
|
|
t.Fatalf("close body stream err: %v", err)
|
|
}
|
|
if err := stream.CloseWithError(errors.New("client closed again")); err != nil {
|
|
t.Fatalf("close body stream twice err: %v", err)
|
|
}
|
|
|
|
resp2 := AcquireResponse()
|
|
defer ReleaseResponse(resp2)
|
|
request2 := AcquireRequest()
|
|
defer ReleaseRequest(request2)
|
|
request2.SetRequestURI(server.URL)
|
|
if err := client.Do(request2, resp2); err != nil {
|
|
t.Fatalf("second request err: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("compressed write releases client stream", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := Client{StreamResponseBody: true, MaxConnsPerHost: 1}
|
|
|
|
resp := AcquireResponse()
|
|
defer ReleaseResponse(resp)
|
|
request := AcquireRequest()
|
|
defer ReleaseRequest(request)
|
|
|
|
request.SetRequestURI(server.URL)
|
|
if err := client.Do(request, resp); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bw := bufio.NewWriter(io.Discard)
|
|
if err := resp.WriteGzip(bw); err != nil {
|
|
t.Fatalf("write gzip response err: %v", err)
|
|
}
|
|
if err := bw.Flush(); err != nil {
|
|
t.Fatalf("flush gzip response err: %v", err)
|
|
}
|
|
|
|
resp2 := AcquireResponse()
|
|
defer ReleaseResponse(resp2)
|
|
request2 := AcquireRequest()
|
|
defer ReleaseRequest(request2)
|
|
|
|
request2.SetRequestURI(server.URL)
|
|
if err := client.Do(request2, resp2); err != nil {
|
|
t.Fatalf("second request err: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("identity", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := Client{StreamResponseBody: true, DisablePathNormalizing: true}
|
|
resp := AcquireResponse()
|
|
request := AcquireRequest()
|
|
request.SetRequestURI(server.URL + "?400BadRequest")
|
|
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, err := io.ReadAll(stream)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(content) != "400 Bad Request" {
|
|
t.Fatalf("unexpected body content, got: %#v, want: %#v", string(content), "400 Bad Request")
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestResponseCompressedBodyStreamCloseClosesOriginal(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
bodyStream := &blockingCloseReader{
|
|
closed: make(chan struct{}),
|
|
}
|
|
|
|
var resp Response
|
|
resp.Header.SetContentType("text/plain")
|
|
resp.SetBodyStream(bodyStream, -1)
|
|
resp.gzipBody(CompressDefaultCompression)
|
|
|
|
if err := resp.CloseBodyStream(); err != nil {
|
|
t.Fatalf("unexpected close error: %v", err)
|
|
}
|
|
|
|
select {
|
|
case <-bodyStream.closed:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout waiting for original body stream close")
|
|
}
|
|
}
|
|
|
|
func TestResponseCompressedBodyStreamCloseKeepsWriteError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
readCh := make(chan struct{})
|
|
closeErrCh := make(chan error, 1)
|
|
bodyStream := newCloseReaderWithError(&gatedReader{
|
|
readCh: readCh,
|
|
data: []byte(strings.Repeat("body", 1024)),
|
|
}, func(err error) error {
|
|
closeErrCh <- err
|
|
return nil
|
|
})
|
|
|
|
var resp Response
|
|
resp.Header.SetContentType("text/plain")
|
|
resp.SetBodyStream(bodyStream, -1)
|
|
resp.gzipBody(CompressDefaultCompression)
|
|
|
|
if err := resp.CloseBodyStream(); err != nil {
|
|
t.Fatalf("unexpected close error: %v", err)
|
|
}
|
|
close(readCh)
|
|
|
|
select {
|
|
case err := <-closeErrCh:
|
|
if err == nil {
|
|
t.Fatalf("unexpected nil close error")
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout waiting for original body stream close")
|
|
}
|
|
}
|
|
|
|
func TestResponseCompressedBodyStreamCloseDoesNotReleaseRequestStreamBeforeReadDone(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
reader := &blockingOnceReader{
|
|
reading: make(chan struct{}),
|
|
unblock: make(chan struct{}),
|
|
}
|
|
|
|
var bodyBuf bytebufferpool.ByteBuffer
|
|
rs := acquireRequestStream(&bodyBuf, bufio.NewReader(reader), fixedRequestStreamHeader{contentLength: 1})
|
|
|
|
var resp Response
|
|
resp.Header.SetContentType("text/plain")
|
|
resp.SetBodyStream(rs, -1)
|
|
resp.gzipBody(CompressDefaultCompression)
|
|
compressedStream := resp.bodyStream.(*compressedBodyStream)
|
|
|
|
select {
|
|
case <-reader.reading:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout waiting for request stream read")
|
|
}
|
|
|
|
if err := resp.CloseBodyStream(); err != nil {
|
|
t.Fatalf("unexpected close error: %v", err)
|
|
}
|
|
if rs.reader == nil {
|
|
t.Fatalf("request stream was released while compression was still reading it")
|
|
}
|
|
|
|
close(reader.unblock)
|
|
select {
|
|
case <-compressedStream.done:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout waiting for compressed stream cleanup")
|
|
}
|
|
if rs.reader != nil {
|
|
t.Fatalf("request stream was not released after compression finished")
|
|
}
|
|
}
|
|
|
|
type blockingCloseReader struct {
|
|
closeOnce sync.Once
|
|
closed chan struct{}
|
|
}
|
|
|
|
func (r *blockingCloseReader) Read(p []byte) (int, error) {
|
|
<-r.closed
|
|
return 0, io.EOF
|
|
}
|
|
|
|
func (r *blockingCloseReader) Close() error {
|
|
r.closeOnce.Do(func() {
|
|
close(r.closed)
|
|
})
|
|
return nil
|
|
}
|
|
|
|
type gatedReader struct {
|
|
readCh <-chan struct{}
|
|
data []byte
|
|
done bool
|
|
}
|
|
|
|
func (r *gatedReader) Read(p []byte) (int, error) {
|
|
<-r.readCh
|
|
if r.done {
|
|
return 0, io.EOF
|
|
}
|
|
r.done = true
|
|
return copy(p, r.data), nil
|
|
}
|
|
|
|
type fixedRequestStreamHeader struct {
|
|
contentLength int
|
|
}
|
|
|
|
func (h fixedRequestStreamHeader) ContentLength() int {
|
|
return h.contentLength
|
|
}
|
|
|
|
func (fixedRequestStreamHeader) ReadTrailer(r *bufio.Reader) error {
|
|
return nil
|
|
}
|
|
|
|
type blockingOnceReader struct {
|
|
reading chan struct{}
|
|
unblock chan struct{}
|
|
readOnce sync.Once
|
|
valueOnce sync.Once
|
|
}
|
|
|
|
func (r *blockingOnceReader) Read(p []byte) (int, error) {
|
|
r.readOnce.Do(func() {
|
|
close(r.reading)
|
|
})
|
|
<-r.unblock
|
|
|
|
wrote := false
|
|
r.valueOnce.Do(func() {
|
|
p[0] = 'x'
|
|
wrote = true
|
|
})
|
|
if !wrote {
|
|
return 0, io.EOF
|
|
}
|
|
return 1, nil
|
|
}
|
|
|
|
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)
|
|
}
|
|
if _, err = bw.Write(strCRLF); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if err = bw.Flush(); 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 := range 1000 {
|
|
req.SetBodyRaw([]byte(strconv.Itoa(i)))
|
|
tmpReq := AcquireRequest()
|
|
req.CopyTo(tmpReq)
|
|
reqs[i] = tmpReq
|
|
}
|
|
for i := range 1000 {
|
|
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 := range 1000 {
|
|
resp.SetBodyRaw([]byte(strconv.Itoa(i)))
|
|
tmpResq := AcquireResponse()
|
|
resp.CopyTo(tmpResq)
|
|
resps[i] = tmpResq
|
|
}
|
|
for i := range 1000 {
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|