Skip to content

Commit

Permalink
Merge pull request #416 from rsyed83/middleware_process_resource
Browse files Browse the repository at this point in the history
feat(middleware): Add middleware method "process_resource"
  • Loading branch information
kgriffs committed Jan 30, 2015
2 parents 3296523 + ead6357 commit 5886e64
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 78 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ below in order of date of first contribution:
* Sriram Madapusi Vasudevan (TheSriram)
* Erik Erwitt (eerwitt)
* Bernhard Weitzhofer (b6d)
* Rahman Syed (rsyed83)
109 changes: 109 additions & 0 deletions doc/api/middleware.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
.. _middleware:

Middleware
==========

Middleware components execute both before and after the framework
routes the request. Middleware is registered by passing components
to the :ref:`API class <api>` initializer.

The middleware interface is defined as follows:


.. code:: python
class ExampleComponent(object):
def process_request(self, req, resp):
"""Process the request before routing it.
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, resp, resource):
"""Process the request after routing.
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. May be None if no route was found for
the request
"""
def process_response(self, req, resp, resource)
"""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
"""
Because middleware can execute before routing has occurred, if a
component modifies ``req.uri`` in its *process_request* method,
the framework will use the modified value to route the request.

Each component's *process_request*, *process_resource*, and
*process_response* methods are executed hierarchically, as a stack.
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 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

If one of the *process_request* middleware methods raises an
error, it will be processed according to the error 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 HTTPError, in which case the framework
will use the latter exception to update the *resp* object.
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, mob3, and routing>
mob2.process_response
mob1.process_response

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.
2 changes: 1 addition & 1 deletion doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Classes and Functions
api/request_and_response
api/status
api/errors
api/middleware
api/hooks
api/routing
api/util

109 changes: 42 additions & 67 deletions falcon/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ class API(object):
Args:
media_type (str, optional): Default media type to use as the value for
the Content-Type header on responses. (default 'application/json')
middleware(object or list, optional): One or more objects (
instantiated classes) that implement the following middleware
middleware(object or list, optional): One or more objects
(instantiated classes) that implement the following middleware
component interface::
class ExampleComponent(object):
Expand All @@ -52,66 +52,30 @@ def process_request(self, req, resp):
the on_* responder
\"""
def process_response(self, req, resp)
\"""Post-processing of the response (after routing).
def process_resource(self, req, resp, resource):
\"""Process the request after routing.
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. May be None if no route was found for
the request
\"""
Middleware components execute both before and after the framework
routes the request, or calls any hooks. For example, if a
component modifies ``req.uri`` in its *process_request* method,
the framework will use the modified value to route the request.
Each component's *process_request* and *process_response* methods
are executed hierarchically, as a stack. 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
<route to responder method>
mob3.process_response
mob2.process_response
mob1.process_response
Note that each component need not implement both process_*
methods; in the case that one of the two 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
<route to responder method>
_
mob2.process_response
mob1.process_response
If one of the *process_request* middleware methods raises an
error, it will be processed according to the error 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 HTTPError, in which case the framework
will use the latter exception to update the *resp* object.
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 mob3 and routing>
mob2.process_response
mob1.process_response
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.
def process_response(self, req, resp, resource)
\"""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
\"""
See also :ref:`Middleware <middleware>`.
request_type (Request, optional): Request-like class to use instead
of Falcon's default class. Among other things, this feature
affords inheriting from ``falcon.request.Request`` in order
Expand Down Expand Up @@ -203,15 +167,18 @@ def __call__(self, env, start_response):
# e.g. a 404.
responder, params, resource = self._get_responder(req)

self._call_rsrc_mw(middleware_stack, req, resp, resource)

responder(req, resp, **params)
self._call_resp_mw(middleware_stack, req, resp)
self._call_resp_mw(middleware_stack, req, resp, resource)

except Exception as ex:
for err_type, err_handler in self._error_handlers:
if isinstance(ex, err_type):
err_handler(ex, req, resp, params)
self._call_after_hooks(req, resp, resource)
self._call_resp_mw(middleware_stack, req, resp)
self._call_resp_mw(middleware_stack, req, resp,
resource)

# NOTE(kgriffs): The following line is not
# reported to be covered under Python 3.4 for
Expand All @@ -232,13 +199,13 @@ def __call__(self, env, start_response):
# process_response when no error_handler is given
# and for whatever exception. If an HTTPError is raised
# remaining process_response will be executed later.
self._call_resp_mw(middleware_stack, req, resp)
self._call_resp_mw(middleware_stack, req, resp, resource)
raise

except HTTPError as ex:
self._compose_error_response(req, resp, ex)
self._call_after_hooks(req, resp, resource)
self._call_resp_mw(middleware_stack, req, resp)
self._call_resp_mw(middleware_stack, req, resp, resource)

#
# Set status and headers
Expand Down Expand Up @@ -528,20 +495,28 @@ def _call_req_mw(self, stack, req, resp):
"""Run process_request middleware methods."""

for component in self._middleware:
process_request, _ = component
process_request, _, _ = component
if process_request is not None:
process_request(req, resp)

# Put executed component on the stack
stack.append(component) # keep track from outside

def _call_resp_mw(self, stack, req, resp):
def _call_rsrc_mw(self, stack, req, resp, resource):
"""Run process_resource middleware methods."""

for component in self._middleware:
_, process_resource, _ = component
if process_resource is not None:
process_resource(req, resp, resource)

def _call_resp_mw(self, stack, req, resp, resource):
"""Run process_response middleware."""

while stack:
_, process_response = stack.pop()
_, _, process_response = stack.pop()
if process_response is not None:
process_response(req, resp)
process_response(req, resp, resource)

def _call_after_hooks(self, req, resp, resource):
"""Executes each of the global "after" hooks, in turn."""
Expand Down
7 changes: 5 additions & 2 deletions falcon/api_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,17 @@ def prepare_middleware(middleware=None):
for component in middleware:
process_request = util.get_bound_method(component,
'process_request')
process_resource = util.get_bound_method(component,
'process_resource')
process_response = util.get_bound_method(component,
'process_response')

if not (process_request or process_response):
if not (process_request or process_resource or process_response):
msg = '{0} does not implement the middleware interface'
raise TypeError(msg.format(component))

prepared_middleware.append((process_request, process_response))
prepared_middleware.append((process_request, process_resource,
process_response))

return prepared_middleware

Expand Down
2 changes: 1 addition & 1 deletion falcon/bench/queues/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class RequestIDComponent(object):
def process_request(self, req, resp):
req.context['request_id'] = '<generate ID>'

def process_response(self, req, resp):
def process_response(self, req, resp, resource):
resp.set_header('X-Request-ID', req.context['request_id'])


Expand Down

0 comments on commit 5886e64

Please sign in to comment.