Skip to content

Deployment

The built-in server is for development

app.run() starts Veloce's own HTTP server. It is convenient for local development — one call, no extra dependency — but it is not intended to face production traffic, and run() logs a reminder to that effect on startup.

For production, run the app under a hardened ASGI server. Veloce is a plain ASGI application (it implements __call__(scope, receive, send)), so any ASGI server works:

uvicorn your_module:app --host 0.0.0.0 --port 8000 --workers 4

Uvicorn (or Hypercorn, Granian, …) brings battle-tested HTTP/1.1 and WebSocket handling, worker management, graceful reloads, and TLS. The built-in server covers the development inner loop; production should not depend on it.

What the built-in server does and doesn't do

The development server does apply a slowloris guard — once a request's bytes start arriving, the whole request must complete within HttpProtocol.REQUEST_TIMEOUT seconds (default 30) or the connection is dropped with 408 — and an idle keep-alive timeout.

It serves HTTP/1.1 only — it performs no WebSocket upgrade handshake and does not implement HTTP/2. Run WebSocket routes and HTTP/2 workloads under uvicorn, which implements both; Veloce's WebSocket support is reached through the ASGI server, not the built-in development server. This is a deliberate scope line: hardening a from-scratch production server is not the project's goal when mature ASGI servers already exist.

Running with multiple workers

uvicorn --workers N (or several app.run() processes) forks N independent processes. They share no Python memory, which has a direct consequence for any state Veloce keeps in-process:

State Shared across workers? Notes
Signed session cookies (SessionMiddleware) Yes The session lives in the client's cookie; any worker can verify it with the shared secret. Safe across workers.
RateLimitMiddleware buckets No Each worker counts only the requests it served, so the effective limit is N × the configured value.
g / request.state n/a Per-request, never shared — correct by construction.
app.state / app.config No (after fork) Mutating app.state at runtime affects only the worker that did it.
Veloce.mount-ed in-memory data, module globals No Per process.

Guidance:

  • Sessions — signed cookies are stateless, so multi-worker is fine. For server-side, revocable sessions use ServerSessionMiddleware with a SessionStore backend. The bundled InMemorySessionStore is per-process, so a multi-worker deployment needs a shared store (Redis, a database) implementing the SessionStore interface.
  • Rate limitingRateLimitMiddleware is in-memory and therefore per-worker. For an accurate global limit across workers, put the limiter in a reverse proxy (nginx limit_req) or back it with Redis.
  • Any in-memory cache or counter — assume it is per-worker. Move anything that must be globally consistent into an external store.

A single-worker deployment behind a reverse proxy sidesteps all of the above; scale out with more workers only once shared state is externalised.

Security considerations

Hardening checklist

  • Call app.use_secure_defaults() (secure session cookies + SecurityHeadersMiddleware) for any internet-facing deployment.
  • Run veloce check your_module:app before deploying — it flags DEBUG, a missing SECRET_KEY, insecure session cookies, and missing security headers.
  • Set MAX_CONTENT_LENGTH so oversized uploads are refused.
  • Keep debug=False for anything reachable beyond localhost — debug tracebacks leak source and internals.

Regex constraints and ReDoS

A pattern= (or regex=) constraint on Query, Path, Header, Cookie, or Form is compiled once and then matched synchronously on the event loop against the request value. A pattern with catastrophic backtracking — typically nested quantifiers like (a+)+, (.*)*, or overlapping alternations — can take super-linear time on a crafted input, stalling the loop and every other request on that worker (a regular-expression denial of service, ReDoS).

Guidance for developer-supplied patterns:

  • Prefer simple, linear-time patterns; avoid nested quantifiers and quantified groups that can match the same text more than one way.
  • Anchor patterns (^…$) so the engine does not retry at every offset.
  • Pair a pattern with a max_length constraint so the input the regex runs against is bounded.
  • Test any non-trivial pattern against a deliberately adversarial input before shipping it.

Veloce compiles each pattern once at route registration, so the compile cost is paid up front — but the match cost is still the developer's responsibility to keep linear.