-
Notifications
You must be signed in to change notification settings - Fork 321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: close http responses in a way to allow the Transport to re-use the TCP connection #2718
Conversation
Two concerns:
My suggestion is the following: Introducing this function: func DiscardBody(resp *http.Response) {
if resp != nil && resp.Body != nil {
_, _ = io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}
} Use this function when we don't plan to read the body. |
Explaining myself a bit further:
The biggest issue is that we can not enforce the usage of those functions. The ideal solution is to have a linter that requires you to read and close the body. |
I don't see any complication in always trying to read the request body just before closing it, even if it was already read before (resulting in a no-op). If this pattern is used across our codebase, it becomes easier for less senior members of the team to pick it up, copy-paste it and guard themselves from the inevitable complex branching logic which can result in not reading the body after all, even though this was the initial intention :) Edit: It is not uncommon either for us to care about reading an HTTP response's body in case of a successful |
You caught me! I found it smart though, not attempting to read larger bodies which would result in more time and resources being spent compared to simply opening a new tcp connection ;) |
That is a good point. I have no strong argument; both approaches help. I will wait for @fracasula feedback. |
bf79377
to
01775bc
Compare
Codecov ReportBase: 46.73% // Head: 46.87% // Increases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## master #2718 +/- ##
==========================================
+ Coverage 46.73% 46.87% +0.13%
==========================================
Files 300 300
Lines 49123 49109 -14
==========================================
+ Hits 22958 23018 +60
+ Misses 24704 24624 -80
- Partials 1461 1467 +6
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
I don't have a problem with always closing the body. Consistency can actually help in making sure we forget less. We're going to forget though. In this regard I think that some kind of linter as @atzoum proposed would be helpful in this case. Worst case we end up with what we have now (non-reusable connection), but writing a linter doesn't seem complex and it's definitely know-how that can turn out to be useful moving forward (and can be useful to others since it can be added to golangci-lint which would also give us visibility). Another thing related to this PR, I'd report the Also another thing is that some for i := 0; i < c.Int("count"); i++ {
/* ... */
resp, err := client.Do(req)
if err != nil {
return err
}
defer func() { httputil.CloseResponse(resp) }()
b, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
fmt.Printf("%s\n%s\n", resp.Status, b)
return fmt.Errorf("status code: %d", resp.StatusCode)
}
} Becomes: for i := 0; i < c.Int("count"); i++ {
/* ... */
resp, b, err := doReq(client, req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
fmt.Printf("%s\n%s\n", resp.Status, b)
return fmt.Errorf("status code: %d", resp.StatusCode)
}
}
// ...
func doReq(client *http.Client, req *http.Request) (*http.Response, []byte, error) {
resp, err := client.Do(req)
if err != nil {
return nil, nil, err
}
defer func() { httputil.CloseResponse(resp) }()
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}
return resp, b, nil
} |
I could spot two occurrences of defers inside for loops. Please highlight any additional issues |
I am afraid the linter's repo doesn't appear to be maintained anymore. It's automated build is failing and last commit is since 2021. There are also similar issues reported here and here without them receiving any response from the maintainer. IMO, our best bet would be to move forward with our own linter, forking bodyclose, but this will take me some time |
3001cee
to
f116d49
Compare
Yeah I didn't realise it wasn't maintained anymore, that is unfortunate. |
36cc7e3
to
0b6e0e4
Compare
… the TCP connection
0b6e0e4
to
534ac62
Compare
… the TCP connection (#2718)
Description
If the http response body is not read, the underlying TCP connection cannot be reused (ref).
For that purpose, introducing
httputil.CloseResponse
Note
The usage patterns of this new function might seem strange to you at first, since instead of
we are doing
The justification for using the above patterns is that
golangci-lint
bodyclose
linter, doesn't seem to recognise an indirect body close operation unless it is performed within a closure!!Notion Ticket
Link
Security