ref!(http/middleware): bump to v2 to use http.Handler rather than http.HandlerFunc

This commit is contained in:
AJ ONeal 2026-01-26 12:40:13 -07:00
parent 8136b7f4b9
commit b559a25404
No known key found for this signature in database
3 changed files with 46 additions and 38 deletions

View File

@ -8,8 +8,8 @@ A simple zero-cost middleware handler for Go's native `net/http` `ServeMux`. \
Turns tedious this: Turns tedious this:
```go ```go
mux.HandleFunc("GET /api/version", logRequests(timeRequests(getVersion))) mux.Handle("GET /api/version", logRequests(timeRequests(http.HandlerFunc(getVersion))))
mux.HandleFunc("GET /api/data", logRequests(timeRequests(requireAuth(requireAdmin(getData))))) mux.Handle("GET /api/data", logRequests(timeRequests(requireAuth(requireAdmin(http.HandlerFunc(getData))))))
``` ```
Into organizable this: Into organizable this:
@ -25,7 +25,7 @@ authMW.HandleFunc("GET /api/data", getData)
Using stdlib this: Using stdlib this:
```go ```go
type Middleware func(http.HandlerFunc) http.HandlerFunc type Middleware func(http.Handler) http.Handler
``` ```
**Zero-cost** because each invocation of `mv.Handle(handler)` composes the function calls _exactly_ the same way as when done manually. \ **Zero-cost** because each invocation of `mv.Handle(handler)` composes the function calls _exactly_ the same way as when done manually. \
@ -49,12 +49,12 @@ import (
func main() { func main() {
mux := http.NewServeMux() mux := http.NewServeMux()
mw := middleware.New(logRequests, basicAuth) mw := middleware.WithMux(mux, recoverPanics, logRequests, basicAuth)
mux.HandleFunc("GET /api/data", mw.Handle(getData)) mux.HandleFunc("GET /api/data", getData)
mux.HandleFunc("POST /api/data", mw.Handle(postData)) mux.HandleFunc("POST /api/data", postData)
adminMW := mw.Use(requireAdmin) adminMW := mw.Use(requireAdmin)
mux.HandleFunc("DELETE /api/data", adminMW.Handle(deleteData)) adminMW.HandleFunc("DELETE /api/data", deleteData)
http.ListenAndServe(":8080", mux) http.ListenAndServe(":8080", mux)
} }
@ -62,16 +62,37 @@ func main() {
### Example Middleware ### Example Middleware
Middleware is any function that wraps and returns the built-in `http.HandlerFunc` handler type. Middleware is any function that wraps and returns the built-in `http.Handler` handler type.
```go ```go
type Middleware func(http.HandlerFunc) http.HandlerFunc type Middleware func(http.Handler) http.Handler
```
#### Example: Panic handler
```go
func recoverPanics(next http.Handler) http.Handler {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if _err := recover(); _err != nil {
err, ok := _err.(error)
if !ok {
err = fmt.Errorf("%v", _err)
}
api.InternalError(w, r, err)
return
}
}()
next.ServeHTTP(w, r)
}
}
``` ```
#### Example: Request logger #### Example: Request logger
```go ```go
func logRequests(next http.HandlerFunc) http.HandlerFunc { func logRequests(next http.Handler) http.Handler {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
start := time.Now() start := time.Now()
@ -91,7 +112,7 @@ var creds = envauth.BasicCredentials{
Password: os.Getenv("BASIC_AUTH_PASSWORD"), Password: os.Getenv("BASIC_AUTH_PASSWORD"),
} }
func basicAuth(next http.HandlerFunc) http.HandlerFunc { func basicAuth(next http.Handler) http.Handler {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
user, pass, _ := r.BasicAuth() user, pass, _ := r.BasicAuth()
@ -108,7 +129,7 @@ func basicAuth(next http.HandlerFunc) http.HandlerFunc {
#### Example: Admin role checker #### Example: Admin role checker
```go ```go
func requireAdmin(next http.HandlerFunc) http.HandlerFunc { func requireAdmin(next http.Handler) http.Handler {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
// Assume JWT in context with roles // Assume JWT in context with roles

View File

@ -1,3 +1,3 @@
module github.com/therootcompany/golib/http/middleware module github.com/therootcompany/golib/http/middleware/v2
go 1.24.6 go 1.24.6

View File

@ -16,21 +16,22 @@ import (
"slices" "slices"
) )
// Middleware receives and returns and http.HandlerFunc // Muxer can mux Handlers and HandleFuncs
type Middleware func(http.HandlerFunc) http.HandlerFunc type Muxer interface {
Handle(path string, handler http.Handler)
HandleFunc(path string, handle func(w http.ResponseWriter, r *http.Request))
}
// Middleware receives and returns and http.Handler
type Middleware func(http.Handler) http.Handler
// MiddlewareChain enables inline chaining // MiddlewareChain enables inline chaining
type MiddlewareChain struct { type MiddlewareChain struct {
middlewares []Middleware middlewares []Middleware
} }
// New creates a reusable MiddlewareChain with 0 or more middleware
func New(middlewares ...Middleware) MiddlewareChain {
return MiddlewareChain{middlewares: middlewares}
}
// Handle composes middleware with the final handler // Handle composes middleware with the final handler
func (c MiddlewareChain) Handle(handler http.HandlerFunc) http.HandlerFunc { func (c MiddlewareChain) Handle(handler http.Handler) http.Handler {
if handler == nil { if handler == nil {
panic("mw.New(...).Use(...).Handle(-->this<--) requires a handler") panic("mw.New(...).Use(...).Handle(-->this<--) requires a handler")
} }
@ -48,20 +49,6 @@ func (c MiddlewareChain) Handle(handler http.HandlerFunc) http.HandlerFunc {
return result return result
} }
// Use appends additional middleware to the chain
func (c MiddlewareChain) Use(middlewares ...Middleware) MiddlewareChain {
newMiddlewares := make([]Middleware, len(c.middlewares), len(c.middlewares)+len(middlewares))
copy(newMiddlewares, c.middlewares)
newMiddlewares = append(newMiddlewares, middlewares...)
return MiddlewareChain{middlewares: newMiddlewares}
}
type Muxer interface {
Handle(path string, handler http.Handler)
HandleFunc(path string, handle func(w http.ResponseWriter, r *http.Request))
}
// MiddlewareMux enables inline chaining // MiddlewareMux enables inline chaining
type MiddlewareMux struct { type MiddlewareMux struct {
middlewares []Middleware middlewares []Middleware
@ -89,15 +76,15 @@ func (c MiddlewareMux) With(middlewares ...Middleware) MiddlewareMux {
} }
func (c MiddlewareMux) Handle(path string, handler http.Handler) { func (c MiddlewareMux) Handle(path string, handler http.Handler) {
c.mux.Handle(path, c.handle(handler.ServeHTTP)) c.mux.Handle(path, c.handle(handler))
} }
func (c MiddlewareMux) HandleFunc(path string, handler http.HandlerFunc) { func (c MiddlewareMux) HandleFunc(path string, handler http.HandlerFunc) {
c.mux.HandleFunc(path, c.handle(handler)) c.mux.Handle(path, c.handle(handler))
} }
// Handle composes middleware with the final handler // Handle composes middleware with the final handler
func (c MiddlewareMux) handle(handler http.HandlerFunc) http.HandlerFunc { func (c MiddlewareMux) handle(handler http.Handler) http.Handler {
if handler == nil { if handler == nil {
panic("mw.New(...).Use(...).Handle(-->this<--) requires a handler") panic("mw.New(...).Use(...).Handle(-->this<--) requires a handler")
} }