Developing Custom HTTP Middleware for Traefik
HTTP Middleware Concepts
In Traefik's architecture, middleware components attach to routers and modify requests before they reach backend services, or transform responses before returning them to clients. This pattern aligns with standard HTTP middleware concepts where the primary goal is intercepting and manipulating HTTP request/response cycles.
Configuration Structure
Traefik provides numerous built-in middleware options configurable through YAML or TOML files. All middleware definitions reside under the http.middlewares namespace. The following example demonstrates the addPrefix middleware:
http:
routers:
myRouter:
service: backendService
middlewares:
- "strip-api-prefix"
rule: "Host(`api.example.com`)"
middlewares:
strip-api-prefix:
addPrefix:
prefix: "/api/v1"
services:
backendService:
loadBalancer:
servers:
- url: "http://localhost:8080"Core Development Components
Building custom middleware requires implementing three fundamental elements:
- Configuration Structure: A Go struct that maps to configuration file entries
- Handler Implementation: A struct implementing the
http.Handlerinterface containing the middleware logic - Constructor Function: A factory function that instantiates the handler with configuration values
Implementing Configuration
Navigate to /pkg/config/dynamic/middlewares.go and define the configuration struct. The built-in AddPrefix middleware provides a reference implementation:
type AddPrefix struct {
Prefix string `json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty" export:"true"`
}Struct tags define how fields map to configuration keys. After defining the configuration struct, register it within the Middleware struct in the same file:
type Middleware struct {
AddPrefix *AddPrefix `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty" export:"true"`
// additional middleware configurations...
}This structure establishes the configuration hierarchy where middleware-specific settings appear under http.middlewares.[middleware-name].addPrefix.
Implementing the Handler
Create a new package under /pkg/middlewares/ directory. For example, the addPrefix middleware resides at /pkg/middlewares/addprefix/add_prefix.go. The handler struct must include a next field of type http.Handler representing the subsequent handler in the chain:
type prefixHandler struct {
next http.Handler
pathValue string
label string
}
func (p *prefixHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// Middleware logic here
p.next.ServeHTTP(rw, req)
}Invoking next.ServeHTTP() passes control to the next middleware. Omitting this call terminates the request chain, enabling flow control scenarios such as authentication failures or request blocking.
Constructor Implementation
Factory Function
Define a New function within the handler's package that returns an initialized handler instance:
func New(ctx context.Context, next http.Handler, cfg dynamic.AddPrefix, label string) (http.Handler, error) {
handler := &prefixHandler{
next: next,
pathValue: cfg.Prefix,
label: label,
}
return handler, nil
}Parameters include:
ctx: Context for logging and cancellation signalsnext: The downstream handler in the middleware chaincfg: Configuration struct populated from file valueslabel: The middleware name from configuration
Registration
Wire the constructor in /pkg/server/middleware/middlewares.go within the buildConstructor function:
func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (alice.Constructor, error) {
var middleware alice.Constructor
if config.AddPrefix != nil {
middleware = func(next http.Handler) (http.Handler, error) {
return addprefix.New(ctx, next, *config.AddPrefix, middlewareName)
}
}
return tracing.Wrap(ctx, middleware), nil
}The registration process validates configuration presence, checks for errors, and wraps the constructor with tracing support.
Implementation Guidelines
- Always invoke
next.ServeHTTP()unless intentionally terminating the request chain - Follow existing Traefik conventions for package structure and naming patterns
- Return meaningful errors from constructors for configuration validation failures