Skip to content
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

Ability to inject middleware functions to manipulate requests and responses #78

Merged
merged 5 commits into from
Jul 21, 2021

Conversation

manicminer
Copy link
Owner

@manicminer manicminer commented Jul 16, 2021

  • Middleware functions can be stacked and provided for both Request and Response handling
  • Replace the request retry logic with https:/hashicorp/go-retryablehttp
  • Export msgraph.Client{}.HttpClient so callers can additionally supply their own http.Client

@manicminer manicminer added the enhancement New feature or request label Jul 16, 2021
@manicminer manicminer added this to the v0.23.0 milestone Jul 16, 2021
Copy link
Collaborator

@tombuildsstuff tombuildsstuff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor comments but this otherwise LGTM 👍

@@ -85,22 +78,25 @@ type Client struct {
DisableRetries bool

// RequestMiddlewares is a slice of functions that are called in order before a request is sent
RequestMiddlewares []RequestMiddleware
RequestMiddlewares *[]RequestMiddleware
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this wants to become:

Suggested change
RequestMiddlewares *[]RequestMiddleware
RequestMiddlewares []*RequestMiddleware

which'd mean the middleware functions themselves would be pointers?


// ResponseMiddlewares is a slice of functions that are called in order before a response is parsed and returned
ResponseMiddlewares []ResponseMiddleware
ResponseMiddlewares *[]ResponseMiddleware
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(same here)?

continue
}
}
status = resp.StatusCode
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if resp is nil here (for example a dropped connection?)?

errText = fmt.Sprintf("OData error: %s", o.Error)
default:
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these two lines assume there's a body, but we're not checking that it has any length here?

if attempts > 0 {
time.Sleep(time.Duration(backoff))
}
c.retryableClient.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because this function sits on the instance of the object (Client) and not (*Client) this change to retryableClient won't be retained, which seems fine but just double-checking

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the intent actually, as the ConsistencyFailureFunc is defined per operation / request type, e.g. a consistency error looks different for say GET /foo/1 to PUT /foo/1

Comment on lines 92 to 102
func NewClient(apiVersion ApiVersion, tenantId string) Client {
r := retryablehttp.NewClient()
return Client{
Endpoint: environments.MsGraphGlobal.Endpoint,
ApiVersion: apiVersion,
TenantId: tenantId,
UserAgent: "Hamilton (Go-http-client/1.1)",
httpClient: &http.Client{},
Endpoint: environments.MsGraphGlobal.Endpoint,
ApiVersion: apiVersion,
TenantId: tenantId,
UserAgent: "Hamilton (Go-http-client/1.1)",
HttpClient: r.StandardClient(),
retryableClient: r,
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to this PR, but a though that it'd be convenient to have a NewClientWithEndpoint constructor that this calls with environments.MsGraphGlobal.Endpoint?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I like that. It has been asked for, but I was hesitant to overly clutter the NewClient function signature

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although actually, to be used anywhere except corner cases, this would also have to be cascaded to every resource client

if err != nil {
return nil, status, nil, err
}
c.retryableClient.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought - is it possible / worthwhile to export this default retry function somehow? I'm just thinking, what if I want to pass in a custom HTTP client, but I still want to use the same retry behaviour? That way maybe I could do something along these lines:

r := retryablehttp.NewClient()
r.HTTPClient = wrappedClient
r.CheckRetry = msgraph.DefaultCheckRetry

graphClient := msgraph.NewApplicationsClient(tenantID, r.StandardClient())

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be a nice feature, but the problem is that the default retry behavior sources a ConsistencyFailureFunc directly from the input struct for each request, so it can be determined whether the failure was due to replication delay within Azure AD. If you wanted to get the generic retry behavior (e.g. 429 backoff, 5xx retries) you can just use retryablehttp as it's providing that functionality here.

@manicminer manicminer merged commit 21b92c5 into main Jul 21, 2021
@manicminer manicminer deleted the middlewares branch July 21, 2021 18:22
manicminer added a commit that referenced this pull request Jul 21, 2021
@manicminer manicminer linked an issue Jul 21, 2021 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support providing an HttpClient
3 participants