diff options
author | Lawouach <sh@defuze.org> | 2014-05-13 21:13:35 +0200 |
---|---|---|
committer | Lawouach <sh@defuze.org> | 2014-05-13 21:13:35 +0200 |
commit | 6d158e1afb5d3c171e185327557eaece36df57ba (patch) | |
tree | 568f004ed52bf0d8bae54958086b67c4931ee98d | |
parent | da4b375338076f1d2725dd0f69b021b9a24772c9 (diff) | |
download | cherrypy-6d158e1afb5d3c171e185327557eaece36df57ba.tar.gz |
restful dispatchers for dynamic dispatching
-rw-r--r-- | sphinx/source/advanced.rst | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/sphinx/source/advanced.rst b/sphinx/source/advanced.rst index 936a28f8..bf1e7f8c 100644 --- a/sphinx/source/advanced.rst +++ b/sphinx/source/advanced.rst @@ -47,6 +47,168 @@ Obviously, your aliases may be whatever suits your needs. The alias may be a single string or a list of them. +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 help 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/track/06/polly + +Notice finally how the whole stack of segments is passed to each +page handler so that you have the full context. + + Response timeouts ################# |