middleware

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 29, 2025 License: MIT Imports: 8 Imported by: 0

README

Go HTTP middleware

Contains a collection of generally useful middlewares for use with Go's built-in net/http server.

Middleware

Cache Control

Automatically sets a Cache-Control header with a max-age based on the Content-Type header. By default, static assets like images, videos, and downloads get a max-age of 1 year, while text assets like HTML and CSS get a max-age of 1 hour.

package main

import (
	"net/http"
	"time"

	"github.com/csmith/middleware"
)

func main() {
	mux := http.NewServeMux()

	// With default cache times
	http.ListenAndServe(":8080", middleware.CacheControl()(mux))

	// With custom cache times
	http.ListenAndServe(":8080", middleware.CacheControl(middleware.WithCacheTimes(map[string]time.Duration{
		"application/json": time.Duration(0),
		"image/*":          time.Hour * 24,
		"text/css":         time.Hour * 12,
	}))(mux))
}
Compress

Automatically compresses the response body if the client accepts gzip encoding. Supports configurable compression levels and handles Accept-Encoding headers with quality values.

package main

import (
	"compress/gzip"
	"net/http"

	"github.com/csmith/middleware"
)

func main() {
	mux := http.NewServeMux()

	// With default compression level
	http.ListenAndServe(":8080", middleware.Compress()(mux))

	// With custom compression level
	http.ListenAndServe(":8080", middleware.Compress(middleware.WithGzipLevel(gzip.BestSpeed))(mux))
	
	// With additional custom logic for disabling compression on certain requests 
	http.ListenAndServe(":8080", middleware.Compress(middleware.WithCompressionCheck(func(r *http.Request) bool {
		return r.URL.Path != "/special"
	}))(mux))
}
Chain

Allows you to chain other middleware together, without directly chaining the function calls.

package main

import (
	"net/http"

	"github.com/csmith/middleware"
)

func main() {
	mux := http.NewServeMux()
	notFoundHandler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
        // ...
	})

	chain := middleware.Chain(middleware.WithMiddleware(
		// Innermost
		middleware.CacheControl(),
		middleware.ErrorHandler(middleware.WithErrorHandler(http.StatusNotFound, notFoundHandler)),
		middleware.TextLog(middleware.WithTextLogFormat(middleware.TextLogFormatCombined)),
		middleware.Recover(),
		// Outermost
    ))
	http.ListenAndServe(":8080", chain(mux))
}
Cross Origin Protection

Defends against CSRF attacks by denying unsafe requests that originated from a different origin. GET, HEAD and OPTIONS requests are always allowed. Any other request has its Sec-Fetch-Site header verified. If present, it must either be same-origin or none for the request to proceed.

Denied requests are responded to with a 403 response with no body. Chain this middleware with Error Handler to customise this.

package main

import (
	"net/http"

	"github.com/csmith/middleware"
)

func main() {
	mux := http.NewServeMux()

	http.ListenAndServe(":8080", middleware.CrossOriginProtection()(mux))
}
Error Handler

Handles HTTP status codes by invoking custom handlers. When a registered status code is returned by the next handler, its response is dropped and replaced with the custom handler's response.

package main

import (
	"net/http"

	"github.com/csmith/middleware"
)

func main() {
	mux := http.NewServeMux()

	notFoundHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte("Custom 404 page"))
	})

	serverErrorHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Custom 500 page"))
	})

	handler := middleware.ErrorHandler(
		// Add one more handlers for specific status codes
		middleware.WithErrorHandler(http.StatusNotFound, notFoundHandler),
		middleware.WithErrorHandler(http.StatusInternalServerError, serverErrorHandler),
		// If you want to preserve headers set by the original handler
		middleware.WithClearHeadersOnError(false),
	)(mux)

	http.ListenAndServe(":8080", handler)
}
Headers

Adds headers to a response as late as possible. This may be useful when chained with other middleware such as ErrorHandler that change headers.

package main

import (
	"net/http"

	"github.com/csmith/middleware"
)

func main() {
	mux := http.NewServeMux()

	handler := middleware.Headers(
		middleware.WithHeader("X-Frame-Options", "DENY"),
		middleware.WithHeader("X-Content-Type-Options", "nosniff"),
		middleware.WithHeader("Cache-Control", "no-cache"),
		middleware.WithHeader("Cache-Control", "no-store"), // Multiple values for same key
	)(mux)

	http.ListenAndServe(":8080", handler)
}
Real Address

