From 9ada70fab98ee6d8ab0336b6bfe3752e5aad9eb2 Mon Sep 17 00:00:00 2001 From: Armin Becher Date: Thu, 3 Mar 2022 09:37:57 +0100 Subject: [PATCH] Read response when client closes connection #1232 --- client.go | 14 ++++++------- client_test.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/client.go b/client.go index b36ca40824..9c4f290ffe 100644 --- a/client.go +++ b/client.go @@ -13,6 +13,7 @@ import ( "strings" "sync" "sync/atomic" + "syscall" "time" ) @@ -1437,12 +1438,11 @@ func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error) if err == nil { err = bw.Flush() } - if err != nil { - c.releaseWriter(bw) + c.releaseWriter(bw) + if err != nil && !errors.Is(err, syscall.ECONNRESET) { c.closeConn(cc) return true, err } - c.releaseWriter(bw) if c.ReadTimeout > 0 { // Set Deadline every time, since golang has fixed the performance issue @@ -1462,14 +1462,14 @@ func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error) } br := c.acquireReader(conn) - if err = resp.ReadLimitBody(br, c.MaxResponseBodySize); err != nil { - c.releaseReader(br) + err = resp.ReadLimitBody(br, c.MaxResponseBodySize) + c.releaseReader(br) + if err != nil && !errors.Is(err, syscall.ECONNRESET) { c.closeConn(cc) // Don't retry in case of ErrBodyTooLarge since we will just get the same again. retry := err != ErrBodyTooLarge return retry, err } - c.releaseReader(br) if resetConnection || req.ConnectionClose() || resp.ConnectionClose() { c.closeConn(cc) @@ -1477,7 +1477,7 @@ func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error) c.releaseConn(cc) } - return false, err + return false, nil } var ( diff --git a/client_test.go b/client_test.go index 78eda2e58e..aa771c8c5d 100644 --- a/client_test.go +++ b/client_test.go @@ -6,7 +6,9 @@ import ( "crypto/tls" "fmt" "io" + "io/ioutil" "net" + "net/http" "net/url" "os" "regexp" @@ -2840,3 +2842,56 @@ func TestHttpsRequestWithoutParsedURL(t *testing.T) { t.Fatalf("https requests with IsTLS client must succeed") } } + +// See issue #1232 +func TestRstConnResponseWhileSending(t *testing.T) { + const expectedStatus = http.StatusTeapot + const payload = "payload" + + srv, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err.Error()) + } + + go func() { + conn, err := srv.Accept() + if err != nil { + t.Errorf(err.Error()) + } + + // Read at least one byte of the header + // Otherwise we would have an unsolicited response + ioutil.ReadAll(io.LimitReader(conn, 1)) + + // Respond + conn.Write([]byte("HTTP/1.1 418 Teapot\r\n\r\n")) + + // Forcefully close connection + err = conn.(*net.TCPConn).SetLinger(0) + if err != nil { + t.Errorf(err.Error()) + } + conn.Close() + }() + + svrUrl := "http://" + srv.Addr().String() + + client := HostClient{Addr: srv.Addr().String()} + + req := AcquireRequest() + defer ReleaseRequest(req) + resp := AcquireResponse() + defer ReleaseResponse(resp) + + req.Header.SetMethod("POST") + req.SetBodyStream(strings.NewReader(payload), len(payload)) + req.SetRequestURI(svrUrl) + + err = client.Do(req, resp) + if err != nil { + t.Fatal(err.Error()) + } + if expectedStatus != resp.StatusCode() { + t.Fatalf("Expected %d status code, but got %d", expectedStatus, resp.StatusCode()) + } +}