diff options
| author | Christoph Zwerschke <cito@online.de> | 2016-04-19 20:07:12 +0200 |
|---|---|---|
| committer | Christoph Zwerschke <cito@online.de> | 2016-04-19 20:07:12 +0200 |
| commit | 3629c49e46207ff5162a82883c14937e6ef4c186 (patch) | |
| tree | 1306181202cb8313f16080789f5b9ab1eeb61d53 /docs/narr/subrequest.rst | |
| parent | 804ba0b2f434781e77d2b5191f1cd76a490f6610 (diff) | |
| parent | 6c16fb020027fac47e4d2e335cd9e264dba8aa3b (diff) | |
| download | pyramid-3629c49e46207ff5162a82883c14937e6ef4c186.tar.gz pyramid-3629c49e46207ff5162a82883c14937e6ef4c186.tar.bz2 pyramid-3629c49e46207ff5162a82883c14937e6ef4c186.zip | |
Merge remote-tracking branch 'refs/remotes/Pylons/master'
Diffstat (limited to 'docs/narr/subrequest.rst')
| -rw-r--r-- | docs/narr/subrequest.rst | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/docs/narr/subrequest.rst b/docs/narr/subrequest.rst new file mode 100644 index 000000000..7c847de50 --- /dev/null +++ b/docs/narr/subrequest.rst @@ -0,0 +1,331 @@ +.. index:: + single: subrequest + +.. _subrequest_chapter: + +Invoking a Subrequest +===================== + +.. versionadded:: 1.4 + +:app:`Pyramid` allows you to invoke a subrequest at any point during the +processing of a request. Invoking a subrequest allows you to obtain a +:term:`response` object from a view callable within your :app:`Pyramid` +application while you're executing a different view callable within the same +application. + +Here's an example application which uses a subrequest: + +.. code-block:: python + :linenos: + + from wsgiref.simple_server import make_server + from pyramid.config import Configurator + from pyramid.request import Request + + def view_one(request): + subreq = Request.blank('/view_two') + response = request.invoke_subrequest(subreq) + return response + + def view_two(request): + request.response.body = 'This came from view_two' + return request.response + + if __name__ == '__main__': + config = Configurator() + config.add_route('one', '/view_one') + config.add_route('two', '/view_two') + config.add_view(view_one, route_name='one') + config.add_view(view_two, route_name='two') + app = config.make_wsgi_app() + server = make_server('0.0.0.0', 8080, app) + server.serve_forever() + +When ``/view_one`` is visted in a browser, the text printed in the browser pane +will be ``This came from view_two``. The ``view_one`` view used the +:meth:`pyramid.request.Request.invoke_subrequest` API to obtain a response from +another view (``view_two``) within the same application when it executed. It +did so by constructing a new request that had a URL that it knew would match +the ``view_two`` view registration, and passed that new request along to +:meth:`pyramid.request.Request.invoke_subrequest`. The ``view_two`` view +callable was invoked, and it returned a response. The ``view_one`` view +callable then simply returned the response it obtained from the ``view_two`` +view callable. + +Note that it doesn't matter if the view callable invoked via a subrequest +actually returns a *literal* Response object. Any view callable that uses a +renderer or which returns an object that can be interpreted by a response +adapter when found and invoked via +:meth:`pyramid.request.Request.invoke_subrequest` will return a Response +object: + +.. code-block:: python + :linenos: + :emphasize-lines: 11 + + from wsgiref.simple_server import make_server + from pyramid.config import Configurator + from pyramid.request import Request + + def view_one(request): + subreq = Request.blank('/view_two') + response = request.invoke_subrequest(subreq) + return response + + def view_two(request): + return 'This came from view_two' + + if __name__ == '__main__': + config = Configurator() + config.add_route('one', '/view_one') + config.add_route('two', '/view_two') + config.add_view(view_one, route_name='one') + config.add_view(view_two, route_name='two', renderer='string') + app = config.make_wsgi_app() + server = make_server('0.0.0.0', 8080, app) + server.serve_forever() + +Even though the ``view_two`` view callable returned a string, it was invoked in +such a way that the ``string`` renderer associated with the view registration +that was found turned it into a "real" response object for consumption by +``view_one``. + +Being able to unconditionally obtain a response object by invoking a view +callable indirectly is the main advantage to using +:meth:`pyramid.request.Request.invoke_subrequest` instead of simply importing a +view callable and executing it directly. Note that there's not much advantage +to invoking a view using a subrequest if you *can* invoke a view callable +directly. Subrequests are slower and are less convenient if you actually do +want just the literal information returned by a function that happens to be a +view callable. + +Note that, by default, if a view callable invoked by a subrequest raises an +exception, the exception will be raised to the caller of +:meth:`~pyramid.request.Request.invoke_subrequest` even if you have a +:term:`exception view` configured: + +.. code-block:: python + :linenos: + :emphasize-lines: 11-16 + + from wsgiref.simple_server import make_server + from pyramid.config import Configurator + from pyramid.request import Request + + def view_one(request): + subreq = Request.blank('/view_two') + response = request.invoke_subrequest(subreq) + return response + + def view_two(request): + raise ValueError('foo') + + def excview(request): + request.response.body = b'An exception was raised' + request.response.status_int = 500 + return request.response + + if __name__ == '__main__': + config = Configurator() + config.add_route('one', '/view_one') + config.add_route('two', '/view_two') + config.add_view(view_one, route_name='one') + config.add_view(view_two, route_name='two', renderer='string') + config.add_view(excview, context=Exception) + app = config.make_wsgi_app() + server = make_server('0.0.0.0', 8080, app) + server.serve_forever() + +When we run the above code and visit ``/view_one`` in a browser, the +``excview`` :term:`exception view` will *not* be executed. Instead, the call +to :meth:`~pyramid.request.Request.invoke_subrequest` will cause a +:exc:`ValueError` exception to be raised and a response will never be +generated. We can change this behavior; how to do so is described below in our +discussion of the ``use_tweens`` argument. + +.. index:: + pair: subrequest; use_tweens + +Subrequests with Tweens +----------------------- + +The :meth:`pyramid.request.Request.invoke_subrequest` API accepts two +arguments: a required positional argument ``request``, and an optional keyword +argument ``use_tweens`` which defaults to ``False``. + +The ``request`` object passed to the API must be an object that implements the +Pyramid request interface (such as a :class:`pyramid.request.Request` +instance). If ``use_tweens`` is ``True``, the request will be sent to the +:term:`tween` in the tween stack closest to the request ingress. If +``use_tweens`` is ``False``, the request will be sent to the main router +handler, and no tweens will be invoked. + +In the example above, the call to +:meth:`~pyramid.request.Request.invoke_subrequest` will always raise an +exception. This is because it's using the default value for ``use_tweens``, +which is ``False``. Alternatively, you can pass ``use_tweens=True`` to ensure +that it will convert an exception to a Response if an :term:`exception view` is +configured, instead of raising the exception. This is because exception views +are called by the exception view :term:`tween` as described in +:ref:`exception_views` when any view raises an exception. + +We can cause the subrequest to be run through the tween stack by passing +``use_tweens=True`` to the call to +:meth:`~pyramid.request.Request.invoke_subrequest`, like this: + +.. code-block:: python + :linenos: + :emphasize-lines: 7 + + from wsgiref.simple_server import make_server + from pyramid.config import Configurator + from pyramid.request import Request + + def view_one(request): + subreq = Request.blank('/view_two') + response = request.invoke_subrequest(subreq, use_tweens=True) + return response + + def view_two(request): + raise ValueError('foo') + + def excview(request): + request.response.body = b'An exception was raised' + request.response.status_int = 500 + return request.response + + if __name__ == '__main__': + config = Configurator() + config.add_route('one', '/view_one') + config.add_route('two', '/view_two') + config.add_view(view_one, route_name='one') + config.add_view(view_two, route_name='two', renderer='string') + config.add_view(excview, context=Exception) + app = config.make_wsgi_app() + server = make_server('0.0.0.0', 8080, app) + server.serve_forever() + +In the above case, the call to ``request.invoke_subrequest(subreq)`` will not +raise an exception. Instead, it will retrieve a "500" response from the +attempted invocation of ``view_two``, because the tween which invokes an +exception view to generate a response is run, and therefore ``excview`` is +executed. + +This is one of the major differences between specifying the ``use_tweens=True`` +and ``use_tweens=False`` arguments to +:meth:`~pyramid.request.Request.invoke_subrequest`. ``use_tweens=True`` may +also imply invoking a transaction commit or abort for the logic executed in the +subrequest if you've got ``pyramid_tm`` in the tween list, injecting debug HTML +if you've got ``pyramid_debugtoolbar`` in the tween list, and other +tween-related side effects as defined by your particular tween list. + +The :meth:`~pyramid.request.Request.invoke_subrequest` function also +unconditionally does the following: + +- It manages the threadlocal stack so that + :func:`~pyramid.threadlocal.get_current_request` and + :func:`~pyramid.threadlocal.get_current_registry` work during a request (they + will return the subrequest instead of the original request). + +- It adds a ``registry`` attribute and an ``invoke_subrequest`` attribute (a + callable) to the request object to which it is handed. + +- It sets request extensions (such as those added via + :meth:`~pyramid.config.Configurator.add_request_method` or + :meth:`~pyramid.config.Configurator.set_request_property`) on the subrequest + object passed as ``request``. + +- It causes a :class:`~pyramid.events.NewRequest` event to be sent at the + beginning of request processing. + +- It causes a :class:`~pyramid.events.ContextFound` event to be sent when a + context resource is found. + +- It ensures that the user implied by the request passed in has the necessary + authorization to invoke the view callable before calling it. + +- It calls any :term:`response callback` functions defined within the + subrequest's lifetime if a response is obtained from the Pyramid application. + +- It causes a :class:`~pyramid.events.NewResponse` event to be sent if a + response is obtained. + +- It calls any :term:`finished callback` functions defined within the + subrequest's lifetime. + +The invocation of a subrequest has more or less exactly the same effect as the +invocation of a request received by the :app:`Pyramid` router from a web client +when ``use_tweens=True``. When ``use_tweens=False``, the tweens are skipped +but all the other steps take place. + +It's a poor idea to use the original ``request`` object as an argument to +:meth:`~pyramid.request.Request.invoke_subrequest`. You should construct a new +request instead as demonstrated in the above example, using +:meth:`pyramid.request.Request.blank`. Once you've constructed a request +object, you'll need to massage it to match the view callable that you'd like to +be executed during the subrequest. This can be done by adjusting the +subrequest's URL, its headers, its request method, and other attributes. The +documentation for :class:`pyramid.request.Request` exposes the methods you +should call and attributes you should set on the request that you create, then +massage it into something that will actually match the view you'd like to call +via a subrequest. + +We've demonstrated use of a subrequest from within a view callable, but you can +use the :meth:`~pyramid.request.Request.invoke_subrequest` API from within a +tween or an event handler as well. Even though you can do it, it's usually a +poor idea to invoke :meth:`~pyramid.request.Request.invoke_subrequest` from +within a tween, because tweens already, by definition, have access to a +function that will cause a subrequest (they are passed a ``handle`` function). +It's fine to invoke :meth:`~pyramid.request.Request.invoke_subrequest` from +within an event handler, however. + + +.. index:: + pair: subrequest; exception view + +Invoking an Exception View +-------------------------- + +.. versionadded:: 1.7 + +:app:`Pyramid` apps may define :term:`exception views <exception view>` which +can handle any raised exceptions that escape from your code while processing +a request. By default an unhandled exception will be caught by the ``EXCVIEW`` +:term:`tween`, which will then lookup an exception view that can handle the +exception type, generating an appropriate error response. + +In :app:`Pyramid` 1.7 the :meth:`pyramid.request.Request.invoke_exception_view` +was introduced, allowing a user to invoke an exception view while manually +handling an exception. This can be useful in a few different circumstances: + +- Manually handling an exception losing the current call stack or flow. + +- Handling exceptions outside of the context of the ``EXCVIEW`` tween. The + tween only covers certain parts of the request processing pipeline (See + :ref:`router_chapter`). There are also some corner cases where an exception + can be raised that will still bubble up to middleware, and possibly to the + web server in which case a generic ``500 Internal Server Error`` will be + returned to the client. + +Below is an example usage of +:meth:`pyramid.request.Request.invoke_exception_view`: + +.. code-block:: python + :linenos: + + def foo(request): + try: + some_func_that_errors() + return response + except Exception: + response = request.invoke_exception_view() + if response is not None: + return response + else: + # there is no exception view for this exception, simply + # re-raise and let someone else handle it + raise + +Please note that in most cases you do not need to write code like this, and you +may rely on the ``EXCVIEW`` tween to handle this for you. |
