-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: explode Client.Do method into a chain of handlers (#463)
This explodes the multiple concerns mixed in the `Do` method into multiple handlers that are chained, similar to the `http.RoundTripper`.
- Loading branch information
Showing
15 changed files
with
785 additions
and
136 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package hcloud | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
) | ||
|
||
// handler is an interface representing a client request transaction. The handler are | ||
// meant to be chained, similarly to the [http.RoundTripper] interface. | ||
// | ||
// The handler chain is placed between the [Client] API operations and the | ||
// [http.Client]. | ||
type handler interface { | ||
Do(req *http.Request, v any) (resp *Response, err error) | ||
} | ||
|
||
// assembleHandlerChain assembles the chain of handlers used to make API requests. | ||
// | ||
// The order of the handlers is important. | ||
func assembleHandlerChain(client *Client) handler { | ||
// Start down the chain: sending the http request | ||
h := newHTTPHandler(client.httpClient) | ||
|
||
// Insert debug writer if enabled | ||
if client.debugWriter != nil { | ||
h = wrapDebugHandler(h, client.debugWriter) | ||
} | ||
|
||
// Read rate limit headers | ||
h = wrapRateLimitHandler(h) | ||
|
||
// Build error from response | ||
h = wrapErrorHandler(h) | ||
|
||
// Retry request if condition are met | ||
h = wrapRetryHandler(h, client.backoffFunc) | ||
|
||
// Finally parse the response body into the provided schema | ||
h = wrapParseHandler(h) | ||
|
||
return h | ||
} | ||
|
||
// cloneRequest clones both the request and the request body. | ||
func cloneRequest(req *http.Request, ctx context.Context) (cloned *http.Request, err error) { //revive:disable:context-as-argument | ||
cloned = req.Clone(ctx) | ||
|
||
if req.ContentLength > 0 { | ||
cloned.Body, err = req.GetBody() | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
return cloned, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package hcloud | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/http/httputil" | ||
) | ||
|
||
func wrapDebugHandler(wrapped handler, output io.Writer) handler { | ||
return &debugHandler{wrapped, output} | ||
} | ||
|
||
type debugHandler struct { | ||
handler handler | ||
output io.Writer | ||
} | ||
|
||
func (h *debugHandler) Do(req *http.Request, v any) (resp *Response, err error) { | ||
// Clone the request, so we can redact the auth header, read the body | ||
// and use a new context. | ||
cloned, err := cloneRequest(req, context.Background()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
cloned.Header.Set("Authorization", "REDACTED") | ||
|
||
dumpReq, err := httputil.DumpRequestOut(cloned, true) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
fmt.Fprintf(h.output, "--- Request:\n%s\n\n", dumpReq) | ||
|
||
resp, err = h.handler.Do(req, v) | ||
if err != nil { | ||
return resp, err | ||
} | ||
|
||
dumpResp, err := httputil.DumpResponse(resp.Response, true) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
fmt.Fprintf(h.output, "--- Response:\n%s\n\n", dumpResp) | ||
|
||
return resp, err | ||
} |
Oops, something went wrong.