sanic/sanic/mixins/middleware.py

227 lines
8.1 KiB
Python
Raw Permalink Normal View History

2022-09-21 22:43:42 +01:00
from collections import deque
2021-01-27 08:25:05 +00:00
from functools import partial
2022-09-21 22:43:42 +01:00
from operator import attrgetter
from typing import Callable, List, Union, overload
2021-01-27 08:25:05 +00:00
from sanic.base.meta import SanicMeta
2022-09-21 22:43:42 +01:00
from sanic.middleware import Middleware, MiddlewareLocation
from sanic.models.futures import FutureMiddleware, MiddlewareType
2022-09-21 22:43:42 +01:00
from sanic.router import Router
2021-01-27 08:25:05 +00:00
class MiddlewareMixin(metaclass=SanicMeta):
2022-09-21 22:43:42 +01:00
router: Router
2021-01-27 08:25:05 +00:00
def __init__(self, *args, **kwargs) -> None:
self._future_middleware: List[FutureMiddleware] = []
2021-01-27 08:25:05 +00:00
def _apply_middleware(self, middleware: FutureMiddleware):
2021-02-15 19:50:20 +00:00
raise NotImplementedError # noqa
2021-01-27 08:25:05 +00:00
@overload
2021-01-27 08:25:05 +00:00
def middleware(
2022-09-21 22:43:42 +01:00
self,
middleware_or_request: MiddlewareType,
attach_to: str = "request",
apply: bool = True,
2022-09-21 22:43:42 +01:00
*,
2023-10-25 00:22:00 +01:00
priority: int = 0,
) -> MiddlewareType:
...
2021-01-27 08:25:05 +00:00
@overload
def middleware(
self,
middleware_or_request: str,
attach_to: str = "request",
apply: bool = True,
*,
2023-10-25 00:22:00 +01:00
priority: int = 0,
) -> Callable[[MiddlewareType], MiddlewareType]:
...
2021-01-28 10:33:09 +00:00
def middleware(
self,
middleware_or_request: Union[MiddlewareType, str],
attach_to: str = "request",
apply: bool = True,
*,
2023-10-25 00:22:00 +01:00
priority: int = 0,
) -> Union[MiddlewareType, Callable[[MiddlewareType], MiddlewareType]]:
"""Decorator for registering middleware.
Decorate and register middleware to be called before a request is
handled or after a response is created. Can either be called as
*@app.middleware* or *@app.middleware('request')*. Although, it is
recommended to use *@app.on_request* or *@app.on_response* instead
for clarity and convenience.
See [Middleware](/guide/basics/middleware) for more information.
Args:
middleware_or_request (Union[Callable, str]): Middleware function
or the keyword 'request' or 'response'.
attach_to (str, optional): When to apply the middleware;
either 'request' (before the request is handled) or 'response'
(after the response is created). Defaults to `'request'`.
apply (bool, optional): Whether the middleware should be applied.
Defaults to `True`.
priority (int, optional): The priority level of the middleware.
Lower numbers are executed first. Defaults to `0`.
Returns:
Union[Callable, Callable[[Callable], Callable]]: The decorated
middleware function or a partial function depending on how
the method was called.
Example:
```python
@app.middleware('request')
async def custom_middleware(request):
...
```
2021-01-27 08:25:05 +00:00
"""
def register_middleware(middleware, attach_to="request"):
nonlocal apply
2022-09-21 22:43:42 +01:00
location = (
MiddlewareLocation.REQUEST
if attach_to == "request"
else MiddlewareLocation.RESPONSE
)
middleware = Middleware(middleware, location, priority=priority)
future_middleware = FutureMiddleware(middleware, attach_to)
2021-02-03 22:42:24 +00:00
self._future_middleware.append(future_middleware)
2021-01-27 08:25:05 +00:00
if apply:
self._apply_middleware(future_middleware)
return middleware
2021-01-27 08:25:05 +00:00
# Detect which way this was called, @middleware or @middleware('AT')
if callable(middleware_or_request):
return register_middleware(middleware_or_request, attach_to=attach_to)
2021-01-27 08:25:05 +00:00
else:
return partial(register_middleware, attach_to=middleware_or_request)
def on_request(self, middleware=None, *, priority=0) -> MiddlewareType:
"""Register a middleware to be called before a request is handled.
This is the same as *@app.middleware('request')*.
Args:
middleware (Callable, optional): A callable that takes in a
request. Defaults to `None`.
Returns:
Callable: The decorated middleware function or a partial function
depending on how the method was called.
Examples:
```python
@app.on_request
async def custom_middleware(request):
request.ctx.custom = 'value'
```
"""
2021-02-15 19:50:20 +00:00
if callable(middleware):
2022-09-21 22:43:42 +01:00
return self.middleware(middleware, "request", priority=priority)
2021-02-15 19:50:20 +00:00
else:
return partial( # type: ignore
2022-09-21 22:43:42 +01:00
self.middleware, attach_to="request", priority=priority
)
2022-09-21 22:43:42 +01:00
def on_response(self, middleware=None, *, priority=0):
"""Register a middleware to be called after a response is created.
This is the same as *@app.middleware('response')*.
Args:
middleware (Callable, optional): A callable that takes in a
request and response. Defaults to `None`.
Returns:
Callable: The decorated middleware function or a partial function
depending on how the method was called.
Examples:
```python
@app.on_response
async def custom_middleware(request, response):
response.headers['X-Server'] = 'Sanic'
```
"""
2021-02-15 19:50:20 +00:00
if callable(middleware):
2022-09-21 22:43:42 +01:00
return self.middleware(middleware, "response", priority=priority)
2021-02-15 19:50:20 +00:00
else:
return partial(self.middleware, attach_to="response", priority=priority)
2022-09-21 22:43:42 +01:00
def finalize_middleware(self) -> None:
"""Finalize the middleware configuration for the Sanic application.
This method completes the middleware setup for the application.
Middleware in Sanic is used to process requests globally before they
reach individual routes or after routes have been processed.
Finalization consists of identifying defined routes and optimizing
Sanic's performance to meet the application's specific needs. If
you are manually adding routes, after Sanic has started, you will
typically want to use the `amend` context manager rather than
calling this method directly.
.. note::
This method is usually called internally during the server setup
process and does not typically need to be invoked manually.
Example:
```python
app.finalize_middleware()
```
"""
2022-09-21 22:43:42 +01:00
for route in self.router.routes:
request_middleware = Middleware.convert(
self.request_middleware, # type: ignore
self.named_request_middleware.get(route.name, deque()), # type: ignore # noqa: E501
2022-09-21 22:43:42 +01:00
location=MiddlewareLocation.REQUEST,
)
response_middleware = Middleware.convert(
self.response_middleware, # type: ignore
self.named_response_middleware.get(route.name, deque()), # type: ignore # noqa: E501
2022-09-21 22:43:42 +01:00
location=MiddlewareLocation.RESPONSE,
)
route.extra.request_middleware = deque(
sorted(
request_middleware,
key=attrgetter("order"),
reverse=True,
)
)
route.extra.response_middleware = deque(
sorted(
response_middleware,
key=attrgetter("order"),
reverse=True,
)[::-1]
)
request_middleware = Middleware.convert(
self.request_middleware, # type: ignore
2022-09-21 22:43:42 +01:00
location=MiddlewareLocation.REQUEST,
)
response_middleware = Middleware.convert(
self.response_middleware, # type: ignore
2022-09-21 22:43:42 +01:00
location=MiddlewareLocation.RESPONSE,
)
self.request_middleware = deque(
sorted(
request_middleware,
key=attrgetter("order"),
reverse=True,
)
)
self.response_middleware = deque(
sorted(
response_middleware,
key=attrgetter("order"),
reverse=True,
)[::-1]
)