Gets the real address of the client by parsing X-Forwarded-For headers from trusted proxies. By default only private IP ranges are trusted. Sets the RemoteAddr field of the request to the first untrusted hop (or the last hop if they're all trusted).

package main

import (
	"net"
	"net/http"

	"github.com/csmith/middleware"
)

func main() {
	mux := http.NewServeMux()

	// With default options
	http.ListenAndServe(":8080", middleware.RealAddress()(mux))

	// With custom trusted proxies
	var trustedProxies []net.IPNet // Populate appropriately
	http.ListenAndServe(":8080", middleware.RealAddress(middleware.WithTrustedProxies(trustedProxies))(mux))
}
Recover

Recovers from downstream panics by logging them and returning a 500 error to the client.

package main

import (
	"log/slog"
	"net/http"

	"github.com/csmith/middleware"
)

func main() {
	mux := http.NewServeMux()

	// With default options
	http.ListenAndServe(":8080", middleware.Recover()(mux))

	// With custom logger
	http.ListenAndServe(":8080", middleware.Recover(middleware.WithPanicLogger(func(r *http.Request, err any) {
		slog.Error("Panic serving request", "err", err, "url", r.URL)
	}))(mux))
}
Text Log

Logs details of each request in either Common Log Format or Combined Log Format.

package main

import (
	"net/http"
	"os"

	"github.com/csmith/middleware"
)

func main() {
	mux := http.NewServeMux()

	// With default options (Common Log Format to stdout)
	http.ListenAndServe(":8080", middleware.TextLog()(mux))

	// With Combined Log Format
	http.ListenAndServe(":8080", middleware.TextLog(middleware.WithTextLogFormat(middleware.TextLogFormatCombined))(mux))

	// With custom sink
	file, _ := os.OpenFile("access.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	http.ListenAndServe(":8080", middleware.TextLog(middleware.WithTextLogSink(func(line string) {
		file.WriteString(line + "\n")
	}))(mux))
}
Strip Trailing Slashes

Removes trailing slashes from request URLs

package main

import (
	"net/http"

	"github.com/csmith/middleware"
)

func main() {
	mux := http.NewServeMux()

	// Requests to /foo/ will be served as /foo
	http.ListenAndServe(":8080", middleware.StripTrailingSlashes()(mux))
}
Redirect Trailing Slashes

Redirects URLs without trailing slashes to their equivalent with a trailing slash

package main

import (
	"net/http"

	"github.com/csmith/middleware"
)

func main() {
	mux := http.NewServeMux()

	// With default redirect code 308 Permanent Redirect
	http.ListenAndServe(":8080", middleware.RedirectTrailingSlashes()(mux))

	// With custom redirect code 307 Temporary Redirect)
	http.ListenAndServe(":8080", middleware.RedirectTrailingSlashes(
		middleware.WithRedirectCode(http.StatusTemporaryRedirect),
	)(mux))
}

Issues/Contributing/etc

Bug reports, feature requests, and pull requests are all welcome.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CacheControl

func CacheControl(opts ...CacheControlOption) func(http.Handler) http.Handler

CacheControl is a middleware that automatically sets a Cache-Control header with a max-age based on the Content-Type header set by the next handler.

By default "static" assets like images, videos, and downloads will have a max age of 1 year, while text assets like HTML and CSS will have a max-age of 1 hour. Use WithCacheTimes to pass custom times.

If the upstream handler sets the Cache-Control header, it will not be changed by this middleware.

func Chain

func Chain(opts ...ChainOption) func(http.Handler) http.Handler

Chain is a middleware that chains together other middlewares (i.e., invokes them in order). Add middlewares using the WithMiddleware option.

Middlewares are invoked in the order supplied, i.e., the first one passed will be invoked with the upstream http.Handler (it will be the innermost), then the second one will be given the result of the first middleware (wrapping it), and so on.

i.e., a chain of `A`, `B`, and `C` is equivalent to `C(B(A(next)))`.

func Compress

func Compress(opts ...CompressOption) func(http.Handler) http.Handler

Compress is a middleware that automatically compresses the response body if the client will accept it. It supports gzip encoding.

If an invalid gzip level is set with WithGzipLevel, requests will be silently served with no compression.

func CrossOriginProtection

func CrossOriginProtection() func(http.Handler) http.Handler

CrossOriginProtection is a middleware that denies unsafe requests that originated from a different origin, to defend against CSRF attacks.

GET, HEAD and OPTIONS requests are always allowed. Any other request has its Sec-Fetch-Site header verified. If present, it must either be "same-origin" or "none" for the request to proceed.

Denied requests are responded to with a 403 response with no body. Chain this middleware with ErrorHandler to customise this.

func ErrorHandler

func ErrorHandler(opts ...ErrorHandlerOption) func(http.Handler) http.Handler

ErrorHandler is a middleware that handles HTTP status codes by invoking a custom handler. Specific error codes can be handled by calling WithErrorHandler. If the next handler writes a status code that has a registered handler, its response will be dropped.

func Headers

func Headers(opts ...HeadersOption) func(http.Handler) http.Handler

Headers is a middleware that adds headers to a response as late as possible. This may be useful when chained with other middleware such as ErrorHandler that change headers.

func RealAddress

