Skip to content

Commit

Permalink
return rawData
Browse files Browse the repository at this point in the history
  • Loading branch information
anhdhbn committed Feb 25, 2024
1 parent 88533fa commit de6e1ea
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 71 deletions.
14 changes: 4 additions & 10 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package sling

import (
"encoding/json"
"io"
"net/http"

"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
Expand All @@ -12,7 +10,7 @@ import (
// ResponseDecoder decodes http responses into struct values.
type ResponseDecoder interface {
// Decode decodes the response into the value pointed to by v.
Decode(resp *http.Response, v interface{}) error
Decode(bytes []byte, v interface{}) error
}

// jsonDecoder decodes http response JSON into a JSON-tagged struct value.
Expand All @@ -21,8 +19,8 @@ type jsonDecoder struct {

// Decode decodes the Response Body into the value pointed to by v.
// Caller must provide a non-nil v and close the resp.Body.
func (d jsonDecoder) Decode(resp *http.Response, v interface{}) error {
return json.NewDecoder(resp.Body).Decode(v)
func (d jsonDecoder) Decode(bytes []byte, v interface{}) error {
return json.Unmarshal(bytes, v)
}

// jsonDecoder decodes http response JSON into a JSON-tagged struct value.
Expand All @@ -31,10 +29,6 @@ type JsonpbDecoder struct {

// Decode decodes the Response Body into the value pointed to by v.
// Caller must provide a non-nil v and close the resp.Body.
func (d JsonpbDecoder) Decode(resp *http.Response, v interface{}) error {
bytes, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
func (d JsonpbDecoder) Decode(bytes []byte, v interface{}) error {
return protojson.Unmarshal(bytes, v.(proto.Message))
}
34 changes: 34 additions & 0 deletions http_wrapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package sling

import (
"io"
"net/http"
)

type HttpWrapper struct {
http *http.Client
}

func (h *HttpWrapper) Do(req *http.Request) (*http.Response, []byte, error) {
resp, err := h.http.Do(req)
if err != nil {
return nil, nil, err
}
// when err is nil, resp contains a non-nil resp.Body which must be closed
defer resp.Body.Close()

// The default HTTP client's Transport may not
// reuse HTTP/1.x "keep-alive" TCP connections if the Body is
// not read to completion and closed.
// See: https://golang.org/pkg/net/http/#Response
defer io.Copy(io.Discard, resp.Body)
rawData, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}
return resp, rawData, nil
}

func NewHttpWrapper(client *http.Client) *HttpWrapper {
return &HttpWrapper{http: client}
}
4 changes: 3 additions & 1 deletion response.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ type Raw []byte
// Response is a http response wrapper
type Response struct {
*http.Response
RawData []byte
}

func NewResponse(response *http.Response) *Response {
func NewResponse(response *http.Response, rawData []byte) *Response {
return &Response{
Response: response,
RawData: rawData,
}
}

Expand Down
22 changes: 12 additions & 10 deletions retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,10 +375,10 @@ func FromRequest(r *http.Request) (*Request, error) {
return &Request{bodyReader, r}, nil
}

func (c *RetryDoer) Do(req *http.Request) (*http.Response, error) {
func (c *RetryDoer) Do(req *http.Request) (*http.Response, []byte, error) {
re, err := FromRequest(req)
if err != nil {
return nil, err
return nil, nil, err
}
return c.DoCustom(re)
}
Expand All @@ -398,7 +398,7 @@ func (c *RetryDoer) logger(ctx context.Context) Logger {
}

// Do wraps calling an HTTP method with retries.
func (c *RetryDoer) DoCustom(req *Request) (*http.Response, error) {
func (c *RetryDoer) DoCustom(req *Request) (*http.Response, []byte, error) {
logger := c.logger(req.Context())

logger.WithFields(Fields{"method": req.Method, "url": req.URL}).Info("performing request")
Expand All @@ -407,6 +407,7 @@ func (c *RetryDoer) DoCustom(req *Request) (*http.Response, error) {
var attempt int
var shouldRetry bool
var doErr, checkErr error
var rawData []byte

for i := 0; ; i++ {
attempt++
Expand All @@ -415,11 +416,11 @@ func (c *RetryDoer) DoCustom(req *Request) (*http.Response, error) {

// Always rewind the request body when non-nil.
if err := req.rewind(); err != nil {
return resp, err
return resp, nil, err
}

// Attempt the request
resp, doErr = c.HTTPClient.Do(req.Request)
resp, rawData, doErr = c.HTTPClient.Do(req.Request)
if resp != nil {
code = resp.StatusCode
}
Expand Down Expand Up @@ -457,7 +458,7 @@ func (c *RetryDoer) DoCustom(req *Request) (*http.Response, error) {
logger.WithFields(Fields{"request": desc, "timeout": wait, "remaining": remain}).Info("retrying request")
select {
case <-req.Context().Done():
return nil, req.Context().Err()
return nil, nil, req.Context().Err()
case <-time.After(wait):
}

Expand All @@ -469,7 +470,7 @@ func (c *RetryDoer) DoCustom(req *Request) (*http.Response, error) {

// this is the closest we have to success criteria
if doErr == nil && checkErr == nil && !shouldRetry {
return resp, nil
return resp, rawData, nil
}

err := doErr
Expand All @@ -478,7 +479,8 @@ func (c *RetryDoer) DoCustom(req *Request) (*http.Response, error) {
}

if c.ErrorHandler != nil {
return c.ErrorHandler(resp, err, attempt)
resp, err = c.ErrorHandler(resp, err, attempt)
return resp, rawData, err
}

// By default, we close the response body and return an error without
Expand All @@ -493,10 +495,10 @@ func (c *RetryDoer) DoCustom(req *Request) (*http.Response, error) {
// this means CheckRetry thought the request was a failure, but didn't
// communicate why
if err == nil {
return nil, fmt.Errorf("%s %s giving up after %d attempt(s)",
return nil, nil, fmt.Errorf("%s %s giving up after %d attempt(s)",
req.Method, req.URL, attempt)
}

return nil, fmt.Errorf("%s %s giving up after %d attempt(s): %w",
return nil, nil, fmt.Errorf("%s %s giving up after %d attempt(s): %w",
req.Method, req.URL, attempt, err)
}
50 changes: 20 additions & 30 deletions sling.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const (
// wrap *http.Client with layers of Doers to form a stack of client-side
// middleware.
type Doer interface {
Do(req *http.Request) (*http.Response, error)
Do(req *http.Request) (*http.Response, []byte, error)
}

// Sling is an HTTP Request builder and sender.
Expand All @@ -61,9 +61,9 @@ type Sling struct {
isSuccess SuccessDecider
}

var defaultClient = &http.Client{
var defaultClient = NewHttpWrapper(&http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport),
}
})

// New returns a new Sling with an http DefaultClient.
func New() *Sling {
Expand Down Expand Up @@ -113,18 +113,18 @@ func (s *Sling) New() *Sling {

// Client sets the http Client used to do requests. If a nil client is given,
// the http.DefaultClient will be used.
func (s *Sling) Client(httpClient *http.Client) *Sling {
if httpClient == nil {
return s.Doer(http.DefaultClient)
func (s *Sling) Client(httpWrapper *HttpWrapper) *Sling {
if httpWrapper == nil {
return s.Doer(defaultClient)
}
return s.Doer(httpClient)
return s.Doer(httpWrapper)
}

// Doer sets the custom Doer implementation used to do requests.
// If a nil client is given, the http.DefaultClient will be used.
func (s *Sling) Doer(doer Doer) *Sling {
if doer == nil {
s.httpClient = http.DefaultClient
s.httpClient = defaultClient
} else {
s.httpClient = doer
}
Expand Down Expand Up @@ -454,58 +454,48 @@ func (s *Sling) Receive(successV, failureV interface{}) (*Response, error) {
// decoding is skipped. Any error sending the request or decoding the response
// is returned.
func (s *Sling) Do(req *http.Request, successV, failureV interface{}) (*Response, error) {
resp, err := s.httpClient.Do(req)
resp, rawData, err := s.httpClient.Do(req)
if err != nil {
return NewResponse(resp), err
return NewResponse(resp, nil), err
}
// when err is nil, resp contains a non-nil resp.Body which must be closed
defer resp.Body.Close()

// The default HTTP client's Transport may not
// reuse HTTP/1.x "keep-alive" TCP connections if the Body is
// not read to completion and closed.
// See: https://golang.org/pkg/net/http/#Response
defer io.Copy(io.Discard, resp.Body)

// Don't try to decode on 204s or Content-Length is 0
if resp.StatusCode == http.StatusNoContent || resp.ContentLength == 0 {
return NewResponse(resp), nil
return NewResponse(resp, nil), nil
}

// Decode from json
if successV != nil || failureV != nil {
err = decodeResponse(resp, s.isSuccess, s.responseDecoder, successV, failureV)
err = decodeResponse(resp, rawData, s.isSuccess, s.responseDecoder, successV, failureV)
}
return NewResponse(resp), err
return NewResponse(resp, rawData), err
}

// decodeResponse decodes response Body into the value pointed to by successV
// if the response is a success (2XX) or into the value pointed to by failureV
// otherwise. If the successV or failureV argument to decode into is nil,
// decoding is skipped.
// Caller is responsible for closing the resp.Body.
func decodeResponse(resp *http.Response, isSuccess SuccessDecider, decoder ResponseDecoder, successV, failureV interface{}) error {
func decodeResponse(resp *http.Response, rawData []byte, isSuccess SuccessDecider, decoder ResponseDecoder, successV, failureV interface{}) error {
if isSuccess(resp) {
switch sv := successV.(type) {
case nil:
return nil
case *Raw:
respBody, err := io.ReadAll(resp.Body)
*sv = respBody
return err
*sv = rawData
return nil
default:
return decoder.Decode(resp, successV)
return decoder.Decode(rawData, successV)
}
} else {
switch fv := failureV.(type) {
case nil:
return nil
case *Raw:
respBody, err := io.ReadAll(resp.Body)
*fv = respBody
return err
*fv = rawData
return nil
default:
return decoder.Decode(resp, failureV)
return decoder.Decode(rawData, failureV)
}
}
}
Loading

0 comments on commit de6e1ea

Please sign in to comment.