You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The net/http package has three (possibly soon to be four) types representing an HTTP client, in the sense of something which can send an HTTP request and receive a response:
RoundTripper is an interface representing the ability to execute a single HTTP transaction.
Transport is an implementation of RoundTripper. It manages a pool of cached connections.
Client is a concrete type which wraps a RoundTripper. It adds cookie management and redirect following handling.
ClientConn is a proposed new addition (see net/http: client connection API #75772). This would be a RoundTripper implementation that represents a single connection.
The RoundTripper interface permits middleware to wrap a Transport or other RoundTripper and add additional functionality. For example, oauth2.Transport is a RoundTripper that adds "Authorization" headers to outbound requests.
While useful in some situations, the RoundTripper layer is the wrong place to add some types of functionality. For example, the oauth2.Transport I just mentioned can cause incorrect behavior when following a redirect: When an http.Client follows a cross-domain redirect, it strips sensitive headers (including "Authorization") from the redirected request. However, oauth2.Transport adds this header at a lower level than http.Client and has no way to identify redirect requests. Therefore, the "Authorization" header is still sent after a cross-domain redirect. (This is golang/oauth2#500, and I don't see any good way to fix it in the context of the current oauth2 package API.)
Middleware which acts on an entire request (including redirects) as opposed to an individual transaction needs to wrap the http.Client, not an RoundTripper.
Client is a concrete type. An API which wants to accept a user-provided client either accepts a concrete *http.Client (which precludes client-level middleware) or needs to define some additional interface. For example:
We could add a new interface type to net/http that is implemented by *Client, and encourage APIs to accept this type rather than a concrete *Client. However, this approach doesn't do anything to help existing APIs which take a concrete *Client. In addition, *Client has six methods; while only one of them (Client.Do) is necessary for this use case, adding an interface type that is less useful than *Client itself seems unfortunate.
I instead propose that we add a new field to Client to provide a place to insert request-modifying middleware:
typeClientstruct {
// PrepareRequest specifies a per-request hook.// If PrepareRequest is not nil, the client calls it before sending a request.// It is not called after redirects.//// PrepareRequest returns the request to send or an error to terminate the request.// If PrepareRequest needs to modify the request, it should clone the PrepareRequestfunc(*Request) (*Request, error)
// ...existing fields
}
The PrepareRequest hook gives us a way to inject middleware layers into any code which currently uses an *http.Client. For example, it would let us change oauth2.Config.Client to return an *http.Client that injects an Authorization header, but which still strips the header after cross-domain redirects.
The
net/httppackage has three (possibly soon to be four) types representing an HTTP client, in the sense of something which can send an HTTP request and receive a response:RoundTripperis an interface representing the ability to execute a single HTTP transaction.Transportis an implementation ofRoundTripper. It manages a pool of cached connections.Clientis a concrete type which wraps aRoundTripper. It adds cookie management and redirect following handling.ClientConnis a proposed new addition (see net/http: client connection API #75772). This would be aRoundTripperimplementation that represents a single connection.The
RoundTripperinterface permits middleware to wrap aTransportor otherRoundTripperand add additional functionality. For example,oauth2.Transportis aRoundTripperthat adds "Authorization" headers to outbound requests.While useful in some situations, the
RoundTripperlayer is the wrong place to add some types of functionality. For example, theoauth2.TransportI just mentioned can cause incorrect behavior when following a redirect: When anhttp.Clientfollows a cross-domain redirect, it strips sensitive headers (including "Authorization") from the redirected request. However,oauth2.Transportadds this header at a lower level thanhttp.Clientand has no way to identify redirect requests. Therefore, the "Authorization" header is still sent after a cross-domain redirect. (This is golang/oauth2#500, and I don't see any good way to fix it in the context of the currentoauth2package API.)Middleware which acts on an entire request (including redirects) as opposed to an individual transaction needs to wrap the
http.Client, not anRoundTripper.Clientis a concrete type. An API which wants to accept a user-provided client either accepts a concrete*http.Client(which precludes client-level middleware) or needs to define some additional interface. For example:*http.Client.HTTPClientinterface defined in the same package.*http.Clientby accepting an interface modelcontextprotocol/go-sdk#522 contains some discussion on whether an API should accept an*http.Clientor some locally-defined interface type.We could add a new interface type to
net/httpthat is implemented by*Client, and encourage APIs to accept this type rather than a concrete*Client. However, this approach doesn't do anything to help existing APIs which take a concrete*Client. In addition,*Clienthas six methods; while only one of them (Client.Do) is necessary for this use case, adding an interface type that is less useful than*Clientitself seems unfortunate.I instead propose that we add a new field to
Clientto provide a place to insert request-modifying middleware:The
PrepareRequesthook gives us a way to inject middleware layers into any code which currently uses an*http.Client. For example, it would let us changeoauth2.Config.Clientto return an*http.Clientthat injects an Authorization header, but which still strips the header after cross-domain redirects.