From 3b523b3dee97926f67d48043c23d9197b369e3c1 Mon Sep 17 00:00:00 2001 From: Erik Dubbelboer Date: Sat, 10 Aug 2024 13:08:25 +0200 Subject: [PATCH] A response without a body can't have trailers --- client.go | 4 +--- client_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ header_test.go | 2 ++ http.go | 3 ++- http_test.go | 17 +++++++++-------- 5 files changed, 63 insertions(+), 12 deletions(-) diff --git a/client.go b/client.go index 42a01d5dbb..ccc6557626 100644 --- a/client.go +++ b/client.go @@ -1365,9 +1365,7 @@ func (c *HostClient) do(req *Request, resp *Response) (bool, error) { defer ReleaseResponse(resp) } - ok, err := c.doNonNilReqResp(req, resp) - - return ok, err + return c.doNonNilReqResp(req, resp) } func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error) { diff --git a/client_test.go b/client_test.go index 13ae07ba15..19629fc259 100644 --- a/client_test.go +++ b/client_test.go @@ -3484,3 +3484,52 @@ func TestDialTimeout(t *testing.T) { }) } } + +func TestClientHeadWithBody(t *testing.T) { + t.Parallel() + + ln := fasthttputil.NewInmemoryListener() + defer ln.Close() + + go func() { + c, err := ln.Accept() + if err != nil { + t.Error(err) + } + c.Write([]byte("HTTP/1.1 200 OK\r\n" + //nolint:errcheck + "content-type: text/plain\r\n" + + "transfer-encoding: chunked\r\n\r\n" + + "24\r\nThis is the data in the first chunk \r\n" + + "1B\r\nand this is the second one \r\n" + + "0\r\n\r\n", + )) + }() + + c := &Client{ + Dial: func(addr string) (net.Conn, error) { + return ln.Dial() + }, + ReadTimeout: time.Millisecond * 10, + MaxIdemponentCallAttempts: 1, + } + + req := AcquireRequest() + req.SetRequestURI("http://127.0.0.1:7070") + req.Header.SetMethod(MethodHead) + + resp := AcquireResponse() + + err := c.Do(req, resp) + if err != nil { + t.Fatal(err) + } + + // The second request on the same connection is going to give a timeout as it can't find + // a proper request in what is left on the connection. + err = c.Do(req, resp) + if err == nil { + t.Error("expected timeout error") + } else if err != ErrTimeout { + t.Error(err) + } +} diff --git a/header_test.go b/header_test.go index 0b46cd8974..11b37a7121 100644 --- a/header_test.go +++ b/header_test.go @@ -2961,6 +2961,8 @@ func verifyRequestHeader(t *testing.T, h *RequestHeader, expectedContentLength i } func verifyResponseTrailer(t *testing.T, h *ResponseHeader, expectedTrailers map[string]string) { + t.Helper() + for k, v := range expectedTrailers { got := h.Peek(k) if !bytes.Equal(got, []byte(v)) { diff --git a/http.go b/http.go index 187f91d00f..853b9a7403 100644 --- a/http.go +++ b/http.go @@ -1446,7 +1446,8 @@ func (resp *Response) ReadLimitBody(r *bufio.Reader, maxBodySize int) error { } } - if resp.Header.ContentLength() == -1 && !resp.StreamBody { + // A response without a body can't have trailers. + if resp.Header.ContentLength() == -1 && !resp.StreamBody && !resp.mustSkipBody() { err = resp.Header.ReadTrailer(r) if err != nil && err != io.EOF { if isConnectionReset(err) { diff --git a/http_test.go b/http_test.go index a9f440a49f..86ceaae41f 100644 --- a/http_test.go +++ b/http_test.go @@ -2021,25 +2021,27 @@ func TestResponseReadWithoutBody(t *testing.T) { 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", nil) + 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\nFoo: bar\r\n\r\n", false, - 204, -1, "aab", map[string]string{"Foo": "bar"}) + 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", nil) + 123, 3434, "xxx") testResponseReadWithoutBody(t, &resp, "HTTP 200 OK\r\nContent-Type: text/xml\r\nContent-Length: 123\r\n\r\nfoobar\r\n", true, - 200, 123, "text/xml", nil) + 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", nil) + 329, 894, "qwe") } func testResponseReadWithoutBody(t *testing.T, resp *Response, s string, skipBody bool, - expectedStatusCode, expectedContentLength int, expectedContentType string, expectedTrailer map[string]string, + expectedStatusCode, expectedContentLength int, expectedContentType string, ) { + t.Helper() + r := bytes.NewBufferString(s) rb := bufio.NewReader(r) resp.SkipBody = skipBody @@ -2051,7 +2053,6 @@ func testResponseReadWithoutBody(t *testing.T, resp *Response, s string, skipBod t.Fatalf("Unexpected response body %q. Expected %q. response=%q", resp.Body(), "", s) } verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, "") - verifyResponseTrailer(t, &resp.Header, expectedTrailer) // verify that ordinal response is read after null-body response resp.SkipBody = false