Middleware¶
The middleware pipeline is defined with the asgi_middleware configuration property.
It must contain a list of functions that receive the next app in the pipeline and
must return a plain asgi middleware instance:
def middleware_factory(app):
async def inner(scope, receive, send):
await app(scope, receive, send)
return inner
Any asgi middleware can be used in the middleware pipeline. For instance, it is possible to use the SessionMiddleware from starlette:
from starlette.middleware.sessions import SessionMiddleware
def session_middleware(app, settings, di):
return SessionMiddleware(
app,
secret_key=settings.session.secret_key
)
settings = {
"asgi_middleware": [
"application.middleware.session_middleware"
],
"session": {
"secret_key": "super_secret_key",
},
}
Usage¶
To demonstrate the middleware system, we will create a timing middleware that will output to the console the time spent in the processing of the request:
from asgikit.requests import Request
from asgikit.responses import respond_json
from zayt.web import get
@get
async def hello(request: Request):
await respond_json(
request.response,
{"greeting": "Hello, World!"}
)
from collections.abc import Callable
from datetime import datetime
import structlog
from zayt.di import service
logger = structlog.get_logger()
def timing_middleware(app, settings, di):
async def inner(scope, receive, send):
request_start = datetime.now()
await app(scope, receive, send)
request_end = datetime.now()
delta = request_end - request_start
logger.info("request duration", duration=str(delta))
return inner
settings = {
"asgi_middleware": [
"application.middleware.timing_middleware",
],
}
Middleware dependencies¶
Middleware functions can receive services as parameters. We could rewrite the timing middleware to persist the timings using a service instead of printing to the console:
from datetime import datetime
from application.service import TimingService
class TimingMiddleware:
def __init__(self, app, timing_service: TimingService):
self.timing_service = timing_service
async def __call__(self, scope, receive, send):
request_start = datetime.now()
await app(scope, receive, send)
request_end = datetime.now()
await self.timing_service.save(request_start, request_end)
async def timing_middleware(app, timing_service: TimingService):
timing_service = await di.get(TimingService)
return TimingMiddleware(app, timing_service)
from datetime import datetime
from zayt.di import service
@service
class TimingService:
async def save(start: datetime, end: datetime):
...
Static and uploaded files¶
The static_files_middleware() and
uploaded_files_middleware() provide a way of
serving static content and user uploaded files.
There are two separate middlewares to allow distinct handling in the middleware pipeline. For example, you could set the uploaded files to be served after authorization, while the static files remain publicly accessible.
Usage¶
First you need to activate the middlewares in the settings.py
settings = {
"asgi_middleware": [
"zayt.web.middleware.files.static_files_middleware",
"zayt.web.middleware.files.uploaded_files_middleware",
],
}
After that, files located in the directories resources/static and resources/uploads
will be served at /static/ and /uploads/, respectively.
Static files mappings¶
You can map specific paths to single static files in order to, for example, serve
the favicon at /favicon.ico pointing to a file in resources/static/:
settings = {
"asgi_middleware": [
"zayt.web.middleware.files.static_files_middleware",
],
"staticfiles": {
"mappings": {
"favicon.ico": "my-icon.ico"
},
},
}
Configuration options¶
The available options to configure the static_files_middleware()
and uploaded_files_middleware() are shown below:
settings = {
"staticfiles": {
"path": "/static", # (1)
"root": "resources/static", # (2)
"mappings": {},
},
"uploadedfiles": {
"path": "/uploads", # (3)
"root": "resources/uploads", # (4)
},
}
Note
Path where static files are served
Directory where static files are located
Path where uploaded files are served
Directory where uploaded files are located