Description
Most web developers are familiar with the concept of middleware, which has been adopted in many HTTP routers and web frameworks. This proposal suggests officially introducing the concept of middleware into the net/http
package.
Background
Middleware is a design pattern that elegantly allows the addition of cross-cutting concerns such as logging, authentication handling, or recovering from a panic, minimizing the need for multiple code contact points. It acts as a layer between the core application logic and the underlying infrastructure or components. Typically, it intercepts requests or responses and performs certain actions or transformations before passing them along to the next component in the processing pipeline.
In Go, middleware is usually provided by an HTTP router or web framework because the net/http
package lacks built-in support for it. However, there are actually functions within the net/http
package that essentially act as middlewares. These functions include AllowQuerySemicolons
, MaxBytesHandler
, StripPrefix
, and TimeoutHandler
. It's just that they all avoid describing themselves as middlewares. This design choice brings some downsides. For example, it's hard to modify them without breaking compatibility, like we can't add parameters to them. And since their parameters are usually different, we cannot easily form them into a handler chain, but can only call them one by one.
There are already numerous middlewares available in the community. However, most of them are designed specifically for a particular HTTP router or web framework. This is because, without the support of the standard library, it is difficult for people to design a general-purpose middleware without compromising extensibility.
This proposal aims to solve the above problems by officially introducing the middleware design pattern to the net/http
package.
Proposal
I propose to add a 1-method interface named "Middleware" to the net/http
package. This interface should contain only one method: func(Handler) Handler
. The purpose of this method is to chain the Handler
passed to it and perform some specific processing before or after the actual execution of the inputted Handler
.
Additionally, for ease of use, I propose to add a func(Handler, ...Middleware) Handler
to the net/http
package. This function is used to associate one or more middlewares with the provided Handler
and compose them into a new Handler
.
Implementation
This proposal will introduce the following API changes to the net/http
package:
package http
// Middleware is used to chain the [Handler].
type Middleware interface {
// ChainHTTPHandler chains the next to the returned [Handler].
//
// Typically, the returned [Handler] is a closure which does something
// with the [ResponseWriter] and [Request] passed to it, and then calls
// the next.ServeHTTP.
ChainHTTPHandler(next Handler) Handler
}
// ApplyMiddleware applies the provided middlewares to the h to form a handler
// chain, and returns a new [Handler] to execute the chain.
//
// For example, ApplyMiddleware(h, m1, m2).ServeHTTP(rw, r) is equivalent to
// m1.ChainHTTPHandler(m2.ChainHTTPHandler(h)).ServeHTTP(rw, r).
func ApplyMiddleware(h Handler, m ...Middleware) Handler { ... }
(Note that the absence of MiddlewareFunc
is intentional, giving that #47487 is likely to be accepted. If #47487 is unfortunately rejected, or if this proposal is accepted before #47487, MiddlewareFunc
should be added.)
For the implementation of this proposal, the difficult part is naming. For the Middleware
interface, some people may use Next(next Handler) Handler
as its method. However, in my opinion, "Next" is not an appropriate name, because it says almost nothing. The purpose of the method itself is to return a closure that does something and executes the next
handler passed to it. Web developers often describe this behavior as "chaining". And considering the existing ServeHTTP
method of the Handler
, I think ChainHTTPHandler
is a good choice for our Middleware
. Alternatively, WithNextHandler
could also be a viable option.