http.middleware
import http
httpexposes this ashttp.middleware, soimport httpis enough and the names are called ashttp.middleware.*.import http.middlewarereaches the same definitions directly.
The middleware every public HTTP service ends up needing: CORS, access logging, the security headers a browser acts on, authentication, and a request rate limit.
Each function here returns a middleware - a function of (request, response, next)
- ready to hand to
HttpServer.use():
import http
import http.middleware
var server = http.server(3000)
server.use(middleware.logger())
server.use(middleware.security_headers())
server.use(middleware.cors({ origins: ['https://app.example.com'] }))
Order matters: middleware run outermost first, so a logger added first sees the final status of everything added after it.
Functions
cors()
http.middleware.cors(options: ?dict) -> function(3)
Answers CORS preflights and adds the cross-origin headers a browser needs before it will let script read a response from another origin.
origins is the list of origins allowed, matched exactly. The literal
'*' allows any origin, but only for requests that carry no
credentials: a wildcard together with Access-Control-Allow-Credentials
is rejected by every browser, so setting credentials reflects the
request’s own origin instead - which means the origins list becomes
the only thing standing between an attacker’s page and an authenticated
response, and it needs to be an explicit list rather than a wildcard.
Parameters
options(?dict) —origins(default['*']),methods,headers(allowed request headers; defaults to reflecting what was asked for),expose(response headers script may read),credentials(defaultfalse),max_age(seconds a preflight may be cached, default 86400)
Returns function(3)
logger()
http.middleware.logger(options: ?dict) -> function(3)
Writes one line per request once the response is finished.
The default format is the Common Log Format extended with the response
time, which every log analyser already understands. Pass format to
build the line yourself, or sink to send it somewhere other than
standard output.
Parameters
options(?dict) —sink(a function taking the line),format(a function taking(request, response, milliseconds)and returning the line),trust_proxy(whether to log the forwarded client address rather than the peer)
Returns function(3)
security_headers()
http.middleware.security_headers(options: ?dict) -> function(3)
Adds the response headers a browser acts on to harden a page.
Every one of them is opt-out, because the right value depends on the application:
| Header | Default | What it does |
|---|---|---|
X-Content-Type-Options | nosniff | stops the browser second-guessing a declared content type |
X-Frame-Options | DENY | refuses to be framed, which is what clickjacking needs |
Referrer-Policy | strict-origin-when-cross-origin | keeps paths and queries out of outbound referrers |
Strict-Transport-Security | one year | only sent over HTTPS, where it is meaningful |
Content-Security-Policy | not set | too application-specific to guess at |
Parameters
options(?dict) — any of the header values above by lowercase name (content_security_policy,frame_options,referrer_policy,hsts_max_age,hsts_subdomains,content_type_options), eachnilto omit
Returns function(3)
basic_auth()
http.middleware.basic_auth(verify, realm: ?string) -> function(3)
Requires HTTP Basic authentication.
verify is called with the username and password and returns whether
they are acceptable; it should compare secrets with
http.util.secure_equals() rather than ==, so that a wrong guess and
a nearly-right one take the same time.
A nil verifier disables the middleware: it passes every request
straight through rather than refusing one. That makes blanking the
verifier a way to switch authentication off without unpicking the chain
around it.
Parameters
verify(?function(2)) —nilto disablerealm(?string) — the realm named in the challenge; defaults to'Restricted'
Returns function(3)
bearer_auth()
http.middleware.bearer_auth(verify, realm: ?string) -> function(3)
Requires a bearer token.
A nil verifier disables the middleware, as with basic_auth().
Parameters
verify(?function(1)) — called with the token; returns whether it is acceptable, or a value to attach torequest.contextas'user'.nilto disablerealm(?string)
Returns function(3)
parse_basic()
http.middleware.parse_basic(header: string)
Parses an HTTP Basic Authorization header into a username and
password.
Parameters
header(string)
Returns — ?list: [username, password], or nil if the header is
not a well-formed Basic credential
parse_bearer()
http.middleware.parse_bearer(header: string) -> ?string
Parses a Bearer Authorization header into its token.
Parameters
header(string)
Returns ?string
jwt_auth()
http.middleware.jwt_auth(verifier, options: ?dict) -> function(3)
Requires a valid JSON Web Token, verified by the jwt module.
verifier is either a jwt.Verifier, or any function taking the token
and returning its claims:
import http.middleware
import jwt
server.use(middleware.jwt_auth(
jwt.Verifier(secret, { algorithms: ['HS256'], audience: 'api' })
))
The function form is what covers a key set resolved by kid, or
anything else the jwt module can do that a fixed verifier cannot:
server.use(middleware.jwt_auth(@(token) {
return jwt.verify_with_jwks(token, keys, { audience: 'api' })
}))
On success the claims land on request.context['claims'], and the sub
claim - the usual place an issuer puts the account the token speaks for
- on
request.context['user'].
Failures follow RFC 6750 §3: a request with no token is answered 401
with a bare Bearer challenge, one whose token does not verify is
answered 401 with error="invalid_token", and one whose token is
valid but lacks a required scope is answered 403 with
error="insufficient_scope".
A nil verifier disables the middleware: every request passes straight
through, unauthenticated. Nothing here raises, so registering it never
needs a catch block around it.
Parameters
verifier(Verifier|function(1)|nil) —nilto disableoptions(?dict) —realm(default'api'),optional(attach the claims when a valid token is present but do not refuse a request without one), andscopes(a list every token must carry)
Returns function(3)
Note: Like
HttpRequest.validate(), this deliberately does not import thejwtmodule. The verifier is built by the caller, which keeps the token format entirely the application’s business and means a server that authenticates nothing never pays to load it.
rate_limit()
http.middleware.rate_limit(options: ?dict) -> function(3)
Limits how many requests one client may make in a window of time.
The counter lives in memory, so it is per worker: with workers
isolates the effective limit is limit times workers. That is a
deliberate trade - a shared counter would need shared state, and this is
meant to blunt a runaway client rather than to meter billing.
Parameters
options(?dict) —limit(requests per window, default 60),window(seconds, default 60),key(a function of the request returning the bucket key; defaults to the client address),trust_proxy
Returns function(3)
request_id()
http.middleware.request_id(header: ?string) -> function(3)
Attaches a unique identifier to every request, echoing back one the client supplied so a trace can be followed across services.
The identifier lands on request.context['request_id'] and in the
response’s X-Request-Id.
Parameters
header(?string) — the header to read and write; defaults to'X-Request-Id'
Returns function(3)
force_https()
http.middleware.force_https(options: ?dict) -> function(3)
Sends every request that arrived over cleartext to the same URL over HTTPS.
Parameters
options(?dict) —status(default308, which preserves the method),port(the HTTPS port, when it is not 443)
Returns function(3)
etag()
http.middleware.etag(min_size: ?number) -> function(3)
Computes a weak ETag over a finished response body and answers 304 Not Modified
when the client already has that version.
A handler that already set its own ETag is left alone - it knows
something about the resource that hashing the bytes does not.
Parameters
min_size(?number) — bodies smaller than this are not tagged; defaults to 128 bytes
Returns function(3)
2026, Richard Ore and Zuri contributors