Skip to content

proposal: net/http: add Client.PrepareRequest #75814

@neild

Description

@neild

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 proposal: 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:

type Client struct {
  // 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 
  PrepareRequest func(*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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolProposal

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions