Documentation
¶
Overview ¶
Package middleware provides composable HTTP middleware for web applications.
All middleware follow Go's standard net/http handler wrapping pattern:
type Middleware func(h http.Handler) http.Handler
Multiple middleware can be composed into a single middleware using CreateStack, which applies them in the order provided so that the first argument is the outermost wrapper and therefore the first to execute on each request.
Available middleware:
- NewLoggingMiddleware: structured request logging via log/slog, recording method, path, status code, and duration.
- NewMaxBytesReader: limits request body size to prevent resource exhaustion.
- NewSetContentType / NewSetContentTypeJSON: sets the Content-Type response header.
- NewStripHTMLExtension: rewrites ".html" paths to clean URLs before routing.
Example — composing a middleware stack for a JSON API:
stack := middleware.CreateStack(
middleware.NewLoggingMiddleware(logger),
middleware.NewMaxBytesReader(1024*1024), // 1 MB
middleware.NewSetContentTypeJSON(),
)
http.ListenAndServe(":8080", stack(mux))
Example ¶
Example demonstrates a realistic JSON API middleware stack.
package main
import (
"fmt"
"log/slog"
"net/http"
"os"
"github.com/harrydayexe/GoWebUtilities/middleware"
)
func main() {
// Create a logger for production use
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
// Create a typical middleware stack for JSON APIs
// Order: logging (first) -> size limiting -> content-type (last)
apiStack := middleware.CreateStack(
middleware.NewLoggingMiddleware(logger),
middleware.NewMaxBytesReader(10*1024*1024), // 10MB limit
middleware.NewSetContentTypeJSON(),
)
// Create a mux and add API endpoints
mux := http.NewServeMux()
// User endpoint
getUserHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"id":1,"name":"Alice"}`))
})
// Apply middleware to all API routes
mux.Handle("/api/users/", apiStack(getUserHandler))
fmt.Println("JSON API server configured with logging, size limiting, and content-type middleware")
}
Output: JSON API server configured with logging, size limiting, and content-type middleware
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Middleware ¶
Middleware is a function that wraps an http.Handler, providing functionality before and after execution of the wrapped handler.
func CreateStack ¶
func CreateStack(xs ...Middleware) Middleware
CreateStack composes multiple middleware into a single middleware. Middleware are applied in the order provided: the first middleware in the list will be the outermost wrapper (executed first on the request).
Example ¶
ExampleCreateStack demonstrates composing multiple middleware.
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"github.com/harrydayexe/GoWebUtilities/middleware"
)
func main() {
// Create a middleware stack with multiple middleware
stack := middleware.CreateStack(
middleware.NewSetContentTypeJSON(),
middleware.NewMaxBytesReader(1024),
)
// Create a base handler
baseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message":"hello"}`))
})
// Apply the stack to the handler
wrappedHandler := stack(baseHandler)
// Create a test server
server := httptest.NewServer(wrappedHandler)
defer server.Close()
// Make a request
resp, err := http.Get(server.URL)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
fmt.Printf("Content-Type: %s\n", resp.Header.Get("Content-Type"))
}
Output: Content-Type: application/json
Example (Complete) ¶
ExampleCreateStack_complete demonstrates a complete HTTP server setup with middleware.
package main
import (
"fmt"
"io"
"log"
"log/slog"
"net/http"
"net/http/httptest"
"github.com/harrydayexe/GoWebUtilities/middleware"
)
func main() {
// Create a logger (discarding output for this example)
logger := slog.New(slog.NewJSONHandler(io.Discard, nil))
// Create a complete middleware stack
stack := middleware.CreateStack(
middleware.NewLoggingMiddleware(logger),
middleware.NewSetContentTypeJSON(),
middleware.NewMaxBytesReader(1024),
)
// Create a mux and add handlers
mux := http.NewServeMux()
apiHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message":"hello"}`))
})
// Apply middleware to the handler
mux.Handle("/api/", stack(apiHandler))
// Create a test server
server := httptest.NewServer(mux)
defer server.Close()
// Make a request
resp, err := http.Get(server.URL + "/api/test")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
fmt.Printf("Status: %d\n", resp.StatusCode)
fmt.Printf("Content-Type: %s\n", resp.Header.Get("Content-Type"))
}
Output: Status: 200 Content-Type: application/json
func NewLoggingMiddleware ¶
func NewLoggingMiddleware(logger *slog.Logger) Middleware
NewLoggingMiddleware returns middleware that logs HTTP requests. Logs include method, path, status code, and duration.
Example ¶
ExampleNewLoggingMiddleware demonstrates structured logging with middleware.
package main
import (
"fmt"
"io"
"log"
"log/slog"
"net/http"
"net/http/httptest"
"github.com/harrydayexe/GoWebUtilities/middleware"
)
func main() {
// Create a structured logger (logs to stderr in production)
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
// Create logging middleware
loggingMw := middleware.NewLoggingMiddleware(logger)
// Create a simple handler
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("success"))
})
// Apply the middleware
wrappedHandler := loggingMw(handler)
// Create a test server
server := httptest.NewServer(wrappedHandler)
defer server.Close()
// Make a request (logging happens automatically)
resp, err := http.Get(server.URL + "/api/test")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
fmt.Printf("Request logged with status: %d\n", resp.StatusCode)
}
Output: Request logged with status: 200
func NewMaxBytesReader ¶
func NewMaxBytesReader(maxBytes int64) Middleware
NewMaxBytesReader returns middleware that limits request body size to maxBytes. Bodies exceeding this limit will cause an error response. This prevents resource exhaustion from overly large requests. If maxBytes is 0, defaults to 1MB
Example ¶
ExampleNewMaxBytesReader demonstrates limiting request body size.
package main
import (
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
"strings"
"github.com/harrydayexe/GoWebUtilities/middleware"
)
func main() {
// Create middleware that limits request body to 512 bytes
limitMiddleware := middleware.NewMaxBytesReader(512)
// Create a handler that reads the request body
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Request body too large", http.StatusRequestEntityTooLarge)
return
}
fmt.Fprintf(w, "Received %d bytes", len(body))
})
// Apply the middleware
wrappedHandler := limitMiddleware(handler)
// Create a test server
server := httptest.NewServer(wrappedHandler)
defer server.Close()
// Test with small payload (should succeed)
smallPayload := strings.NewReader("small data")
resp, err := http.Post(server.URL, "text/plain", smallPayload)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Status: %d\n", resp.StatusCode)
fmt.Printf("Response: %s\n", body)
}
Output: Status: 200 Response: Received 10 bytes
func NewSetContentType ¶
func NewSetContentType(contentType string) Middleware
NewSetContentType returns middleware that sets the Content-Type header for all responses. Common values: "application/json", "text/html; charset=utf-8".
func NewSetContentTypeJSON ¶
func NewSetContentTypeJSON() Middleware
NewSetContentTypeJSON returns middleware that sets the Content-Type header to application/json. This is a convenience wrapper around NewSetContentType for JSON APIs. Apply this to route groups where all endpoints return JSON to avoid repetitive header setting in individual handlers.
Example ¶
ExampleNewSetContentTypeJSON demonstrates creating and using the JSON content-type middleware.
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"github.com/harrydayexe/GoWebUtilities/middleware"
)
func main() {
// Create a JSON content-type middleware
jsonMiddleware := middleware.NewSetContentTypeJSON()
// Create a simple handler that returns JSON data
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok"}`))
})
// Apply the middleware to the handler
wrappedHandler := jsonMiddleware(handler)
// Create a test server
server := httptest.NewServer(wrappedHandler)
defer server.Close()
// Make a request
resp, err := http.Get(server.URL)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
fmt.Printf("Content-Type: %s\n", resp.Header.Get("Content-Type"))
}
Output: Content-Type: application/json
func NewStripHTMLExtension ¶ added in v1.4.0
func NewStripHTMLExtension() Middleware
NewStripHTMLExtension returns middleware that rewrites incoming request paths by removing any trailing ".html" suffix before passing the request to the next handler. This allows file-based routers to serve clean URLs — e.g. a request for "/about.html" is handled as if the client requested "/about".
Index pages are handled as a special case: paths that end in "/index" after stripping (including the bare "/index.html" root path) are further trimmed to the parent directory path with a trailing slash. For example, "/about/index.html" becomes "/about/" and "/index.html" becomes "/".
When the URL contains percent-encoded characters that cause Go's HTTP parser to populate url.RawPath (most notably %2F, an encoded slash), RawPath is updated in sync with Path so that EscapedPath() and RequestURI() continue to return the rewritten path with correct encoding semantics.
Paths that do not end in ".html" are forwarded to the next handler unchanged. Query parameters and URL fragments are never modified.