Skip to content

Commit

Permalink
Read response when client closes connection valyala#1232
Browse files Browse the repository at this point in the history
  • Loading branch information
becheran committed Mar 3, 2022
1 parent 61aa8b1 commit 76a66e1
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 7 deletions.
14 changes: 7 additions & 7 deletions client.go
Expand Up @@ -13,6 +13,7 @@ import (
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
)

Expand Down Expand Up @@ -1427,12 +1428,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
Expand All @@ -1452,22 +1452,22 @@ 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)
} else {
c.releaseConn(cc)
}

return false, err
return false, nil
}

var (
Expand Down
55 changes: 55 additions & 0 deletions client_test.go
Expand Up @@ -6,7 +6,9 @@ import (
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"regexp"
Expand Down Expand Up @@ -2820,3 +2822,56 @@ func TestHostClientMaxConnWaitTimeoutWithEarlierDeadline(t *testing.T) {
t.Fatalf("at least one request body was empty")
}
}

// 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())
}
}

0 comments on commit 76a66e1

Please sign in to comment.