Skip to content

Commit a63a1be

Browse files
authored
uhttp improvements. (#257)
- Wrap more network errors in grpc statuses that cause us to retry. - Add WithAlwaysJSONResponse that doesn't check the content type header.
1 parent 28482af commit a63a1be

File tree

1 file changed

+37
-1
lines changed

1 file changed

+37
-1
lines changed

pkg/uhttp/wrapper.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"io"
1111
"net/http"
1212
"net/url"
13+
"syscall"
1314
"time"
1415

1516
"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
@@ -154,6 +155,20 @@ func WithJSONResponse(response interface{}) DoOption {
154155
}
155156
}
156157

158+
// Ignore content type header and always try to parse the response as JSON.
159+
func WithAlwaysJSONResponse(response interface{}) DoOption {
160+
return func(resp *WrapperResponse) error {
161+
if response == nil && len(resp.Body) == 0 {
162+
return nil
163+
}
164+
err := json.Unmarshal(resp.Body, response)
165+
if err != nil {
166+
return fmt.Errorf("failed to unmarshal json response: %w. status code: %d. body %v", err, resp.StatusCode, logBody(resp.Body, maxBodySize))
167+
}
168+
return nil
169+
}
170+
}
171+
157172
func logBody(body []byte, size int) string {
158173
if len(body) > size {
159174
return string(body[:size]) + " ..."
@@ -234,6 +249,17 @@ func WithResponse(response interface{}) DoOption {
234249
}
235250
}
236251

252+
func WrapErrors(preferredCode codes.Code, statusMsg string, errs ...error) error {
253+
st := status.New(preferredCode, statusMsg)
254+
255+
if len(errs) == 0 {
256+
return st.Err()
257+
}
258+
259+
allErrs := append([]error{st.Err()}, errs...)
260+
return errors.Join(allErrs...)
261+
}
262+
237263
func WrapErrorsWithRateLimitInfo(preferredCode codes.Code, resp *http.Response, errs ...error) error {
238264
st := status.New(preferredCode, resp.Status)
239265

@@ -281,7 +307,10 @@ func (c *BaseHttpClient) Do(req *http.Request, options ...DoOption) (*http.Respo
281307
var urlErr *url.Error
282308
if errors.As(err, &urlErr) {
283309
if urlErr.Timeout() {
284-
return nil, status.Error(codes.DeadlineExceeded, fmt.Sprintf("request timeout: %v", urlErr.URL))
310+
return nil, WrapErrors(codes.DeadlineExceeded, fmt.Sprintf("request timeout: %v", urlErr.URL), urlErr)
311+
}
312+
if urlErr.Temporary() {
313+
return nil, WrapErrors(codes.Unavailable, fmt.Sprintf("temporary error: %v", urlErr.URL), urlErr)
285314
}
286315
}
287316
if errors.Is(err, context.DeadlineExceeded) {
@@ -297,6 +326,13 @@ func (c *BaseHttpClient) Do(req *http.Request, options ...DoOption) (*http.Respo
297326
if len(body) > 0 {
298327
resp.Body = io.NopCloser(bytes.NewBuffer(body))
299328
}
329+
// Turn certain body read errors into grpc statuses so we retry
330+
if errors.Is(err, io.ErrUnexpectedEOF) {
331+
return resp, WrapErrors(codes.Unavailable, "unexpected EOF", err)
332+
}
333+
if errors.Is(err, syscall.ECONNRESET) {
334+
return resp, WrapErrors(codes.Unavailable, "connection reset", err)
335+
}
300336
return resp, err
301337
}
302338

0 commit comments

Comments
 (0)