diff --git a/go.mod b/go.mod index 17864b6d..d6b5a6bc 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/fatih/color v1.10.0 // indirect github.com/mattn/go-runewidth v0.0.10 // indirect github.com/nmiyake/pkg/errorstringer v1.0.2 // indirect - github.com/palantir/conjure-go-runtime/v2 v2.12.0 + github.com/palantir/conjure-go-runtime/v2 v2.13.0 github.com/palantir/go-metrics v1.1.1 // indirect github.com/palantir/goastwriter v0.1.0 github.com/palantir/godel/v2 v2.39.0 diff --git a/go.sum b/go.sum index e8c11d4a..ff04ae33 100644 --- a/go.sum +++ b/go.sum @@ -197,8 +197,8 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/palantir/conjure-go-runtime/v2 v2.2.0/go.mod h1:k4+u7YJwBAO6Kk7SGvFCQR7vrkVW8vgQxO8rPZGuS5g= -github.com/palantir/conjure-go-runtime/v2 v2.12.0 h1:1P6N4jWAMIPVPyGcZu40vLhrQqQllCoCbmVTK4d3vvk= -github.com/palantir/conjure-go-runtime/v2 v2.12.0/go.mod h1:c36GPPKT6mk9ZpO54DtZJCqTWHBXLDsBWYmldTvd+i8= +github.com/palantir/conjure-go-runtime/v2 v2.13.0 h1:P7FMFHXvxjaIqFz4u8kCAQQCN4jvpkak2CinD54sPts= +github.com/palantir/conjure-go-runtime/v2 v2.13.0/go.mod h1:c36GPPKT6mk9ZpO54DtZJCqTWHBXLDsBWYmldTvd+i8= github.com/palantir/distgo/pkg/git v1.0.0/go.mod h1:eXrr3SOmf/sWTYmtiubYVxSaVegTlMGgRgBUFMFFedw= github.com/palantir/go-metrics v1.1.0/go.mod h1:fRkuipBnsI4nD8Vd9UNcrUJvD8Y0wOJMSbicygcBrGs= github.com/palantir/go-metrics v1.1.1 h1:YL/UmptBjrC6iSCTVr7vfuIcjL0M359Da3/gBGNny10= diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client.go index 9a111e5e..deb17083 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client.go @@ -96,7 +96,7 @@ func (c *clientImpl) Do(ctx context.Context, params ...RequestParam) (*http.Resp if retryErr != nil { return nil, retryErr } - resp, err = c.doOnce(ctx, uri, params...) + resp, err = c.doOnce(ctx, uri, retrier.IsRelocatedURI(uri), params...) } if err != nil { return nil, err @@ -104,7 +104,12 @@ func (c *clientImpl) Do(ctx context.Context, params ...RequestParam) (*http.Resp return resp, nil } -func (c *clientImpl) doOnce(ctx context.Context, baseURI string, params ...RequestParam) (*http.Response, error) { +func (c *clientImpl) doOnce( + ctx context.Context, + baseURI string, + useBaseURIOnly bool, + params ...RequestParam, +) (*http.Response, error) { // 1. create the request b := &requestBuilder{ @@ -121,6 +126,10 @@ func (c *clientImpl) doOnce(ctx context.Context, baseURI string, params ...Reque return nil, err } } + if useBaseURIOnly { + b.path = "" + } + for _, c := range b.configureCtx { ctx = c(ctx) } diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/errors.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/errors.go index 93a08a39..0965fb4c 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/errors.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/errors.go @@ -26,9 +26,23 @@ import ( // parameter on the error. func StatusCodeFromError(err error) (statusCode int, ok bool) { statusCodeI, ok := werror.ParamFromError(err, "statusCode") - if !ok { + if statusCodeI == nil { return 0, false } statusCode, ok = statusCodeI.(int) return statusCode, ok } + +// LocationFromError retrieves the 'location' parameter from the provided werror. +// If the error is not a werror or does not have the location param, ok is false. +// +// The default client error decoder sets the location parameter on its returned errors +// if the status code is 3xx and a location is set in the response header +func LocationFromError(err error) (location string, ok bool) { + locationI, _ := werror.ParamFromError(err, "location") + if locationI == nil { + return "", false + } + location, ok = locationI.(string) + return location, ok +} diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/request_retrier.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/request_retrier.go index c7564ea6..997d18b2 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/request_retrier.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/request_retrier.go @@ -34,13 +34,14 @@ const ( // In the case of servers in a service-mesh, requests will never be retried and the mesh URI will only be returned on the // first call to GetNextURI type RequestRetrier struct { - currentURI string - retrier retry.Retrier - uris []string - offset int - failedURIs map[string]struct{} - maxAttempts int - attemptCount int + currentURI string + retrier retry.Retrier + uris []string + offset int + relocatedURIs map[string]struct{} + failedURIs map[string]struct{} + maxAttempts int + attemptCount int } // NewRequestRetrier creates a new request retrier. @@ -48,13 +49,14 @@ type RequestRetrier struct { func NewRequestRetrier(uris []string, retrier retry.Retrier, maxAttempts int) *RequestRetrier { offset := rand.Intn(len(uris)) return &RequestRetrier{ - currentURI: uris[offset], - retrier: retrier, - uris: uris, - offset: offset, - failedURIs: map[string]struct{}{}, - maxAttempts: maxAttempts, - attemptCount: 0, + currentURI: uris[offset], + retrier: retrier, + uris: uris, + offset: offset, + relocatedURIs: map[string]struct{}{}, + failedURIs: map[string]struct{}{}, + maxAttempts: maxAttempts, + attemptCount: 0, } } @@ -111,11 +113,10 @@ func (r *RequestRetrier) getRetryFn(resp *http.Response, respErr error) func() { // Immediately backoff and select the next URI. // TODO(whickman): use the retry-after header once #81 is resolved return r.nextURIAndBackoff - } else if isUnavailableResponse(resp, respErr) || resp == nil { + } else if isUnavailableResponse(resp, respErr) { // 503: go to next node - // Or if we get a nil response, we can assume there is a problem with host and can move on to the next. return r.nextURIOrBackoff - } else if shouldTryOther, otherURI := isRetryOtherResponse(resp); shouldTryOther { + } else if shouldTryOther, otherURI := isRetryOtherResponse(resp, respErr); shouldTryOther { // 307 or 308: go to next node, or particular node if provided. if otherURI != nil { return func() { @@ -123,12 +124,23 @@ func (r *RequestRetrier) getRetryFn(resp *http.Response, respErr error) func() { } } return r.nextURIOrBackoff + } else if resp == nil { + // if we get a nil response, we can assume there is a problem with host and can move on to the next. + return r.nextURIOrBackoff } return nil } func (r *RequestRetrier) setURIAndResetBackoff(otherURI *url.URL) { + // If the URI returned by relocation header is a relative path + // We will resolve it with the current URI + if !otherURI.IsAbs() { + if currentURI := parseLocationURL(r.currentURI); currentURI != nil { + otherURI = currentURI.ResolveReference(otherURI) + } + } nextURI := otherURI.String() + r.relocatedURIs[otherURI.String()] = struct{}{} r.retrier.Reset() r.currentURI = nextURI } @@ -169,6 +181,12 @@ func (r *RequestRetrier) isMeshURI(uri string) bool { return strings.HasPrefix(uri, meshSchemePrefix) } +// IsRelocatedURI is a helper function to identify if the provided URI is a relocated URI from response during retry +func (r *RequestRetrier) IsRelocatedURI(uri string) bool { + _, relocatedURI := r.relocatedURIs[uri] + return relocatedURI +} + func (r *RequestRetrier) getErrorForUnretriableResponse(ctx context.Context, resp *http.Response, respErr error) error { message := "GetNextURI called, but retry should not be attempted" params := []werror.Param{ diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/retry.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/retry.go index dbeb5849..6e77e0ad 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/retry.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/retry.go @@ -59,7 +59,15 @@ const ( StatusCodeUnavailable = http.StatusServiceUnavailable ) -func isRetryOtherResponse(resp *http.Response) (bool, *url.URL) { +func isRetryOtherResponse(resp *http.Response, err error) (bool, *url.URL) { + errCode, ok := StatusCodeFromError(err) + if ok && (errCode == StatusCodeRetryOther || errCode == StatusCodeRetryTemporaryRedirect) { + locationStr, ok := LocationFromError(err) + if ok { + return true, parseLocationURL(locationStr) + } + } + if resp == nil { return false, nil } @@ -68,15 +76,19 @@ func isRetryOtherResponse(resp *http.Response) (bool, *url.URL) { return false, nil } locationStr := resp.Header.Get("Location") + return true, parseLocationURL(locationStr) +} + +func parseLocationURL(locationStr string) *url.URL { if locationStr == "" { - return true, nil + return nil } locationURL, err := url.Parse(locationStr) if err != nil { - // Unable to parse non-zero header as something we recognize... - return true, nil + // Unable to parse location as something we recognize + return nil } - return true, locationURL + return locationURL } func isThrottleResponse(resp *http.Response, err error) (bool, time.Duration) { diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/response_error_decoder_middleware.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/response_error_decoder_middleware.go index 5c4f7672..97e0f7db 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/response_error_decoder_middleware.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/response_error_decoder_middleware.go @@ -54,7 +54,7 @@ func errorDecoderMiddleware(errorDecoder ErrorDecoder) Middleware { } // restErrorDecoder is our default error decoder. -// It handles responses of status code >= 400. In this case, +// It handles responses of status code >= 307. In this case, // we create and return a werror with the 'statusCode' parameter // set to the integer value from the response. // @@ -69,33 +69,47 @@ type restErrorDecoder struct{} var _ ErrorDecoder = restErrorDecoder{} func (d restErrorDecoder) Handles(resp *http.Response) bool { - return resp.StatusCode >= http.StatusBadRequest + return resp.StatusCode >= http.StatusTemporaryRedirect } func (d restErrorDecoder) DecodeError(resp *http.Response) error { - statusParam := werror.SafeParam("statusCode", resp.StatusCode) + safeParams := map[string]interface{}{ + "statusCode": resp.StatusCode, + } + unsafeParams := map[string]interface{}{} + if resp.StatusCode >= http.StatusTemporaryRedirect && + resp.StatusCode < http.StatusBadRequest { + unsafeParams["location"] = resp.Header.Get("Location") + } + wSafeParams := werror.SafeParams(safeParams) + wUnsafeParams := werror.UnsafeParams(unsafeParams) // TODO(#98): If a byte buffer pool is configured, use it to avoid an allocation. body, err := ioutil.ReadAll(resp.Body) if err != nil { - return werror.Wrap(err, "server returned an error and failed to read body", statusParam) + return werror.Wrap(err, "server returned an error and failed to read body", wSafeParams, wUnsafeParams) } if len(body) == 0 { - return werror.Error(resp.Status, statusParam) + return werror.Error(resp.Status, wSafeParams, wUnsafeParams) } // If JSON, try to unmarshal as conjure error if isJSON := strings.Contains(resp.Header.Get("Content-Type"), codecs.JSON.ContentType()); !isJSON { - return werror.Error(resp.Status, statusParam, werror.UnsafeParam("responseBody", string(body))) + return werror.Error(resp.Status, wSafeParams, wUnsafeParams, werror.UnsafeParam("responseBody", string(body))) } conjureErr, err := errors.UnmarshalError(body) if err != nil { - return werror.Wrap(err, "", statusParam, werror.UnsafeParam("responseBody", string(body))) + return werror.Wrap(err, "", wSafeParams, wUnsafeParams, werror.UnsafeParam("responseBody", string(body))) } - return werror.Wrap(conjureErr, "", statusParam) + return werror.Wrap(conjureErr, "", wSafeParams, wUnsafeParams) } // StatusCodeFromError wraps the internal StatusCodeFromError func. For behavior details, see its docs. func StatusCodeFromError(err error) (statusCode int, ok bool) { return internal.StatusCodeFromError(err) } + +// LocationFromError wraps the internal LocationFromError func. For behavior details, see its docs. +func LocationFromError(err error) (location string, ok bool) { + return internal.LocationFromError(err) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 565b2bd5..61d35df5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -21,7 +21,7 @@ github.com/mattn/go-runewidth github.com/nmiyake/pkg # github.com/nmiyake/pkg/errorstringer v1.0.2 github.com/nmiyake/pkg/errorstringer -# github.com/palantir/conjure-go-runtime/v2 v2.12.0 +# github.com/palantir/conjure-go-runtime/v2 v2.13.0 github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal github.com/palantir/conjure-go-runtime/v2/conjure-go-contract/codecs