Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Developing Custom HTTP Middleware for Traefik

Tech May 19 2

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:

  1. Configuration Structure: A Go struct that maps to configuration file entries
  2. Handler Implementation: A struct implementing the http.Handler interface containing the middleware logic
  3. 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 signals
  • next: The downstream handler in the middleware chain
  • cfg: Configuration struct populated from file values
  • label: 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

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.