1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
|
.. _middleware:
Middleware
==========
Middleware components provide a way to execute logic before the
framework routes each request, after each request is routed but before
the target responder is called, or just before the response is returned
for each request.
.. Note::
Unlike hooks, middleware methods apply globally to the entire App.
Components are registered with the `middleware` kwarg
when instantiating Falcon's :ref:`App class <app>`. A middleware component
is simply a class that implements one or more of the event handler methods
defined below.
.. tab-set::
.. tab-item:: WSGI
Falcon's middleware interface is defined as follows:
.. code:: python
from typing import Any
from falcon import Request, Response
class ExampleMiddleware:
def process_request(self, req: Request, resp: Response) -> None:
"""Process the request before routing it.
Note:
Because Falcon routes each request based on req.path, a
request can be effectively re-routed by setting that
attribute to a new value from within process_request().
Args:
req: Request object that will eventually be
routed to an on_* responder method.
resp: Response object that will be routed to
the on_* responder.
"""
def process_resource(
self,
req: Request,
resp: Response,
resource: object,
params: dict[str, Any],
) -> None:
"""Process the request after routing.
Note:
This method is only called when the request matches
a route to a resource.
Args:
req: Request object that will be passed to the
routed responder.
resp: Response object that will be passed to the
responder.
resource: Resource object to which the request was
routed.
params: A dict-like object representing any additional
params derived from the route's URI template fields,
that will be passed to the resource's responder
method as keyword arguments.
"""
def process_response(
self,
req: Request,
resp: Response,
resource: object,
req_succeeded: bool
) -> None:
"""Post-processing of the response (after routing).
Args:
req: Request object.
resp: Response object.
resource: Resource object to which the request was
routed. May be None if no route was found
for the request.
req_succeeded: True if no exceptions were raised while
the framework processed and routed the request;
otherwise False.
"""
# A middleware instance can then be added to the Falcon app init
from falcon import App
app = App(middleware=[ExampleMiddleware()])
.. tab-item:: ASGI
The ASGI middleware interface is similar to WSGI, but also supports the
standard ASGI lifespan events. However, because lifespan events are an
optional part of the ASGI specification, they may or may not fire depending
on your ASGI server.
.. code:: python
from typing import Any
from falcon.asgi import Request, Response, WebSocket
class ExampleMiddleware:
async def process_startup(
self, scope: dict[str, Any], event: dict[str, Any]
) -> None:
"""Process the ASGI lifespan startup event.
Invoked when the server is ready to start up and
receive connections, but before it has started to
do so.
To halt startup processing and signal to the server that it
should terminate, simply raise an exception and the
framework will convert it to a "lifespan.startup.failed"
event for the server.
Args:
scope (dict): The ASGI scope dictionary for the
lifespan protocol. The lifespan scope exists
for the duration of the event loop.
event (dict): The ASGI event dictionary for the
startup event.
"""
async def process_shutdown(
self, scope: dict[str, Any], event: dict[str, Any]
) -> None:
"""Process the ASGI lifespan shutdown event.
Invoked when the server has stopped accepting
connections and closed all active connections.
To halt shutdown processing and signal to the server
that it should immediately terminate, simply raise an
exception and the framework will convert it to a
"lifespan.shutdown.failed" event for the server.
Args:
scope (dict): The ASGI scope dictionary for the
lifespan protocol. The lifespan scope exists
for the duration of the event loop.
event (dict): The ASGI event dictionary for the
shutdown event.
"""
async def process_request(self, req: Request, resp: Response) -> None:
"""Process the request before routing it.
Note:
Because Falcon routes each request based on req.path, a
request can be effectively re-routed by setting that
attribute to a new value from within process_request().
Args:
req: Request object that will eventually be
routed to an on_* responder method.
resp: Response object that will be routed to
the on_* responder.
"""
async def process_resource(
self,
req: Request,
resp: Response,
resource: object,
params: dict[str, Any],
) -> None:
"""Process the request after routing.
Note:
This method is only called when the request matches
a route to a resource.
Args:
req: Request object that will be passed to the
routed responder.
resp: Response object that will be passed to the
responder.
resource: Resource object to which the request was
routed.
params: A dict-like object representing any additional
params derived from the route's URI template fields,
that will be passed to the resource's responder
method as keyword arguments.
"""
async def process_response(
self,
req: Request,
resp: Response,
resource: object,
req_succeeded: bool
) -> None:
"""Post-processing of the response (after routing).
Args:
req: Request object.
resp: Response object.
resource: Resource object to which the request was
routed. May be None if no route was found
for the request.
req_succeeded: True if no exceptions were raised while
the framework processed and routed the request;
otherwise False.
"""
async def process_request_ws(self, req: Request, ws: WebSocket) -> None:
"""Process a WebSocket handshake request before routing it.
Note:
Because Falcon routes each request based on req.path, a
request can be effectively re-routed by setting that
attribute to a new value from within process_request().
Args:
req: Request object that will eventually be
passed into an on_websocket() responder method.
ws: The WebSocket object that will be passed into
on_websocket() after routing.
"""
async def process_resource_ws(
self,
req: Request,
ws: WebSocket,
resource: object,
params: dict[str, Any],
) -> None:
"""Process a WebSocket handshake request after routing.
Note:
This method is only called when the request matches
a route to a resource.
Args:
req: Request object that will be passed to the
routed responder.
ws: WebSocket object that will be passed to the
routed responder.
resource: Resource object to which the request was
routed.
params: A dict-like object representing any additional
params derived from the route's URI template fields,
that will be passed to the resource's responder
method as keyword arguments.
"""
# A middleware instance can then be added to the Falcon app init
from falcon.asgi import App
app = App(middleware=[ExampleMiddleware()])
It is also possible to implement a middleware component that is compatible
with both ASGI and WSGI apps. This is done by applying an `*_async` postfix
to distinguish the two different versions of each middleware method, as in
the following example:
.. code:: python
import falcon as wsgi
from falcon import asgi
class ExampleMiddleware:
def process_request(self, req: wsgi.Request, resp: wsgi.Response) -> None:
"""Process WSGI request using synchronous logic.
Note that req and resp are instances of falcon.Request and
falcon.Response, respectively.
"""
async def process_request_async(self, req: asgi.Request, resp: asgi.Response) -> None:
"""Process ASGI request using asynchronous logic.
Note that req and resp are instances of falcon.asgi.Request and
falcon.asgi.Response, respectively.
"""
.. Tip::
Because *process_request* executes before routing has occurred, if a
component modifies ``req.path`` in its *process_request* method,
the framework will use the modified value to route the request.
For example::
# Route requests based on the host header.
req.path = '/' + req.host + req.path
.. Tip::
The *process_resource* method is only called when the request matches
a route to a resource. To take action when a route is not found, a
:meth:`sink <falcon.App.add_sink>` may be used instead.
.. Tip::
In order to pass data from a middleware function to a resource function
use the ``req.context`` and ``resp.context`` objects. These context objects
are intended to hold request and response data specific to your app as it
passes through the framework.
Each component's *process_request*, *process_resource*, and
*process_response* methods are executed hierarchically, as a stack, following
the ordering of the list passed via the `middleware` kwarg of
:class:`falcon.App` or :class:`falcon.asgi.App`. For example, if a list of middleware objects are
passed as ``[mob1, mob2, mob3]``, the order of execution is as follows::
mob1.process_request
mob2.process_request
mob3.process_request
mob1.process_resource
mob2.process_resource
mob3.process_resource
<route to resource responder method>
mob3.process_response
mob2.process_response
mob1.process_response
Note that each component need not implement all `process_*`
methods; in the case that one of the three methods is missing,
it is treated as a noop in the stack. For example, if ``mob2`` did
not implement *process_request* and ``mob3`` did not implement
*process_response*, the execution order would look
like this::
mob1.process_request
_
mob3.process_request
mob1.process_resource
mob2.process_resource
mob3.process_resource
<route to responder method>
_
mob2.process_response
mob1.process_response
Short-Circuiting
----------------
A *process_request* or *process_resource* middleware method may short-circuit
further request processing by setting :attr:`falcon.Response.complete` to ``True``, e.g.::
resp.complete = True
After the method returns, setting this flag will cause the framework to skip
any remaining *process_request* and *process_resource* methods, as well as
the responder method that the request would have been routed to. However, any
*process_response* middleware methods will still be called.
In a similar manner, setting :attr:`falcon.Response.complete` to ``True`` from
within a *process_resource* method will short-circuit further request processing
at that point.
In the example below, you can see how request processing will be short-circuited
once :attr:`falcon.Response.complete` has been set to
``True``, i.e., the framework will prevent ``mob3.process_request``, all *process_resource*
methods, as well as the routed responder method from processing the request.
However, all *process_response* methods will still be called::
mob1.process_request
mob2.process_request # resp.complete = True
<skip mob3.process_request>
<skip mob1/mob2/mob3.process_resource>
<skip route to resource responder method>
mob3.process_response
mob2.process_response
mob1.process_response
This feature affords use cases in which the response may be pre-constructed,
such as in the case of caching.
Exception Handling
------------------
If one of the *process_request* middleware methods raises an
exception, it will be processed according to the exception type. If
the type matches a registered error handler, that handler will
be invoked and then the framework will begin to unwind the
stack, skipping any lower layers. The error handler may itself
raise an instance of :class:`~.HTTPError` or :class:`~.HTTPStatus`, in
which case the framework will use the latter exception to update the
*resp* object.
.. Note::
By default, the framework installs two handlers, one for
:class:`~.HTTPError` and one for :class:`~.HTTPStatus`. These can
be overridden via :meth:`~.falcon.App.add_error_handler`.
Regardless, the framework will continue unwinding the middleware
stack. For example, if *mob2.process_request* were to raise an
error, the framework would execute the stack as follows::
mob1.process_request
mob2.process_request
<skip mob1/mob2 process_resource>
<skip mob3.process_request>
<skip mob3.process_resource>
<skip route to resource responder method>
mob3.process_response
mob2.process_response
mob1.process_response
As illustrated above, by default, all *process_response* methods will be
executed, even when a *process_request*, *process_resource*, or *on_\** resource
responder raises an error. This behavior is controlled by the
:ref:`App class's <app>` `independent_middleware` keyword argument.
Finally, if one of the *process_response* methods raises an error,
or the routed ``on_*`` responder method itself raises an error, the
exception will be handled in a similar manner as above. Then,
the framework will execute any remaining middleware on the
stack.
|