diff --git a/httpc/client.go b/httpc/client.go index 7cc2540..1b48f4e 100644 --- a/httpc/client.go +++ b/httpc/client.go @@ -88,11 +88,19 @@ func New(cfg Config) (*Client, error) { // consumed. Callers own response.Body. Only GET, HEAD and OPTIONS are retried, // and only when their bodies can be replayed. MaxRetries=0 disables retries. func (c *Client) Do(request *http.Request) (*http.Response, error) { + var requestError error if c.closed.Load() { - return nil, ErrClosed + requestError = ErrClosed + } else if request == nil || request.URL == nil { + requestError = errors.New("httpc: nil request or URL") } - if request == nil || request.URL == nil { - return nil, errors.New("httpc: nil request or URL") + if requestError != nil { + // Match http.Client.Do's ownership rule even when rejection happens + // before the underlying transport takes ownership of the body. + if request != nil && request.Body != nil { + _ = request.Body.Close() + } + return nil, requestError } ctx, cancel := context.WithTimeout(request.Context(), c.config.Timeout) base := request.Clone(ctx) diff --git a/httpc/client_test.go b/httpc/client_test.go index 4ccd644..c49ea47 100644 --- a/httpc/client_test.go +++ b/httpc/client_test.go @@ -15,6 +15,31 @@ import ( "git.yuntuo.hk/k2safe/go-omni/logx" ) +type trackedBody struct { + io.Reader + closed bool +} + +func (b *trackedBody) Close() error { b.closed = true; return nil } + +func TestRejectedRequestClosesBody(t *testing.T) { + for _, closed := range []bool{false, true} { + client, _ := New(Config{DisableLogging: true}) + body := &trackedBody{Reader: strings.NewReader("body")} + request, _ := http.NewRequest("POST", "http://unused.invalid", body) + if closed { + client.Close() + } else { + request.URL = nil + } + _, err := client.Do(request) + client.Close() + if err == nil || !body.closed { + t.Fatalf("error=%v bodyClosed=%v", err, body.closed) + } + } +} + func TestRetryCorrelationPoolAndWriteSafety(t *testing.T) { var calls, connections atomic.Int32 server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {