HTTP Server
Options on the application object that shape how HTTP is served. Configure them right after
AustialFactory.create(...), before the application is initialized.
app = AustialFactory.create(AppModule)
app.set_global_prefix("api", exclude=["health"])
app.use_compression()
app.set_trust_proxy()Global prefix#
app.set_global_prefix("api") prefixes every route: GET /cats becomes GET /api/cats. List routes
that must stay put in exclude. An entry is a path (any method) or a RouteInfo:
from austial import RequestMethod, RouteInfo
app.set_global_prefix(
"api",
exclude=["health", RouteInfo("webhooks/stripe", RequestMethod.POST)],
)With URI versioning the order is prefix, version, controller: /api/v1/cats.
Host-based routing#
A controller can serve only requests whose Host header matches. A :name segment captures a value,
which HostParam reads:
from austial import Controller, Get, HostParam
@Controller("dashboard", host=":tenant.example.com")
class TenantDashboard:
@Get()
async def home(self, tenant: str = HostParam("tenant")):
return {"tenant": tenant} # acme.example.com -> {"tenant": "acme"}
@Controller("dashboard", host="admin.example.com") # same path, a different host
class AdminDashboard:
@Get()
async def home(self): ...host= also takes a list of patterns, and HostParam() with no name returns all captures as a dict.
The port is ignored. A request whose host matches no controller gets a 404.
Client IP and reverse proxies#
Ip() returns the client’s address. Behind a load balancer that is the balancer’s address unless you
trust its X-Forwarded-For header:
app.set_trust_proxy("10.0.0.0/8") # or "*" to trust every upstream (only if nothing else can reach the app)@Get("whoami")
async def whoami(self, ip: str = Ip()):
return {"ip": ip}Compression#
app.use_compression(minimum_size=500) gzip-compresses responses at least that large, for clients that
send Accept-Encoding: gzip. If a reverse proxy already compresses, leave it off.
Static files#
app.use_static_assets(directory, prefix="/") serves a directory. It’s mounted after your API routes,
so a controller route always wins over a file of the same path:
app.use_static_assets("public", prefix="/static")
app.use_static_assets("web/dist", prefix="/", html=True) # serve index.html for directories (a SPA)Extra keyword arguments go to Starlette’s StaticFiles.
HTTPS, the listening URL and the HTTP adapter#
listen forwards extra keyword arguments to uvicorn, so TLS is a matter of passing the files; afterwards get_url() tells
you where the app is:
await app.listen(8443, ssl_certfile="cert.pem", ssl_keyfile="key.pem")
app.get_url() # "https://127.0.0.1:8443" (RuntimeError before listen() started)
app.get_http_server() # the uvicorn.Server, e.g. to set should_exitBehind a TLS-terminating proxy you usually want plain HTTP here and set_trust_proxy() above instead.
A provider that needs the underlying FastAPI app (to mount a sub-application, add a route) injects HttpAdapterHost:
from austial import HttpAdapterHost
@Injectable()
class Docs:
def __init__(self, host: HttpAdapterHost):
self.host = host
def on_application_bootstrap(self):
self.host.http_adapter.mount("/legacy", legacy_wsgi_app) # http_adapter is the FastAPI instanceAWS Lambda#
create_lambda_handler wraps the application with Mangum for API Gateway (REST and HTTP
APIs), Function URLs and ALB:
# handler.py (Lambda handler setting: handler.handler)
from austial import create_lambda_handler
from src.app.app_module import AppModule
handler = create_lambda_handler(AppModule) # extra keyword arguments go to mangum.MangumInstall the extra into the deployment package (pip install 'austial[lambda]', which brings in mangum). The application is built once per cold start; lifecycle hooks and async providers
run during the lifespan startup on the first invocation. Keep initialisation light (a long on_module_init is a slower cold start),
and don’t rely on in-memory state between invocations. Microservices, WebSocket gateways and austial.schedule jobs need a
long-running process and don’t belong in a Lambda.
Sessions and other ASGI middleware#
app.use(MiddlewareClass, **options) adds any ASGI middleware. For example, cookie sessions
(pip install itsdangerous), read with the Session() parameter helper:
from starlette.middleware.sessions import SessionMiddleware
from austial import Session
app.use(SessionMiddleware, secret_key=config.get_or_throw("SESSION_SECRET"))
@Get("visit")
async def visit(self, session: dict = Session()):
session["visits"] = session.get("visits", 0) + 1
return sessionSession("key") returns just that key.
@All#
@All("path") registers a handler for every HTTP verb on the path, useful for catch-all proxies and
webhook endpoints that don’t care about the method.