diff options
Diffstat (limited to 'docs/advanced.rst')
-rw-r--r-- | docs/advanced.rst | 666 |
1 files changed, 0 insertions, 666 deletions
diff --git a/docs/advanced.rst b/docs/advanced.rst deleted file mode 100644 index 731fa1bb..00000000 --- a/docs/advanced.rst +++ /dev/null @@ -1,666 +0,0 @@ -.. _advanced: - -Advanced --------- - -CherryPy has support for more advanced features that these sections -will describe. - -.. contents:: - :depth: 4 - -.. _aliases: - -Set aliases to page handlers -############################ - -A fairly unknown, yet useful, feature provided by the :func:`cherrypy.expose` -decorator is to support aliases. - -Let's use the template provided by :ref:`tutorial 03 <tut03>`: - -.. code-block:: python - - import random - import string - - import cherrypy - - class StringGenerator(object): - @cherrypy.expose(['generer', 'generar']) - def generate(self, length=8): - return ''.join(random.sample(string.hexdigits, int(length))) - - if __name__ == '__main__': - cherrypy.quickstart(StringGenerator()) - -In this example, we create localized aliases for -the page handler. This means the page handler will be -accessible via: - -- /generate -- /generer (French) -- /generar (Spanish) - -Obviously, your aliases may be whatever suits your needs. - -.. note:: - - The alias may be a single string or a list of them. - -.. _restful: - -RESTful-style dispatching -######################### - -The term `RESTful URL` is sometimes used to talk about friendly URLs -that nicely map to the entities an application exposes. - -.. important:: - - We will not enter the debate around what is restful or not but we will - showcase two mechanisms to implement the usual idea in your - CherryPy application. - -Let's assume you wish to create an application that exposes -music bands and their records. Your application will probably have -the following URLs: - -- http://hostname/<bandname>/ -- http://hostname/<bandname>/albums/<recordname>/ - -It's quite clear you would not create a page handler named after -every possible band in the world. This means you will need a page handler -that acts as a proxy for all of them. - -The default dispatcher cannot deal with that scenario on its own -because it expects page handlers to be explicitely declared in your -source code. Luckily, CherryPy provides ways to support those use cases. - -.. seealso:: - - This section extends from this `stackoverflow response <http://stackoverflow.com/a/15789415/1363905>`_. - -The special _cp_dispatch method -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -``_cp_dispatch`` is a special method you declare in any of your :term:`controller` -to massage the remaining segments before CherryPy gets to process them. -This offers you the capacity to remove, add or otherwise handle any segment -you wish and, even, entirely change the remaining parts. - -.. code-block:: python - - import cherrypy - - class Band(object): - def __init__(self): - self.albums = Album() - - def _cp_dispatch(self, vpath): - if len(vpath) == 1: - cherrypy.request.params['name'] = vpath.pop() - return self - - if len(vpath) == 3: - cherrypy.request.params['artist'] = vpath.pop(0) # /band name/ - vpath.pop(0) # /albums/ - cherrypy.request.params['title'] = vpath.pop(0) # /album title/ - return self.albums - - return vpath - - @cherrypy.expose - def index(self, name): - return 'About %s...' % name - - class Album(object): - @cherrypy.expose - def index(self, artist, title): - return 'About %s by %s...' % (title, artist) - - if __name__ == '__main__': - cherrypy.quickstart(Band()) - -Notice how the controller defines `_cp_dispatch`, it takes -a single argument, the URL path info broken into its segments. - -The method can inspect and manipulate the list of segments, -removing any or adding new segments at any position. The new list of -segments is then sent to the dispatcher which will use it -to locate the appropriate resource. - -In the above example, you should be able to go to the following URLs: - -- http://localhost:8080/nirvana/ -- http://localhost:8080/nirvana/albums/nevermind/ - -The ``/nirvana/`` segment is associated to the band and -the ``/nevermind/`` segment relates to the album. - -To achieve this, our `_cp_dispatch` method works on the idea -that the default dispatcher matches URLs against page handler -signatures and their position in the tree of handlers. - -In this case, we take the dynamic segments in the URL (band and record names), -we inject them into the request parameters and we remove them -from the segment lists as if they had never been there in the first place. - -In other words, `_cp_dispatch` makes it as if we were -working on the following URLs: - -- http://localhost:8080/?artist=nirvana -- http://localhost:8080/albums/?artist=nirvana&title=nevermind - - -The popargs decorator -^^^^^^^^^^^^^^^^^^^^^ -:func:`cherrypy.popargs` is more straightforward as it gives a name to any segment -that CherryPy wouldn't be able to interpret otherwise. This makes the -matching of segments with page handler signatures easier and helps CherryPy -understand the structure of your URL. - -.. code-block:: python - - import cherrypy - - @cherrypy.popargs('name') - class Band(object): - def __init__(self): - self.albums = Album() - - @cherrypy.expose - def index(self, name): - return 'About %s...' % name - - @cherrypy.popargs('title') - class Album(object): - @cherrypy.expose - def index(self, name, title): - return 'About %s by %s...' % (title, name) - - if __name__ == '__main__': - cherrypy.quickstart(Band()) - -This works similarly to `_cp_dispatch` but, as said above, is more -explicit and localized. It says: - -- take the first segment and store it into a parameter name `band` -- take again the first segment (since we removed the previous first) - and store it into a parameter named `title` - -Note that the decorator accepts more than a single binding. For instance: - -.. code-block:: python - - @cherrypy.popargs('title') - class Album(object): - def __init__(self): - self.tracks = Track() - - @cherrypy.popargs('num', 'track') - class Track(object): - @cherrypy.expose - def index(self, name, title, num, track): - ... - -This would handle the following URL: - -- http://localhost:8080/nirvana/albums/nevermind/tracks/06/polly - -Notice finally how the whole stack of segments is passed to each -page handler so that you have the full context. - -Streaming the response body -########################### - -CherryPy handles HTTP requests, packing and unpacking the low-level details, -then passing control to your application's :term:`page handler`, which produce -the body of the response. CherryPy allows you to return body content in a -variety of types: a string, a list of strings, a file. CherryPy also allows you -to *yield* content, rather than *return* content. When you use "yield", you also -have the option of streaming the output. - -**In general, it is safer and easier to not stream output.** Therefore, -streaming output is off by default. Streaming output and also using sessions -requires a good understanding of :py:mod:`how session locks work -<cherrypy.lib.sessions>`. - -The "normal" CherryPy response process -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When you provide content from your page handler, CherryPy manages the -conversation between the HTTP server and your code like this: - -.. image:: _static/images/cpreturn.gif - -Notice that the HTTP server gathers all output first and then writes everything -to the client at once: status, headers, and body. This works well for static or -simple pages, since the entire response can be changed at any time, either in -your application code, or by the CherryPy framework. - -How "streaming output" works with CherryPy -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When you set the config entry "response.stream" to True (and use "yield"), -CherryPy manages the conversation between the HTTP server and your code like this: - -.. image:: _static/images/cpyield.gif - -When you stream, your application doesn't immediately pass raw body content -back to CherryPy or to the HTTP server. Instead, it passes back a generator. -At that point, CherryPy finalizes the status and headers, **before** the -generator has been consumed, or has produced any output. This is necessary to -allow the HTTP server to send the headers and pieces of the body as they become -available. - -Once CherryPy has set the status and headers, it sends them to the HTTP server, -which then writes them out to the client. From that point on, the CherryPy -framework mostly steps out of the way, and the HTTP server essentially requests -content directly from your application code (your page handler method). - -Therefore, when streaming, if an error occurs within your page handler, -CherryPy will not catch it--the HTTP server will catch it. Because the headers -(and potentially some of the body) have already been written to the client, -the server *cannot* know a safe means of handling the error, and will therefore -simply close the connection (the current, builtin servers actually write out a -short error message in the body, but this may be changed, and is not guaranteed -behavior for all HTTP servers you might use with CherryPy). - -In addition, you cannot manually modify the status or headers within your page -handler if that handler method is a streaming generator, because the method will -not be iterated over until after the headers have been written to the client. -**This includes raising exceptions like HTTPError, NotFound, InternalRedirect -and HTTPRedirect.** To use a streaming generator while modifying headers, you -would have to return a generator that is separate from (or embedded in) your -page handler. For example: - -.. code-block:: python - - class Root: - @cherrypy.expose - def thing(self): - cherrypy.response.headers['Content-Type'] = 'text/plain' - if not authorized(): - raise cherrypy.NotFound() - def content(): - yield "Hello, " - yield "world" - return content() - thing._cp_config = {'response.stream': True} - -Streaming generators are sexy, but they play havoc with HTTP. CherryPy allows -you to stream output for specific situations: pages which take many minutes to -produce, or pages which need a portion of their content immediately output to -the client. Because of the issues outlined above, **it is usually better to -flatten (buffer) content rather than stream content**. Do otherwise only when -the benefits of streaming outweigh the risks. - -Response timeouts -################# - -CherryPy responses include 3 attributes related to time: - - * ``response.time``: the :func:`time.time` at which the response began - * ``response.timeout``: the number of seconds to allow responses to run - * ``response.timed_out``: a boolean indicating whether the response has - timed out (default False). - -The request processing logic inspects the value of ``response.timed_out`` at -various stages; if it is ever True, then :class:`TimeoutError` is raised. -You are free to do the same within your own code. - -Rather than calculate the difference by hand, you can call -``response.check_timeout`` to set ``timed_out`` for you. - -.. note:: - - The default response timeout is 300 seconds. - -.. _timeoutmonitor: - -Timeout Monitor -^^^^^^^^^^^^^^^ - -In addition, CherryPy includes a ``cherrypy.engine.timeout_monitor`` which -monitors all active requests in a separate thread; periodically, it calls -``check_timeout`` on them all. It is subscribed by default. To turn it off: - -.. code-block:: ini - - [global] - engine.timeout_monitor.on: False - -or: - -.. code-block:: python - - cherrypy.engine.timeout_monitor.unsubscribe() - -You can also change the interval (in seconds) at which the timeout monitor runs: - -.. code-block:: ini - - [global] - engine.timeout_monitor.frequency: 60 * 60 - -The default is once per minute. The above example changes that to once per hour. - -Deal with signals -################# - -This :ref:`engine plugin <busplugins>` is instantiated automatically as -`cherrypy.engine.signal_handler`. -However, it is only *subscribed* automatically by :func:`cherrypy.quickstart`. -So if you want signal handling and you're calling: - -.. code-block:: python - - tree.mount() - engine.start() - engine.block() - -on your own, be sure to add before you start the engine: - -.. code-block:: python - - engine.signals.subscribe() - -.. index:: Windows, Ctrl-C, shutdown -.. _windows-console: - -Windows Console Events -^^^^^^^^^^^^^^^^^^^^^^ - -Microsoft Windows uses console events to communicate some signals, like Ctrl-C. -When deploying CherryPy on Windows platforms, you should obtain the -`Python for Windows Extensions <http://sourceforge.net/projects/pywin32/>`_; -once you have them installed, CherryPy will handle Ctrl-C and other -console events (CTRL_C_EVENT, CTRL_LOGOFF_EVENT, CTRL_BREAK_EVENT, -CTRL_SHUTDOWN_EVENT, and CTRL_CLOSE_EVENT) automatically, shutting down the -bus in preparation for process exit. - - -Securing your server -#################### - -.. note:: - - This section is not meant as a complete guide to securing - a web application or ecosystem. Please review the various - guides provided at `OWASP <https://www.owasp.org/index.php/Main_Page>`_. - - -There are several settings that can be enabled to make CherryPy pages more secure. These include: - - Transmitting data: - - #. Use Secure Cookies - - Rendering pages: - - #. Set HttpOnly cookies - #. Set XFrame options - #. Enable XSS Protection - #. Set the Content Security Policy - -An easy way to accomplish this is to set headers with a tool -and wrap your entire CherryPy application with it: - -.. code-block:: python - - import cherrypy - - def secureheaders(): - headers = cherrypy.response.headers - headers['X-Frame-Options'] = 'DENY' - headers['X-XSS-Protection'] = '1; mode=block' - headers['Content-Security-Policy'] = "default-src='self'" - - # set the priority according to your needs if you are hooking something - # else on the 'before_finalize' hook point. - cherrypy.tools.secureheaders = cherrypy.Tool('before_finalize', secureheaders, priority=60) - -.. note:: - - Read more about `those headers <https://www.owasp.org/index.php/List_of_useful_HTTP_headers>`_. - -Then, in the :ref:`configuration file <config>` (or any other place that you want to enable the tool): - -.. code-block:: ini - - [/] - tools.secureheaders.on = True - - -If you use :ref:`sessions <basicsession>` you can also enable these settings: - -.. code-block:: ini - - [/] - tools.sessions.on = True - # increase security on sessions - tools.sessions.secure = True - tools.sessions.httponly = True - - -If you use SSL you can also enable Strict Transport Security: - -.. code-block:: python - - # add this to secureheaders(): - # only add Strict-Transport headers if we're actually using SSL; see the ietf spec - # "An HSTS Host MUST NOT include the STS header field in HTTP responses - # conveyed over non-secure transport" - # http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14#section-7.2 - if (cherrypy.server.ssl_certificate != None and cherrypy.server.ssl_private_key != None): - headers['Strict-Transport-Security'] = 'max-age=31536000' # one year - -Next, you should probably use :ref:`SSL <ssl>`. - -Multiple HTTP servers support -############################# - -CherryPy starts its own HTTP server whenever you start the -engine. In some cases, you may wish to host your application -on more than a single port. This is easily achieved: - -.. code-block:: python - - from cherrypy._cpserver import Server - server = Server() - server.socket_port = 8090 - server.subscribe() - -You can create as many :class:`server <cherrypy._cpserver.Server>` -server instances as you need, once :ref:`subscribed <busplugins>`, -they will follow the CherryPy engine's life-cycle. - -WSGI support -############ - -CherryPy supports the WSGI interface defined in :pep:`333` -as well as its updates in :pep:`3333`. It means the following: - -- You can host a foreign WSGI application with the CherryPy server -- A CherryPy application can be hosted by another WSGI server - -Make your CherryPy application a WSGI application -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -A WSGI application can be obtained from your application as follows: - -.. code-block:: python - - import cherrypy - wsgiapp = cherrypy.Application(StringGenerator(), '/', config=myconf) - -Simply use the `wsgiapp` instance in any WSGI-aware server. - -.. _hostwsgiapp: - -Host a foreign WSGI application in CherryPy -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Assuming you have a WSGI-aware application, you can host it -in your CherryPy server using the :meth:`cherrypy.tree.graft <cherrypy._cptree.Tree.graft>` -facility. - -.. code-block:: python - - def raw_wsgi_app(environ, start_response): - status = '200 OK' - response_headers = [('Content-type','text/plain')] - start_response(status, response_headers) - return ['Hello world!'] - - cherrypy.tree.graft(raw_wsgi_app, '/') - -.. important:: - - You cannot use tools with a foreign WSGI application. - However, you can still benefit from the - :ref:`CherryPy bus <buspattern>`. - - -No need for the WSGI interface? -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The default CherryPy HTTP server supports the WSGI interfaces -defined in :pep:`333` and :pep:`3333`. However, if your application -is a pure CherryPy application, you can switch to a HTTP -server that by-passes the WSGI layer altogether. It will provide -a slight performance increase. - -.. code-block:: python - - import cherrypy - - class Root(object): - @cherrypy.expose - def index(self): - return "Hello World!" - - if __name__ == '__main__': - from cherrypy._cpnative_server import CPHTTPServer - cherrypy.server.httpserver = CPHTTPServer(cherrypy.server) - - cherrypy.quickstart(Root(), '/') - -.. important:: - - Using the native server, you will not be able to - graft a WSGI application as shown in the previous section. - Doing so will result in a server error at runtime. - -WebSocket support -################# - -`WebSocket <http://tools.ietf.org/html/rfc6455>`_ -is a recent application protocol that came to life -from the HTML5 working-group in response to the needs for -bi-directional communication. Various hacks had been proposed -such as Comet, polling, etc. - -WebSocket is a socket that starts its life from a HTTP upgrade request. -Once the upgrade is performed, the underlying socket is -kept opened but not used in a HTTP context any longer. -Instead, both connected endpoints may use the socket -to push data to the other end. - -CherryPy itself does not support WebSocket, but the feature -is provided by an external library called -`ws4py <https://github.com/Lawouach/WebSocket-for-Python>`_. - -Database support -################ - -CherryPy does not bundle any database access but its architecture -makes it easy to integrate common database interfaces such as -the DB-API specified in :pep:`249`. Alternatively, you can also -use an `ORM <en.wikipedia.org/wiki/Object-relational_mapping>`_ -such as `SQLAlchemy <http://sqlalchemy.readthedocs.org>`_ -or `SQLObject <https://pypi.python.org/pypi/SQLObject/>`_. - -You will find `here <https://bitbucket.org/Lawouach/cherrypy-recipes/src/tip/web/database/sql_alchemy/>`_ -a recipe on how integrating SQLAlchemy using a mix of -:ref:`plugins <busplugins>` and :ref:`tools <tools>`. - -HTML Templating support -####################### - -CherryPy does not provide any HTML template but its architecture -makes it easy to integrate one. Popular ones are `Mako <http://www.makotemplates.org>`_ -or `Jinja2 <http://jinja.pocoo.org/docs/>`_. - -You will find `here <https://bitbucket.org/Lawouach/cherrypy-recipes/src/tip/web/templating/>`_ -a recipe on how to integrate them using a mix -:ref:`plugins <busplugins>` and :ref:`tools <tools>`. - -Testing your application -######################## - -Web applications, like any other kind of code, must be tested. CherryPy provides -a :class:`helper class <cherrypy.test.helper.CPWebCase>` to ease writing -functional tests. - -Here is a simple example for a basic echo application: - -.. code-block:: python - - import cherrypy - from cherrypy.test import helper - - class SimpleCPTest(helper.CPWebCase): - def setup_server(): - class Root(object): - @cherrypy.expose - def echo(self, message): - return message - - cherrypy.tree.mount(Root()) - setup_server = staticmethod(setup_server) - - def test_message_should_be_returned_as_is(self): - self.getPage("/echo?message=Hello%20world") - self.assertStatus('200 OK') - self.assertHeader('Content-Type', 'text/html;charset=utf-8') - self.assertBody('Hello world') - - def test_non_utf8_message_will_fail(self): - """ - CherryPy defaults to decode the query-string - using UTF-8, trying to send a query-string with - a different encoding will raise a 404 since - it considers it's a different URL. - """ - self.getPage("/echo?message=A+bient%F4t", - headers=[ - ('Accept-Charset', 'ISO-8859-1,utf-8'), - ('Content-Type', 'text/html;charset=ISO-8859-1') - ] - ) - self.assertStatus('404 Not Found') - -As you can see the, test inherits from that helper class. You should -setup your application and mount it as per-usual. Then, define your various -tests and call the helper :meth:`~cherrypy.test.helper.CPWebCase.getPage` -method to perform a request. Simply use the various specialized -assert* methods to validate your workflow and data. - -You can then run the test using `py.test <http://pytest.org/latest/>`_ as follows: - -.. code-block:: bash - - $ py.test -s test_echo_app.py - -The ``-s`` is necessary because the CherryPy class also wraps stdin and stdout. - -.. note:: - - Although they are written using the typical pattern the - :mod:`unittest` module supports, they are not bare unit tests. - Indeed, a whole CherryPy stack is started for you and runs your application. - If you want to really unit test your CherryPy application, meaning without - having to start a server, you may want to have a look at - this `recipe <https://bitbucket.org/Lawouach/cherrypy-recipes/src/tip/testing/unit/serverless/>`_. |