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

proposal: net/http: add RoundTripperFunc and Middleware for server & client #38479

Open
leaxoy opened this issue Apr 16, 2020 · 5 comments
Open

Comments

@leaxoy
Copy link

leaxoy commented Apr 16, 2020

Now, in net/http, for server side, we have Handler & HandlerFunc, HandlerFunc is the convenient way for user to define new handler, but in client side, we only have RoundTripper, so I proposal to add RoundTripperFunc to net/http.

// RoundTripperFn implement http.RoundTripper for convenient usage.
type RoundTripperFunc func(request *http.Request) (*http.Response, error)
func (fn RoundTripperFunc) RoundTrip(request *http.Request) (*http.Response, error) { return fn(request) }

With this new type, we can easily implement RoundTripper interface.

In modern web application, middleware pattern is widely used. With Middleware we can add more action before/after handler/request call.
For the server side, we can define Middleware or similar:

type Middleware interface {
	Next(h http.Handler) http.Handler
}

// MiddlewareFn support wrap function with same signature as Middleware.
type MiddlewareFn func(h http.Handler) http.Handler

func (fn MiddlewareFn) Next(h http.Handler) http.Handler { return fn(h) }

// and compose chains of middleware
// ComposeInterceptor compose interceptors to given http.RoundTripper
func ComposeInterceptor(rt http.RoundTripper, interceptors ...Interceptor) http.RoundTripper {
	if len(interceptors) == 0 {
		return rt
	}
	return ComposeInterceptor(interceptors[0].Next(rt), interceptors[1:]...)
}

For the client side, we define Interceptor or similar:

type Interceptor interface {
	Next(fn http.RoundTripper) http.RoundTripper
}

// InterceptorFn implement Interceptor for convenient usage.
type InterceptorFn func(rt http.RoundTripper) http.RoundTripper

func (fn InterceptorFn) Next(rt http.RoundTripper) http.RoundTripper { return fn(rt) }

// and a function compose chains of interceptor
func ComposeInterceptor(rt http.RoundTripper, interceptors ...Interceptor) http.RoundTripper {
	if len(interceptors) == 0 {
		return rt
	}
	return ComposeInterceptor(interceptors[0].Next(rt), interceptors[1:]...)
}

All the above are not necessary, but can reduce and simplify user's boilerplate code.
Please consider this proposal.

The original code is middleware, interceptor & RoundTripperFunc

@bradfitz bradfitz changed the title net/http: add RoundTripperFunc and Middleware for server & client proposal: net/http: add RoundTripperFunc and Middleware for server & client Apr 16, 2020
@gopherbot gopherbot added this to the Proposal milestone Apr 16, 2020
@instabledesign
Copy link

I've done a similare library
https://github.com/gol4ng/httpware

@bradfitz
Copy link
Contributor

bradfitz commented Oct 7, 2020

I definitely want the HTTP client to be composable like the server is and was assuming that would happen as part of the new HTTP client (#23707). I don't think there's a clean way to add it to the existing client so I'm not very excited about this particular proposal.

@leaxoy
Copy link
Author

leaxoy commented Oct 8, 2020

I definitely want the HTTP client to be composable like the server is and was assuming that would happen as part of the new HTTP client (#23707). I don't think there's a clean way to add it to the existing client so I'm not very excited about this particular proposal.

It's sound good to me, is there any plan on that issue.

@leaxoy
Copy link
Author

leaxoy commented Jun 5, 2023

Since we have generics in recent versions. We can write generic Middleware

type Middleware[H any] interface {
    Then(h H) H
}

type MiddlewareFunc[H any] func(H) H

func (fn MiddlewareFunc[H]) Then(h H) H { return fn(h) }

func Compose[H any](middlewares ...Middleware[H]) Middleware[H] {
    for i := len(middlewares); i >= 0; i++ {
        h = middlewares[i].Then(h)
    }
    return h
}

// And Server Middleware bacames:
type ServerMiddleware = Middleware[http.Handler]
// Client Middleware bacames:
type ClientMiddleware = Middleware[http.RoundTripper]

@jsumners
Copy link

jsumners commented Feb 25, 2024

I think some sort of standard library support for this pattern would be quite welcome. I came across this issue while researching how to apply multiple RoundTrippers (RT) to an http.Client. As an example, I want one RT to add a static authorization header to all outgoing requests, and another to manage rate limiting imposed by the remote service.

Adding to the variations already presented in this thread, here is my take on at least one of the articles I found on the topic:

type Interceptor func(http.RoundTripper) InterceptorRT

type InterceptorRT func(*http.Request) (*http.Response, error)

func (irt InterceptorRT) RoundTrip(req *http.Request) (*http.Response, error) {
	return irt(req)
}

// InterceptorChain is a series of [Interceptor] functions that will be applied
// on each request. A chain should be supplied as the [http.Client.Transport]
// on a http client.
func InterceptorChain(rt http.RoundTripper, interceptors ...Interceptor) http.RoundTripper {
	if rt == nil {
		rt = http.DefaultTransport
	}

	for _, interceptor := range interceptors {
		rt = interceptor(rt)
	}

	return rt
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Incoming
Development

No branches or pull requests

5 participants