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

Qualquer middleware asgi pode ser utilizado no pipeline de middleware. Por exemplo, é possível utilizar o SessionMiddleware do starlette:

application/middleware.py
from starlette.middleware.sessions import SessionMiddleware


def session_middleware(app, settings, di):
    return SessionMiddleware(
        app,
        secret_key=settings.session.secret_key
    )
configuration/settings.py
settings = {
    "asgi_middleware": [
        "application.middleware.session_middleware"
    ],
    "session": {
        "secret_key": "super_secret_key",
    },
}

Utilização

Para demonstrar o sistema de middleware, nós criaremos um middleware temporizador que exibirá no console o tempo gasto no processamento da requisição:

application/handler.py
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!"}
    )
application/middleware.py
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
configuration/settings.py
settings = {
    "asgi_middleware": [
        "application.middleware.timing_middleware",
    ],
}

Dependências do middleware

Funções de middleware podem receber serviços como parâmetros. Nós podemos reescrever o middleware temporizador para persistir os tempos usando um serviço ao invés de imprimir no console:

application/middleware.py
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)
application/service.py
from datetime import datetime

from zayt.di import service

@service
class TimingService:
    async def save(start: datetime, end: datetime):
        ...

Arquivos estáticos e uploads

The static_files_middleware() and uploaded_files_middleware() provide a way of serving static content and user uploaded files.

Há dois middlewares separados para permitir tratamento distinto no pipeline de middleware. Por exemplo, você poderia definir que os uploads serão servidos após autorização, enquanto os arquivos estáticos permanecem publicamente acessíveis.

Utilização

Primeiro você precisa ativar os middlewares no settings.py

settings = {
    "asgi_middleware": [
        "zayt.web.middleware.files.static_files_middleware",
        "zayt.web.middleware.files.uploaded_files_middleware",
    ],
}

Após isto, arquivos localizados nos diretórios resourcres/static e resources/uploads serão serão servidos em /static e /uploads/, respectivamente.

Mapeamento de arquivos estáticos

Você pode mapear caminhos para arquivos estáticos específicos para, por exemplo, servir o favicon em /favicon.ico apontando para um arquivo em resources/static/:

settings = {
    "asgi_middleware": [
        "zayt.web.middleware.files.static_files_middleware",
    ],
    "staticfiles": {
        "mappings": {
            "favicon.ico": "my-icon.ico"
        },
    },
}

Opções de configuração

As opções disponíveis para configurar o static_files_middleware() e o uploaded_files_middleware() são mostradas abaixo:

settings = {
    "staticfiles": {
        "path": "/static", # (1)
        "root": "resources/static", # (2)
        "mappings": {},
    },
    "uploadedfiles": {
        "path": "/uploads", # (3)
        "root": "resources/uploads", # (4)
    },
}

Nota

  1. Caminho onde os arquivos estáticos serão servidos

  2. Diretório onde os arquivos estáticos são localizados

  3. Caminho onde os uploads são servidos

  4. Diretório onde os uploads são localizados