diff --git a/http/middleware/README.md b/http/middleware/README.md index caa3b35..e2d8ca2 100644 --- a/http/middleware/README.md +++ b/http/middleware/README.md @@ -8,8 +8,8 @@ A simple zero-cost middleware handler for Go's native `net/http` `ServeMux`. \ Turns tedious this: ```go -mux.HandleFunc("GET /api/version", logRequests(timeRequests(getVersion))) -mux.HandleFunc("GET /api/data", logRequests(timeRequests(requireAuth(requireAdmin(getData))))) +mux.Handle("GET /api/version", logRequests(timeRequests(http.HandlerFunc(getVersion)))) +mux.Handle("GET /api/data", logRequests(timeRequests(requireAuth(requireAdmin(http.HandlerFunc(getData)))))) ``` Into organizable this: @@ -25,7 +25,7 @@ authMW.HandleFunc("GET /api/data", getData) Using stdlib this: ```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. \ @@ -49,12 +49,12 @@ import ( func main() { mux := http.NewServeMux() - mw := middleware.New(logRequests, basicAuth) - mux.HandleFunc("GET /api/data", mw.Handle(getData)) - mux.HandleFunc("POST /api/data", mw.Handle(postData)) + mw := middleware.WithMux(mux, recoverPanics, logRequests, basicAuth) + mux.HandleFunc("GET /api/data", getData) + mux.HandleFunc("POST /api/data", postData) adminMW := mw.Use(requireAdmin) - mux.HandleFunc("DELETE /api/data", adminMW.Handle(deleteData)) + adminMW.HandleFunc("DELETE /api/data", deleteData) http.ListenAndServe(":8080", mux) } @@ -62,16 +62,37 @@ func main() { ### 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 -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 ```go -func logRequests(next http.HandlerFunc) http.HandlerFunc { +func logRequests(next http.Handler) http.Handler { return func(w http.ResponseWriter, r *http.Request) { start := time.Now() @@ -91,7 +112,7 @@ var creds = envauth.BasicCredentials{ 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) { user, pass, _ := r.BasicAuth() @@ -108,7 +129,7 @@ func basicAuth(next http.HandlerFunc) http.HandlerFunc { #### Example: Admin role checker ```go -func requireAdmin(next http.HandlerFunc) http.HandlerFunc { +func requireAdmin(next http.Handler) http.Handler { return func(w http.ResponseWriter, r *http.Request) { // Assume JWT in context with roles diff --git a/http/middleware/go.mod b/http/middleware/go.mod index 720f8ab..8739efc 100644 --- a/http/middleware/go.mod +++ b/http/middleware/go.mod @@ -1,3 +1,3 @@ -module github.com/therootcompany/golib/http/middleware +module github.com/therootcompany/golib/http/middleware/v2 go 1.24.6 diff --git a/http/middleware/middleware.go b/http/middleware/middleware.go index 49fd5f9..51c39ce 100644 --- a/http/middleware/middleware.go +++ b/http/middleware/middleware.go @@ -16,21 +16,22 @@ import ( "slices" ) -// Middleware receives and returns and http.HandlerFunc -type Middleware func(http.HandlerFunc) http.HandlerFunc +// Muxer can mux Handlers and HandleFuncs +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 type MiddlewareChain struct { 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 -func (c MiddlewareChain) Handle(handler http.HandlerFunc) http.HandlerFunc { +func (c MiddlewareChain) Handle(handler http.Handler) http.Handler { if handler == nil { panic("mw.New(...).Use(...).Handle(-->this<--) requires a handler") } @@ -48,20 +49,6 @@ func (c MiddlewareChain) Handle(handler http.HandlerFunc) http.HandlerFunc { 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 type MiddlewareMux struct { middlewares []Middleware @@ -89,15 +76,15 @@ func (c MiddlewareMux) With(middlewares ...Middleware) MiddlewareMux { } 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) { - c.mux.HandleFunc(path, c.handle(handler)) + c.mux.Handle(path, c.handle(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 { panic("mw.New(...).Use(...).Handle(-->this<--) requires a handler") }