Add WithHTTPRequestHook to allow callers to inspect outgoing HTTP requests and modify request metadata before they are sent to the daemon. Request hooks receive a clone of the original request so that modifications made by hooks do not mutate the request passed to the client's RoundTripper. Changes made by hooks are preserved on the request passed to the underlying transport. Protect request bodies while hooks are running, preventing hooks from reading or closing the body while restoring the original body before the request is sent. Hooks are invoked in registration order and may return an error to abort the request. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
71 lines
1.4 KiB
Go
71 lines
1.4 KiB
Go
package client
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
)
|
|
|
|
type hookTransport struct {
|
|
base http.RoundTripper
|
|
reqHooks []RequestHook
|
|
respHooks []ResponseHook
|
|
}
|
|
|
|
func (t *hookTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
if len(t.reqHooks) > 0 {
|
|
hookReq := req.Clone(req.Context())
|
|
|
|
if hookReq.Body != nil {
|
|
hookReq.Body = hookBody{}
|
|
}
|
|
if hookReq.GetBody != nil {
|
|
hookReq.GetBody = func() (io.ReadCloser, error) {
|
|
return hookBody{}, nil
|
|
}
|
|
}
|
|
|
|
for _, h := range t.reqHooks {
|
|
if err := h(hookReq); err != nil {
|
|
// The base transport never takes ownership, so close the original
|
|
// request body before returning the hook error.
|
|
if req.Body != nil {
|
|
_ = req.Body.Close()
|
|
}
|
|
return nil, err
|
|
}
|
|
}
|
|
// Restore the actual request body after the hooks have run. The
|
|
// modified request metadata is preserved on hookReq.
|
|
hookReq.Body = req.Body
|
|
hookReq.GetBody = req.GetBody
|
|
req = hookReq
|
|
}
|
|
|
|
resp, err := t.base.RoundTrip(req)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
|
|
if len(t.respHooks) > 0 {
|
|
hookResp := *resp
|
|
if hookResp.Body != nil {
|
|
hookResp.Body = hookBody{}
|
|
}
|
|
|
|
for _, h := range t.respHooks {
|
|
h(&hookResp)
|
|
}
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
type hookBody struct{}
|
|
|
|
func (hookBody) Read([]byte) (int, error) {
|
|
return 0, errors.New("hooks must not read HTTP message body")
|
|
}
|
|
|
|
func (hookBody) Close() error { return nil }
|