Middleware

Middleware runs before and after route handlers. Use it for authentication, logging, compression, and rate limiting.

How the chain works

  1. Global middleware registered with server.use() runs first, in registration order.
  2. Group and route middleware runs next.
  3. If process_request returns an HTTPResponse, the chain stops and the response returns to the client.
  4. Return null from process_request to continue.
  5. process_response methods run in reverse order after the handler returns.

Write custom middleware

Extend HTTPMiddleware and override both methods:

class_name AuthMiddleware
extends HTTPMiddleware

func process_request(request: HTTPMessage) -> Variant:
    var token := request.header("authorization")
    if token.is_empty():
        return HTTPResponse.unauthorized("Missing auth token")
    return null  # Continue the chain

func process_response(_request: HTTPMessage, response: HTTPResponse) -> HTTPResponse:
    response.set_header("X-Powered-By", "GodotHTTPServer")
    return response

Register middleware

Attach middleware globally, per group, or per route:

# Global, runs for every request
server.use(LoggingMiddleware.new())
server.use(CORSMiddleware.new(["*"]))

# Scoped to a route group
server.router.group("/api", func(r: HTTPRouter):
    r.handle_get("/secure", _handle_secure, null, [AuthMiddleware.new()])
)

# Scoped to a single route
server.router.handle_get("/admin", _handle_admin, null, [AuthMiddleware.new()])

Built-in middleware

CORS

Restrict allowed origins:

server.use(CORSMiddleware.new(["https://example.com"]))

Logging

Log each request and response. Add the middleware first so it wraps the whole chain:

server.use(LoggingMiddleware.new())

Rate limiting

RateLimitMiddleware uses a token bucket per client IP. Pass the request limit and the window in seconds:

server.use(RateLimitMiddleware.new(100, 60.0))  # 100 requests per 60 seconds

Exceeded requests return 429 Too Many Requests with a Retry-After header.

Compression

CompressionMiddleware applies GZIP to eligible responses:

server.use(CompressionMiddleware.new())

Basic auth

BasicAuthMiddleware validates a Authorization: Basic header. Use a static credential map or a validator callback:

var auth := BasicAuthMiddleware.new("My Realm")
auth.credentials = {"admin": "secret"}
server.use(auth)

# Or a callback: (username, password) -> bool
server.use(BasicAuthMiddleware.new("My Realm", func(u, p): return u == "bob"))

Failures return 401 Unauthorized with a WWW-Authenticate challenge.

Bearer auth

BearerAuthMiddleware validates a Authorization: Bearer token against a callback or a static list:

server.use(BearerAuthMiddleware.new(verify_token_callback))
server.use(BearerAuthMiddleware.new(Callable(), ["static-token"]))

API key

ApiKeyMiddleware reads a key from a header or query parameter:

server.use(ApiKeyMiddleware.new(["key1", "key2"]))

Sessions

SessionMiddleware stores data server-side, keyed by an HMAC-signed cookie:

server.use(SessionMiddleware.new("some-secret"))

# In a handler
session_mw.set_session(request, {"user": "alice"})
var data := session_mw.get_session(request)
session_mw.destroy_session(request)

Request IDs

RequestIdMiddleware generates an X-Request-Id, stores it in the request metadata, and echoes it on the response:

server.use(RequestIdMiddleware.new())

Metrics

MetricsMiddleware exposes request counts and cumulative latency in Prometheus text format:

server.use(MetricsMiddleware.new())  # GET /metrics

Static files

StaticFileServer is not a middleware class. Wrap it in your own middleware:

class_name StaticFileMiddleware
extends HTTPMiddleware

var static_files := StaticFileServer.new("res://web")

func process_request(request: HTTPMessage) -> Variant:
    if request.method == "GET" and request.clean_path.begins_with("/static/"):
        return static_files.handle(request)
    return null

Register it:

server.use(StaticFileMiddleware.new())

The static server adds ETag and Last-Modified caching, Cache-Control, and Accept-Ranges byte-range support for media streaming. Options on the StaticFileServer instance:

  • index_file — the index document for directory roots.
  • enable_directory_listing — list directory contents when no index file exists.
  • enable_spa_fallback — serve index.html for unknown non-file paths.
  • enable_streaming / stream_threshold_bytes — stream large files in chunks.
  • serve_precompressed — serve a .gz sibling with Content-Encoding: gzip when accepted.