File: middleware.rst

package info (click to toggle)
python-falcon 4.0.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,172 kB
  • sloc: python: 33,608; javascript: 92; sh: 50; makefile: 50
file content (417 lines) | stat: -rw-r--r-- 17,349 bytes parent folder | download
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.