-
Notifications
You must be signed in to change notification settings - Fork 23
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
improvement: Introduce new URIPool and URISelector interfaces. #341
base: develop
Are you sure you want to change the base?
Changes from 4 commits
cd09537
7e3a617
96b410a
a060bc2
a1a87f5
cd32673
dc1bf3b
5e2d60f
cdfd790
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 |
---|---|---|
@@ -0,0 +1,5 @@ | ||
type: improvement | ||
improvement: | ||
description: Introduce new URIPool and URISelector interfaces. | ||
links: | ||
- https://github.com/palantir/conjure-go-runtime/pull/341 |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -55,7 +55,8 @@ type clientImpl struct { | |||||
errorDecoderMiddleware Middleware | ||||||
recoveryMiddleware Middleware | ||||||
|
||||||
uriScorer internal.RefreshableURIScoringMiddleware | ||||||
uriPool internal.URIPool | ||||||
uriSelector internal.URISelector | ||||||
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. These interfaces exist for the lifetime of the client - we don't believe we need to manage state on a request-specific basis, so not optimizing the interface for that. This works well for state managed over the liftime of the client and URI strategies that are stateless (i.e. rendezvous hashing). |
||||||
maxAttempts refreshable.IntPtr // 0 means no limit. If nil, uses 2*len(uris). | ||||||
backoffOptions refreshingclient.RefreshableRetryParams | ||||||
bufferPool bytesbuffers.Pool | ||||||
|
@@ -82,45 +83,34 @@ func (c *clientImpl) Delete(ctx context.Context, params ...RequestParam) (*http. | |||||
} | ||||||
|
||||||
func (c *clientImpl) Do(ctx context.Context, params ...RequestParam) (*http.Response, error) { | ||||||
uris := c.uriScorer.CurrentURIScoringMiddleware().GetURIsInOrderOfIncreasingScore() | ||||||
if len(uris) == 0 { | ||||||
return nil, werror.ErrorWithContextParams(ctx, "no base URIs are configured") | ||||||
} | ||||||
|
||||||
attempts := 2 * len(uris) | ||||||
attempts := 2 * c.uriPool.NumURIs() | ||||||
if c.maxAttempts != nil { | ||||||
if confMaxAttempts := c.maxAttempts.CurrentIntPtr(); confMaxAttempts != nil { | ||||||
attempts = *confMaxAttempts | ||||||
} | ||||||
} | ||||||
|
||||||
var err error | ||||||
var resp *http.Response | ||||||
|
||||||
retrier := internal.NewRequestRetrier(uris, c.backoffOptions.CurrentRetryParams().Start(ctx), attempts) | ||||||
var err error | ||||||
retrier := internal.NewRequestRetrier(c.backoffOptions.CurrentRetryParams().Start(ctx), attempts) | ||||||
for { | ||||||
uri, isRelocated := retrier.GetNextURI(resp, err) | ||||||
if uri == "" { | ||||||
shouldRetry, retryURL := retrier.Next(resp, err) | ||||||
if !shouldRetry { | ||||||
break | ||||||
} | ||||||
resp, err = c.doOnce(ctx, retryURL, params...) | ||||||
if err != nil { | ||||||
svc1log.FromContext(ctx).Debug("Retrying request", svc1log.Stacktrace(err)) | ||||||
} | ||||||
resp, err = c.doOnce(ctx, uri, isRelocated, params...) | ||||||
} | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
return resp, nil | ||||||
return resp, err | ||||||
} | ||||||
|
||||||
func (c *clientImpl) doOnce( | ||||||
ctx context.Context, | ||||||
baseURI string, | ||||||
useBaseURIOnly bool, | ||||||
retryURL *url.URL, | ||||||
params ...RequestParam, | ||||||
) (*http.Response, error) { | ||||||
|
||||||
// 1. create the request | ||||||
b := &requestBuilder{ | ||||||
headers: make(http.Header), | ||||||
|
@@ -136,9 +126,6 @@ func (c *clientImpl) doOnce( | |||||
return nil, err | ||||||
} | ||||||
} | ||||||
if useBaseURIOnly { | ||||||
b.path = "" | ||||||
} | ||||||
|
||||||
for _, c := range b.configureCtx { | ||||||
ctx = c(ctx) | ||||||
|
@@ -147,12 +134,23 @@ func (c *clientImpl) doOnce( | |||||
if b.method == "" { | ||||||
return nil, werror.ErrorWithContextParams(ctx, "httpclient: use WithRequestMethod() to specify HTTP method") | ||||||
} | ||||||
reqURI := joinURIAndPath(baseURI, b.path) | ||||||
req, err := http.NewRequest(b.method, reqURI, nil) | ||||||
var uri string | ||||||
if retryURL == nil { | ||||||
var err error | ||||||
uri, err = c.uriSelector.Select(c.uriPool.URIs(), b.headers) | ||||||
if err != nil { | ||||||
return nil, werror.WrapWithContextParams(ctx, err, "failed to select uri") | ||||||
} | ||||||
uri = joinURIAndPath(uri, b.path) | ||||||
} else { | ||||||
b.path = "" | ||||||
uri = retryURL.String() | ||||||
} | ||||||
req, err := http.NewRequestWithContext(ctx, b.method, uri, nil) | ||||||
if err != nil { | ||||||
return nil, werror.WrapWithContextParams(ctx, err, "failed to build new HTTP request") | ||||||
return nil, werror.WrapWithContextParams(ctx, err, "failed to build request") | ||||||
} | ||||||
req = req.WithContext(ctx) | ||||||
|
||||||
req.Header = b.headers | ||||||
if q := b.query.Encode(); q != "" { | ||||||
req.URL.RawQuery = q | ||||||
|
@@ -164,7 +162,8 @@ func (c *clientImpl) doOnce( | |||||
transport := clientCopy.Transport // start with the client's transport configured with default middleware | ||||||
|
||||||
// must precede the error decoders to read the status code of the raw response. | ||||||
transport = wrapTransport(transport, c.uriScorer.CurrentURIScoringMiddleware()) | ||||||
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 scoring URI middleware was moved into clientImpl.middleware instantiated when the client is built here: conjure-go-runtime/conjure-go-client/httpclient/client_builder.go Lines 160 to 161 in 6bf0ab9
|
||||||
transport = wrapTransport(transport, c.uriSelector) | ||||||
transport = wrapTransport(transport, c.uriPool) | ||||||
// request decoder must precede the client decoder | ||||||
// must precede the body middleware to read the response body | ||||||
transport = wrapTransport(transport, b.errorDecoderMiddleware, c.errorDecoderMiddleware) | ||||||
|
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.
URIPool
is new functionality. I think the behavior can be captured behind aURISelector
implementation so prefer to just use the refreshable slice of URIs directly for now.