func RealAddress(opts ...RealAddressOption) func(http.Handler) http.Handler

RealAddress is a middleware that sets the RemoteAddr property on the http.Request to the client's real IP address according to the X-Forwarded-For header.

By default, only proxies on private IP addresses will be trusted. If you need to trust other addresses, use the WithTrustedProxies option.

func Recover

func Recover(opts ...RecoverOption) func(http.Handler) http.Handler

Recover is a middleware that will recover from downstream panics, log the error, and send a 500 response to the client.

func RedirectTrailingSlashes added in v1.1.0

func RedirectTrailingSlashes(opts ...RedirectTrailingSlashesOption) func(http.Handler) http.Handler

RedirectTrailingSlashes is a middleware that redirects URLs without trailing slashes to include one. Uses a 308 Permanent Redirect status code by default.

func StripTrailingSlashes added in v1.1.0

func StripTrailingSlashes() func(http.Handler) http.Handler

StripTrailingSlashes is a middleware that removes trailing slashes from URLs.

func TextLog

func TextLog(opts ...TextLogOption) func(http.Handler) http.Handler

TextLog logs details of each request in a textual format.

By default each request will be logged to stdout in the 'common' log format. Use WithTextLogSink to handle the log lines differently, and WithTextLogFormat to change the format.

Types

type CacheControlOption

type CacheControlOption func(*cacheControlConfig)

func WithCacheTimes

func WithCacheTimes(cacheTimes map[string]time.Duration) CacheControlOption

WithCacheTimes allows setting a custom cache time policy for the CacheControl middleware.

cacheTimes is a map of mime types to the max-age that they should be cached. The `*` character can be used in place of a subtype (e.g. `image/*`) to match all subtypes. More specific entries will be used before wildcard entries.

type ChainOption

type ChainOption func(*chainConfig)

func WithMiddleware

func WithMiddleware(middleware ...func(http.Handler) http.Handler) ChainOption

WithMiddleware appends one or more middleware to the chain.

type CompressOption

type CompressOption func(*compressConfig)

func WithCompressionCheck

func WithCompressionCheck(check func(*http.Request) bool) CompressOption

WithCompressionCheck sets a function to determine if a request should be compressed. The function should return true if compression should be applied, false otherwise. Compression is still subject to the client sending the appropriate Accent-Encoding header.

func WithGzipLevel

func WithGzipLevel(level int) CompressOption

WithGzipLevel sets the compression level for gzip encoding

type ErrorHandlerOption

type ErrorHandlerOption func(*errorHandlerConfig)

func WithClearHeadersOnError

func WithClearHeadersOnError(clearHeaders bool) ErrorHandlerOption

WithClearHeadersOnError sets whether or not the headers should be cleared when a custom handler is invoked. True by default.

func WithErrorHandler

func WithErrorHandler(statusCode int, handler http.Handler) ErrorHandlerOption

WithErrorHandler registers a handler to be invoked when the specified status code is returned by the next handler in the chain.

type HeadersOption

type HeadersOption func(*headersConfig)

func WithHeader

func WithHeader(key, value string) HeadersOption

WithHeader specifies one header to be added to a request. The same key can be used multiple times, resulting in multiple headers being set.

type RealAddressOption

type RealAddressOption func(*realAddressConfig)

func WithTrustedProxies

func WithTrustedProxies(trustedProxies []net.IPNet) RealAddressOption

WithTrustedProxies configures the IP ranges that RealAddress will accept X-Forwarded-For hops from.

type RecoverOption

type RecoverOption func(*recoverConfig)

func WithPanicLogger

func WithPanicLogger(logger RecoverPanicLogger) RecoverOption

WithPanicLogger configures the logger that Recover will use to log the details of the panic.

type RecoverPanicLogger

type RecoverPanicLogger func(r *http.Request, err any)

type RedirectTrailingSlashesOption added in v1.1.0

type RedirectTrailingSlashesOption func(*redirectTrailingSlashesConfig)

func WithRedirectCode added in v1.1.0

func WithRedirectCode(code int) RedirectTrailingSlashesOption

WithRedirectCode sets the HTTP status code to use for redirects. Defaults to 308 Permanent Redirect if not specified.

type TextLogFormat

type TextLogFormat int
const (
	// TextLogFormatCommon is the "Common Log Format" as used by Apache
	TextLogFormatCommon TextLogFormat = iota
	// TextLogFormatCombined is the "Combined Log Format" as used by Apache and Nginx
	TextLogFormatCombined
)

type TextLogOption

type TextLogOption func(*textLogConfig)

func WithTextLogFormat

func WithTextLogFormat(format TextLogFormat) TextLogOption

WithTextLogFormat specifies the log format used by TextLog.

func WithTextLogSink

func WithTextLogSink(sink func(string)) TextLogOption

WithTextLogSink specifies where logs should be written to by TextLog.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL