-
Notifications
You must be signed in to change notification settings - Fork 832
Harden HTTP Response Body Handling #4489
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
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ import ( | |
"compress/gzip" | ||
"context" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"time" | ||
|
@@ -74,6 +75,12 @@ func createHttpSender(httpClient *http.Client, endpoint config.AgmaAnalyticsHttp | |
glog.Errorf("[agmaAnalytics] Sending request failed %v", err) | ||
return err | ||
} | ||
defer func() { | ||
if _, err := io.Copy(io.Discard, resp.Body); err != nil { | ||
glog.Errorf("[agmaAnalytics] Draining response body failed: %v", err) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The response body must be fully read and closed to ensure connection reuse. Discarding the body is especially needed when the body content is ignored / not used. |
||
resp.Body.Close() | ||
}() | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
glog.Errorf("[agmaAnalytics] Wrong code received %d instead of %d", resp.StatusCode, http.StatusOK) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,24 +2,38 @@ package pubstack | |
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"time" | ||
|
||
"github.com/docker/go-units" | ||
"github.com/golang/glog" | ||
) | ||
|
||
func fetchConfig(client *http.Client, endpoint *url.URL) (*Configuration, error) { | ||
res, err := client.Get(endpoint.String()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to add a timeout to this fetch while we are here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree we should. I don't know the pubstack analytics module well enough to implement it. Will reach out to pubstack to implement. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discussed offline. I will reach out to pubstack to see what a reasonable timeout would be and we can update it in another PR. |
||
if err != nil { | ||
return nil, err | ||
} | ||
defer func() { | ||
// read the entire response body to ensure full connection reuse if there's an | ||
// error while decoding the json | ||
if _, err := io.Copy(io.Discard, res.Body); err != nil { | ||
glog.Errorf("[pubstack] Draining config response body failed: %v", err) | ||
} | ||
res.Body.Close() | ||
}() | ||
|
||
if res.StatusCode != http.StatusOK { | ||
return nil, fmt.Errorf("unexpected status code: %d", res.StatusCode) | ||
} | ||
|
||
defer res.Body.Close() | ||
c := Configuration{} | ||
err = json.NewDecoder(res.Body).Decode(&c) | ||
if err != nil { | ||
return nil, err | ||
return nil, fmt.Errorf("failed to decode config: %w", err) | ||
} | ||
return &c, nil | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ package eventchannel | |
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"path" | ||
|
@@ -27,7 +28,12 @@ func NewHttpSender(client *http.Client, endpoint string) Sender { | |
if err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to add a timeout just above to the http request? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree we should. I don't know the pubstack analytics module well enough to implement it. Will reach out to pubstack to implement. |
||
return err | ||
} | ||
resp.Body.Close() | ||
defer func() { | ||
if _, err := io.Copy(io.Discard, resp.Body); err != nil { | ||
glog.Errorf("[pubstack] Draining sender response body failed: %v", err) | ||
} | ||
resp.Body.Close() | ||
}() | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
glog.Errorf("[pubstack] Wrong code received %d instead of %d", resp.StatusCode, http.StatusOK) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,14 +52,20 @@ func (rc *RateConverter) fetch() (*Rates, error) { | |
if err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to add a timeout to the http request just above? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. Good catch. I added a configurable timeout with a default of 60 seconds. In test from my machine, latency is around 30ms. Since this isn't part of the real time auction flow, happy to set this to a higher limit considering there is no limit today. Hosts can reduce if they find it necessary or we can revisit the default later if there are no concerns right now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discussed offline. We can address this in a follow-up PR. |
||
return nil, err | ||
} | ||
defer func() { | ||
// read the entire response body to ensure full connection reuse if there's an | ||
// invalid status code | ||
if _, err := io.Copy(io.Discard, response.Body); err != nil { | ||
glog.Errorf("Error draining conversion rates response body: %v", err) | ||
} | ||
response.Body.Close() | ||
}() | ||
|
||
if response.StatusCode >= 400 { | ||
message := fmt.Sprintf("The currency rates request failed with status code %d", response.StatusCode) | ||
return nil, &errortypes.BadServerResponse{Message: message} | ||
} | ||
|
||
defer response.Body.Close() | ||
|
||
bytesJSON, err := io.ReadAll(response.Body) | ||
if err != nil { | ||
return nil, err | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -238,13 +238,13 @@ func (f *PriceFloorFetcher) fetchAndValidate(config config.AccountFloorFetch) (* | |
} | ||
|
||
if len(floorResp) > (config.MaxFileSizeKB * 1024) { | ||
glog.Errorf("Recieved invalid floor data from URL: %s, reason : floor file size is greater than MaxFileSize", config.URL) | ||
glog.Errorf("Received invalid floor data from URL: %s, reason : floor file size is greater than MaxFileSize", config.URL) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unrelated typo spotted while editing this file. |
||
return nil, 0 | ||
} | ||
|
||
var priceFloors openrtb_ext.PriceFloorRules | ||
if err = json.Unmarshal(floorResp, &priceFloors.Data); err != nil { | ||
glog.Errorf("Recieved invalid price floor json from URL: %s", config.URL) | ||
glog.Errorf("Received invalid price floor json from URL: %s", config.URL) | ||
return nil, 0 | ||
} | ||
|
||
|
@@ -271,6 +271,14 @@ func (f *PriceFloorFetcher) fetchFloorRulesFromURL(config config.AccountFloorFet | |
if err != nil { | ||
return nil, 0, errors.New("error while getting response from url : " + err.Error()) | ||
} | ||
defer func() { | ||
// read the entire response body to ensure full connection reuse if there's an | ||
// invalid status code | ||
if _, err := io.Copy(io.Discard, httpResp.Body); err != nil { | ||
glog.Errorf("Error while draining fetched floor response body: %v", err) | ||
} | ||
httpResp.Body.Close() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is
Do we get a panic for calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I follow. unpackResponse does not close the body. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line 94 inside There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I got this one wrong @SyntaxNode. Nevermind |
||
}() | ||
|
||
if httpResp.StatusCode != http.StatusOK { | ||
return nil, 0, errors.New("no response from server") | ||
|
@@ -291,7 +299,6 @@ func (f *PriceFloorFetcher) fetchFloorRulesFromURL(config config.AccountFloorFet | |
if err != nil { | ||
return nil, 0, errors.New("unable to read response") | ||
} | ||
defer httpResp.Body.Close() | ||
|
||
return respBody, maxAge, nil | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not directly related. Updated the function signature since only a single error is used.