Middleware
Middleware runs before and after route handlers. Use it for authentication, logging, compression, and rate limiting.
How the chain works
- Global middleware registered with
server.use()runs first, in registration order. - Group and route middleware runs next.
- If
process_requestreturns anHTTPResponse, the chain stops and the response returns to the client. - Return
nullfromprocess_requestto continue. process_responsemethods 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— serveindex.htmlfor unknown non-file paths.enable_streaming/stream_threshold_bytes— stream large files in chunks.serve_precompressed— serve a.gzsibling withContent-Encoding: gzipwhen accepted.