Middleware
Starlette contains several middleware classes for adding behavior that applies to the entire application. These are implemented as standard ASGI middleware classes and can be applied to Starlette or any other ASGI application.
using the middleware
The Starlette application class allows you to include ASGI middleware in a way that ensures it is always wrapped by exception handlers.
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
from starlette.middleware.trustedhost import TrustedHostMiddleware
routes = ...
# Ensure that all requests include an 'example.com' or
# '*.example.com' host header, and strictly enforce https-only access.
middleware = [
Middleware(
TrustedHostMiddleware,
allowed_hosts=['example.com', '*.example.com'],
),
Middleware(HTTPSRedirectMiddleware)
]
app = Starlette(routes=routes, middleware=middleware)
By default, each Starlette application automatically contains two pieces of middleware:
ServerErrorMiddleware-ensures that application exceptions can return custom 500 pages, or display application trace information in DEBUG mode. This is always the outermost middleware layer.ExceptionMiddleware-Add exception handlers so that specific types of expected exception conditions can be associated with handler functions. For example, firingHTTPException(status_code=404)inside an endpoint will eventually render a custom 404 page.
Middleware is evaluated from top to bottom, so in our sample application, the execution flow would look like this:
- middleware
ServerErrorMiddlewareTrustedHostMiddlewareHTTPSRedirectMiddlewareExceptionMiddleware
- routing
- endpoint
The following middleware implementations are available in Starlette packages:
CORS middleware
Add appropriate CORS headers for outgoing responses to allow cross-origin requests from browsers.
The default parameters used by CORSMiddling implementations are restrictive by default, so you need to explicitly enable specific sources, methods, or headers for browsers to be allowed to use them in a cross-domain context.
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
routes = ...
middleware = [
Middleware(CORSMiddleware, allow_origins=['*'])
]
app = Starlette(routes=routes, middleware=middleware)
The following arguments were supported:
allow_origins-List of sources that should be allowed to make cross-source requests. For example['https://example.org', 'https://www.example.org']. You can use['*']to allow any source.allow_origin_regex- A regular expression string used to match sources that should be allowed to make cross-source requests. For example:'https://.*\.example\.org'。allow_methods- A list of HTTP methods that should be allowed for cross-origin requests. Default is['GET']. You can use['*']to allow all standard methods.allow_headers-A list of HTTP request headers that should be supported for cross-origin requests. The default is[]. You can use['*']to allow all headers. For CORS requests,Accept,Accept-Language,Content-Language, andContent-Typeheaders are always allowed.allow_credentials-indicates that cookies should be supported for cross-origin requests. The default isFalse. In addition,allow_origins,allow_methods, andallow_headerscannot be set to['*']in order to allow credentials, they must all be explicitly specified.expose_headers-Indicates any response header that should be accessible to browsers. Default is[].max_age-Sets the maximum time in seconds for browsers to cache CORS responses. Default is600.
Middleware responds to two specific types of HTTP requests...
Cross-domain resource sharing (CORS) pre-screening requests
These are any OPTIONS requests with Origin and Access-Control-Request-Method headers. In this case, middleware will intercept incoming requests and respond with appropriate CORS headers, as well as 200 or 400 responses for informational purposes.
simple request
Any request with an Origin header. In this case, middleware will pass the request as usual, but will include the appropriate CORS header in the response.
Session middleware
Add HTTP sessions based on signature cookies. Session information is readable but not modifiable.
Use the request.session dictionary interface to access or modify session data.
The following arguments were supported:
secret_key- A random string is expected.session_cookie- The default value is "session."max_age-Session expiration time in seconds. The default is 2 weeks. If set toNone, the Cookie will be valid for the same duration as the browser session.same_site-The SameSite flag prevents the browser from sending session cookies during cross-site requests. The default value is'lax'.path-The path to set for the session Cookie. The default is'/'.https_only-Indicates that the security flag should be set (can only be used with HTTPS). The default isFalse.-
domain- Cookie domain used to share cookies between subdomains or across domains. Browsers default domains to the same host on which cookies are set, excluding subdomains (see).from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.middleware.sessions import SessionMiddleware
routes = ...
middleware = [ Middleware(SessionMiddleware, secret_key=..., https_only=True) ]
app = Starlette(routes=routes, middleware=middleware)
HTTPS redirect middleware
Enforces that all incoming requests must be https or wss. Any incoming requests to http or ws will be redirected to the security scheme.
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
routes = ...
middleware = [
Middleware(HTTPSRedirectMiddleware)
]
app = Starlette(routes=routes, middleware=middleware)
For this middleware class, there are no configuration options.
Trusted Host Middleware
Enforce all incoming requests to have a properly set Host header to protect against HTTP host header attacks.
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.trustedhost import TrustedHostMiddleware
routes = ...
middleware = [
Middleware(TrustedHostMiddleware, allowed_hosts=['example.com', '*.example.com'])
]
app = Starlette(routes=routes, middleware=middleware)
The following arguments were supported:
allowed_hosts-List of domain names that should be allowed as host names. Wildcard domain names such as*.example.comcan be used to match subdomains. To allow any host name, useallowed_hosts=["*"]or omit middleware.www_redirect-If set to True, requests for non-www versions of allowed hosts will be redirected to their www counterparts. Default isTrue.
If the incoming request is not properly validated, a 400 response will be sent.
GZip middleware
Handles GZip responses to any requests that contain "gzip" in the Accept-Encoding header.
Middleware will handle both standard and stream responses.
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.gzip import GZipMiddleware
routes = ...
middleware = [
Middleware(GZipMiddleware, minimum_size=1000, compresslevel=9)
]
app = Starlette(routes=routes, middleware=middleware)
The following arguments were supported:
minimum_size-Do not GZip compress responses smaller than this minimum byte size. The default is500.compresslevel-used during GZip compression. It is an integer from 1 to 9. The default value is9. The lower the value, the faster the compression, but the larger the file size; the higher the value, the slower the compression, but the smaller the file size.
Middleware does not GZip responses that have Content-Encoding set to prevent them from being encoded twice.
BaseHTTP middleware
An abstract class that allows you to write ASGI middleware for the request/response interface.
service condition
To implement a middleware class using BaseHTTPMiddleware, you must override the async def dispatch(request, call_next) method.
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
class CustomHeaderMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
response.headers['Custom'] = 'Example'
return response
routes = ...
middleware = [
Middleware(CustomHeaderMiddleware)
]
app = Starlette(routes=routes, middleware=middleware)
If you want to provide configuration options for your middleware class, you should override the __init__ method to ensure that the first argument is app and any remaining arguments are optional keyword arguments. If you do this, make sure the app attribute is set on the instance.
class CustomHeaderMiddleware(BaseHTTPMiddleware):
def __init__(self, app, header_value='Example'):
super().__init__(app)
self.header_value = header_value
async def dispatch(self, request, call_next):
response = await call_next(request)
response.headers['Custom'] = self.header_value
return response
middleware = [
Middleware(CustomHeaderMiddleware, header_value='Customized')
]
app = Starlette(routes=routes, middleware=middleware)
Middleware classes should not modify their state outside of __init__ methods. Instead, you should keep any state local to dispatch methods or explicitly pass it along, rather than changing the state of middleware instances.
limitations
Currently,BaseHTTPMiddleware has some known limitations:
- Using
BaseHTTPMiddlewareprevents changes tocontextlib.ContextVarfrom propagating upward. That is, if you set a value forContextVarin the endpoint and try to read it from the relay, you will find that the value is different from the value you set in the endpoint (see this test for an example of this behavior).
To overcome these limitations, use pure ASGI middleware, as shown below.
Pure ASGI middleware
The ASGI specification makes it possible to implement ASGI middleware directly using the ASGI interface as a series of ASGI applications that invoke the next one in turn. In fact, the middleware classes shipped with Starlette are implemented in this way.
This lower-level approach provides greater control over behavior and enhanced interoperability across frameworks and servers. It also overcomes the limitations of BaseHTTPMiddleware.
Writing Pure ASGI Middleware
The most common way to create ASGI middleware is to use a class.
class ASGIMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
await self.app(scope, receive, send)
The middleware above is the most basic ASGI middleware. It takes a parent ASGI application as an argument in its constructor and implements an async __call__ method that calls the parent application.
Some implementations, such as asgi-cors, use an alternative style, using the function:
import functools
def asgi_middleware():
def asgi_decorator(app):
@functools.wraps(app)
async def wrapped_app(scope, receive, send):
await app(scope, receive, send)
return wrapped_app
return asgi_decorator
In any case, ASGI middleware must be callable and accept three parameters:scope,receive, and send.
scopeis a dictionary that holds information about connections, wherescope["type"]could be:"http": Used for HTTP requests."websocket": Used for WebSocket connections."lifespan": Used for ASGI lifecycle messages.
receiveandsendcan be used to exchange ASGI event messages with ASGI servers-more on this below. The type and content of these messages depend on the scope type. Learn more in the ASGI specification.
Use pure ASGI middleware
Pure ASGI middleware can be used just like any other middleware:
from starlette.applications import Starlette
from starlette.middleware import Middleware
from .middleware import ASGIMiddleware
routes = ...
middleware = [
Middleware(ASGIMiddleware),
]
app = Starlette(..., middleware=middleware)
See also Using middleware.
type annotation
There are two ways to annotate middleware: use Starlette itself or asgiref.
-
Using Starlette: Suitable for most common use cases.
from starlette.types import ASGIApp, Message, Scope, Receive, Send
class ASGIMiddleware: def init(self, app: ASGIApp) -> None: self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: if scope["type"] != "http": return await self.app(scope, receive, send) async def send_wrapper(message: Message) -> None: # ... Do something await send(message) await self.app(scope, receive, send_wrapper) -
uses
asgiref: to implement stricter type hinting.from asgiref.typing import ASGI3Application, ASGIReceiveCallable, ASGISendCallable, Scope from asgiref.typing import ASGIReceiveEvent, ASGISendEvent
class ASGIMiddleware: def init(self, app: ASGI3Application) -> None: self.app = app
async def __call__(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: if scope["type"] != "http": await self.app(scope, receive, send) return async def send_wrapper(message: ASGISendEvent) -> None: # ... Do something await send(message) return await self.app(scope, receive, send_wrapper)
common pattern
Processing only certain requests
ASGI middleware can apply specific behaviors based on the content of scope.
For example, if only HTTP requests are processed, you can write...
class ASGIMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
... # Do something here!
await self.app(scope, receive, send)
Similarly, WebSocket-only middleware is protected on scope["type"] != "websocket".
Middleware may also behave differently depending on the request method, URL, header, etc.
multiplexing Starlette components
Starlette provide several data structures that accept that ASGI scope 、 receive and/or send Parameters that enable you to work at a higher level of abstraction. Such data structures include Request 、 Headers 、 QueryParams 、 URL And so on.
For example, you can instantiate an Request to check HTTP requests more easily:
from starlette.requests import Request
class ASGIMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope["type"] == "http":
request = Request(scope)
... # Use `request.method`, `request.url`, `request.headers`, etc.
await self.app(scope, receive, send)
You can also reuse responses, which are also ASGI applications.
Send an eager response
Checking the connection scope allows you to conditionally invoke to different ASGI applications. One use case might be sending a response without invoking the application.
For example, this middleware uses a dictionary to perform permanent redirection based on the path of the request. If you need to refactor routing URL patterns, this can be used to achieve continued support for legacy URLs.
from starlette.datastructures import URL
from starlette.responses import RedirectResponse
class RedirectsMiddleware:
def __init__(self, app, path_mapping: dict):
self.app = app
self.path_mapping = path_mapping
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
url = URL(scope=scope)
if url.path in self.path_mapping:
url = url.replace(path=self.path_mapping[url.path])
response = RedirectResponse(url, status_code=301)
await response(scope, receive, send)
return
await self.app(scope, receive, send)
An example usage would look something like this:
from starlette.applications import Starlette
from starlette.middleware import Middleware
routes = ...
redirections = {
"/v1/resource/": "/v2/resource/",
# ...
}
middleware = [
Middleware(RedirectsMiddleware, path_mapping=redirections),
]
app = Starlette(routes=routes, middleware=middleware)
Check or modify requests
Request information can be accessed or changed by manipulating scope. For a complete example of this pattern, see Uvicorn's ProxyHeadersMiddleware, which checks and adjusts scope when serving behind a front-end proxy.
In addition, wrapper receive ASGI callable objects enable you to access or modify HTTP request bodies by manipulating http.request ASGI event messages.
For example, this middleware calculates and records the size of the incoming request body...
class LoggedRequestBodySizeMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
body_size = 0
async def receive_logging_request_body_size():
nonlocal body_size
message = await receive()
assert message["type"] == "http.request"
body_size += len(message.get("body", b""))
if not message.get("more_body", False):
print(f"Size of request body was: {body_size} bytes")
return message
await self.app(scope, receive_logging_request_body_size, send)
Similarly, WebSocket middleware may manipulate websocket.receive ASGI event messages to inspect or change incoming WebSocket data.
For an example of changing the HTTP request body, see msgpack-asgi.
Check or modify response
Wrapping send ASGI callable objects allows you to inspect or modify HTTP responses sent by the underlying application. To do this, react to http.response.start or http.response.body ASGI event messages.
For example, this middleware adds some fixed extra response headers:
from starlette.datastructures import MutableHeaders
class ExtraResponseHeadersMiddleware:
def __init__(self, app, headers):
self.app = app
self.headers = headers
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
return await self.app(scope, receive, send)
async def send_with_extra_headers(message):
if message["type"] == "http.response.start":
headers = MutableHeaders(scope=message)
for key, value in self.headers:
headers.append(key, value)
await send(message)
await self.app(scope, receive, send_with_extra_headers)
See also asgi-logger for an example of examining HTTP responses and logging configurable HTTP access log lines.
Similarly, WebSocket middleware may manipulate websocket.send ASGI event messages to inspect or change outgoing WebSocket data.
Note that if you change the response body, you need to update the response Content-Length header to match the new response body length. For a complete example, see brotli-asgi.
Delivering information to endpoints
If you need to share information with the underlying application or endpoint, you can store it in the scope dictionary. Note that this is a convention--for example, Starlette uses this method to share routing information with endpoints--but it's not part of the ASGI specification. If you do, make sure you use keys that are unlikely to be used by other middleware or applications to avoid conflicts.
For example, endpoints will be able to access request.scope["asgi_transaction_id"] when the following middleware is included.
import uuid
class TransactionIDMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
scope["asgi_transaction_id"] = uuid.uuid4()
await self.app(scope, receive, send)
Cleaning and error handling
You can wrap your application in an try/except/finally block or context manager for cleanup or error handling.
For example, the following middleware might collect metrics and handle application exceptions…
import time
class MonitoringMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
start = time.time()
try:
await self.app(scope, receive, send)
except Exception as exc:
... # Process the exception
raise
finally:
end = time.time()
elapsed = end - start
... # Submit `elapsed` as a metric to a monitoring backend
See also timing-asgi for a complete example of this pattern.
potential problems
ASGI middleware should be stateless
Because ASGI is designed to handle concurrent requests, any connection-specific state should be confined to the __call__ implementation. Failure to do so usually results in variable read/write conflicts across requests, and errors are likely to occur.
For example, if the X-Mock header is present in the response, this action conditionally replaces the response body…
Do
```python
from starlette.datastructures import Headers
class MockResponseBodyMiddleware:
def __init__(self, app, content):
self.app = app
self.content = content
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
# A flag that we will turn `True` if the HTTP response
# has the 'X-Mock' header.
# ✅: Scoped to this function.
should_mock = False
async def maybe_send_with_mock_content(message):
nonlocal should_mock
if message["type"] == "http.response.start":
headers = Headers(raw=message["headers"])
should_mock = headers.get("X-Mock") == "1"
await send(message)
elif message["type"] == "http.response.body":
if should_mock:
message = {"type": "http.response.body", "body": self.content}
await send(message)
await self.app(scope, receive, maybe_send_with_mock_content)
```
❌ 不要
```python hl_lines="7-8"
from starlette.datastructures import Headers
class MockResponseBodyMiddleware:
def __init__(self, app, content):
self.app = app
self.content = content
# ❌: This variable would be read and written across requests!
self.should_mock = False
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
async def maybe_send_with_mock_content(message):
if message["type"] == "http.response.start":
headers = Headers(raw=message["headers"])
self.should_mock = headers.get("X-Mock") == "1"
await send(message)
elif message["type"] == "http.response.body":
if self.should_mock:
message = {"type": "http.response.body", "body": self.content}
await send(message)
await self.app(scope, receive, maybe_send_with_mock_content)
```
See also GZipMiddleware for a complete sample implementation that addresses this potential problem.
further reading
This documentation should be sufficient as a good basis for how to create ASGI middleware.
However, there are many excellent articles on this topic:
- ASGI Introduction: Emergence of the Asynchronous Python Network Ecosystem
- How to write ASGI middleware
Using middleware in other frameworks
To wrap ASGI middleware around other ASGI applications, you should wrap application instances with a more generic pattern:
app = TrustedHostMiddleware(app, allowed_hosts=['example.com'])
You can also do this for Starlette application instances, but it is best to use middleware=<List of Middleware instances> style because it will:
- ensures that everything is wrapped in an outermost
ServerErrorMiddleware. - Keep Top
appfor more information.
Applying middleware to routing groups
Middleware can also be added to Mount instances, which enables you to apply middleware to a set of routes or sub-applications:
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.gzip import GZipMiddleware
from starlette.routing import Mount, Route
routes = [
Mount(
"/",
routes=[
Route(
"/example",
endpoint=...,
)
],
middleware=[Middleware(GZipMiddleware)]
)
]
app = Starlette(routes=routes)
Note that middleware used in this manner is not wrapped in exception handling middleware like middleware applied to Starlette applications. This is usually not a problem because it only applies to middleware that checks or modifies Response, and even then you may not want to apply this logic to error responses. If you do want to apply middleware logic to error responses only on certain routes, there are several options:
- Add an
ExceptionMiddlewaretoMount - adds an
try/exceptblock to your middleware and returns an error response from there - splits tagging and processing into two middleware pieces, one placed on
Mountto tag responses as needing processing (e.g. by settingscope["log-response"] = True) and the other applied toStarletteapplications to do the heavy lifting.
The Route/WebSocket class also accepts an middleware parameter that allows you to apply middleware to a single route:
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.gzip import GZipMiddleware
from starlette.routing import Route
routes = [
Route(
"/example",
endpoint=...,
middleware=[Middleware(GZipMiddleware)]
)
]
app = Starlette(routes=routes)
You can also apply middleware to the Router class, which enables you to apply middleware to a set of routes:
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.gzip import GZipMiddleware
from starlette.routing import Route, Router
routes = [
Route("/example", endpoint=...),
Route("/another", endpoint=...),
]
router = Router(routes=routes, middleware=[Middleware(GZipMiddleware)])
Third-party middleware
asgi-auth-github )
This middleware adds authentication to any ASGI app, requiring users to log in with their GitHub account (via OAuth). Access can be restricted to specific users or members of specific GitHub organizations or teams.
asgi-csrf
Middleware to protect against CSRF attacks. This middleware implements the double commit Cookie pattern, where a Cookie is set and then combined with a csrftoken hidden form field or x-csrftoken HTTP headers for comparison.
Authlib middleware
A direct replacement for Starlette session middleware, using authlib's jwt module.
Bugsnag middleware
A middleware class for logging exceptions to Bugsnag.
CSRF middleware
Middleware to protect against CSRF attacks. This middleware implements a double commit Cookie pattern in which a Cookie is set and then compared to an x-csrftoken HTTP header.
Early Data Middleware
Middleware and decorators for detecting and rejecting TLSv1.3 early data requests.
prometheus middleware
A middleware class for capturing Prometheus metrics related to requests and responses, including requests in progress, time, etc.
proxy header middleware
Uvicorn contains a middleware class for determining client IP addresses based on the X-Forwarded-Proto and X-Forwarded-For headers when using proxy servers. For more complex proxy configurations, you may need to tune this middleware.
rate-limiting middleware
A rate-limiting middleware. Regular expressions match URLs; flexible rules; highly customizable. Very easy to use.
RequestId Middleware
Middleware class for reading/generating request IDs and appending them to application logs.
Rollbar middleware
A middleware class for logging exceptions, errors, and logging messages to Rollbar.
StarletteOpentracing
A middleware class that sends trace information to OpenTracing.io trackers and can be used to analyze and monitor distributed applications.
Security Cookie middleware
Customizable middleware for adding automatic Cookie encryption and decryption to Starlette applications, providing additional support for existing cookie-based middleware.
timed middleware
A middleware class that emits timing information (CPU time and clock time) for each request that passes through it. Includes examples of how to emit these timings as statsd metrics.
WSGIMiddleware
A middleware class responsible for transforming WSGI applications into ASGI applications.