| 1 |
<?xml version="1.0" encoding="UTF-8"?> |
|---|
| 2 |
<section xmlns:db="http://docbook.org/docbook-ng" xmlns:xi="http://www.w3.org/2001/XInclude" |
|---|
| 3 |
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:id="globaloverviewcherrypy"> |
|---|
| 4 |
<title>Global Overview</title> |
|---|
| 5 |
<section> |
|---|
| 6 |
<title>Mapping URI's to handlers</title> |
|---|
| 7 |
<para>CherryPy has lots of fancy features to help you manage HTTP messages. But the most |
|---|
| 8 |
fundamental thing it does is allow you to map URI's to handler functions. It does this in a |
|---|
| 9 |
very straightforward way: the path portion of a URI is heirarchical, so CherryPy uses a |
|---|
| 10 |
parallel heirarchy of objects, starting with <code>cherrypy.root</code>. If your application |
|---|
| 11 |
receives a request for "/admin/user?name=idunno", then CherryPy will try to find the handler: |
|---|
| 12 |
<code>cherrypy.root.admin.user</code>. If it exists, is callable, and has an "exposed = True" |
|---|
| 13 |
attribute, then CherryPy will hand off control to that function. Any URI parameters (like |
|---|
| 14 |
"name=idunno", above) are passed to the handler as keyword arguments.</para> |
|---|
| 15 |
<section> |
|---|
| 16 |
<title>Index methods</title> |
|---|
| 17 |
<para>There are some special cases, however. To what handler should we map a path like |
|---|
| 18 |
"/admin/search/"? Note the trailing slash after "search"—it indicates that our path has |
|---|
| 19 |
three components: "admin", "search", and "". Static webservers interpret this to mean |
|---|
| 20 |
that the <code>search</code> object is a directory, and, since the third component is |
|---|
| 21 |
blank, they use an <code>index.html</code> file if it exists. CherryPy is a dynamic |
|---|
| 22 |
webserver, so it allows you to specify an <code>index</code> method to handle this. In |
|---|
| 23 |
our example, CherryPy will look for a handler at |
|---|
| 24 |
<code>cherrypy.root.admin.search.index</code>. Let's pause and show our example |
|---|
| 25 |
application so far:</para> |
|---|
| 26 |
<example> |
|---|
| 27 |
<title>Sample application (handler mapping example)</title> |
|---|
| 28 |
<programlisting>import cherrypy |
|---|
| 29 |
|
|---|
| 30 |
class Root: |
|---|
| 31 |
def index(self): |
|---|
| 32 |
return "Hello, world!" |
|---|
| 33 |
index.exposed = True |
|---|
| 34 |
|
|---|
| 35 |
class Admin: |
|---|
| 36 |
def user(self, name=""): |
|---|
| 37 |
return "You asked for user '%s'" % name |
|---|
| 38 |
user.exposed = True |
|---|
| 39 |
|
|---|
| 40 |
class Search: |
|---|
| 41 |
def index(self): |
|---|
| 42 |
return search_page() |
|---|
| 43 |
index.exposed = True |
|---|
| 44 |
|
|---|
| 45 |
cherrypy.root = Root() |
|---|
| 46 |
cherrypy.root.admin = Admin() |
|---|
| 47 |
cherrypy.root.admin.search = Search()</programlisting> |
|---|
| 48 |
</example> |
|---|
| 49 |
<para>So far, we have three exposed handlers:</para> |
|---|
| 50 |
<itemizedlist> |
|---|
| 51 |
<listitem> |
|---|
| 52 |
<para><code>root.index</code>. This will be called for the URI's "/" and |
|---|
| 53 |
"/index".</para> |
|---|
| 54 |
</listitem> |
|---|
| 55 |
<listitem> |
|---|
| 56 |
<para><code>root.admin.user</code>. This will be called for the URI |
|---|
| 57 |
"/admin/user".</para> |
|---|
| 58 |
</listitem> |
|---|
| 59 |
<listitem> |
|---|
| 60 |
<para><code>root.admin.search.index</code>. This will be called for the URI's |
|---|
| 61 |
"/admin/search/" and "/admin/search".</para> |
|---|
| 62 |
</listitem> |
|---|
| 63 |
</itemizedlist> |
|---|
| 64 |
<para>Yes, you read that third line correctly: <code>root.admin.search.index</code> will |
|---|
| 65 |
be called whether or not the URI has a trailing slash. Actually, that isn't quite true; |
|---|
| 66 |
CherryPy will answer a request for "/admin/search" (without the slash) with an HTTP |
|---|
| 67 |
Redirect response. Most browsers will then request "/admin/search/" as the redirection |
|---|
| 68 |
suggests, and <emphasis>then</emphasis> our <code>root.admin.search.index</code> handler |
|---|
| 69 |
will be called. But the final outcome is the same.</para> |
|---|
| 70 |
</section> |
|---|
| 71 |
<section> |
|---|
| 72 |
<title>Positional Parameters</title> |
|---|
| 73 |
<para>Now, let's consider another special case. What if, instead of passing a user name |
|---|
| 74 |
as a parameter, we wish to use a user id as part of the path? What to do with a URI like |
|---|
| 75 |
"/admin/user/8173/schedule"? This is intended to reference the schedule belonging to |
|---|
| 76 |
"user #8173", but we certainly don't want to have a separate function for each user |
|---|
| 77 |
id!</para> |
|---|
| 78 |
<para>CherryPy allows you to map a single handler to multiple URI's with the simple |
|---|
| 79 |
approach of <emphasis>not writing handlers you don't need</emphasis>. If a node in the |
|---|
| 80 |
<code>cherrypy.root</code> tree doesn't have any children, that node will be called for |
|---|
| 81 |
all of its child paths, and CherryPy will pass the leftover path info as positional |
|---|
| 82 |
arguments. In our example, CherryPy will call <code>cherrypy.root.admin.user("8173", |
|---|
| 83 |
"schedule")</code>. Let's rewrite our user method to handle such requests:</para> |
|---|
| 84 |
<example> |
|---|
| 85 |
<title>A user method which handles positional parameters</title> |
|---|
| 86 |
<programlisting>class Admin: |
|---|
| 87 |
def user(self, *args): |
|---|
| 88 |
if not args: |
|---|
| 89 |
raise cherrypy.HTTPError(400, "A user id was expected but not supplied.") |
|---|
| 90 |
id = args.pop(0) |
|---|
| 91 |
if args and args[0] == 'schedule': |
|---|
| 92 |
return self.schedule(id) |
|---|
| 93 |
return "You asked for user '%s'" % id |
|---|
| 94 |
user.exposed = True</programlisting> |
|---|
| 95 |
</example> |
|---|
| 96 |
<para>Note that this is different behavior than CherryPy 2.1, which only allowed |
|---|
| 97 |
positional params for methods named "default".</para> |
|---|
| 98 |
</section> |
|---|
| 99 |
<section> |
|---|
| 100 |
<title>Default methods</title> |
|---|
| 101 |
<para>Are you ready for another special case? What handler is called in our example if |
|---|
| 102 |
you request the URI "/not/a/valid/path"? Given the behavior we have described up to this |
|---|
| 103 |
point, you might deduce that the <code>root.index</code> method will end up handling |
|---|
| 104 |
<emphasis>any</emphasis> path that can't be mapped elsewhere. This would mean, in effect, |
|---|
| 105 |
that CherryPy applications with a <code>root.index</code> could never return a "404 Not |
|---|
| 106 |
Found" response!</para> |
|---|
| 107 |
<para>To prevent this, CherryPy doesn't try to call index methods unless they are |
|---|
| 108 |
attached to the last node in the path; in our example, the only index method that might |
|---|
| 109 |
be called would be a <code>root.not.a.valid.path.index</code> method. If you truly want |
|---|
| 110 |
an intermediate index method to receive positional parameters, well, you can't do that. |
|---|
| 111 |
But what you can do is define a <code>default</code> method to do that for you, instead |
|---|
| 112 |
of an <code>index</code> method. If we wanted our <code>cherrypy.root</code> to handle |
|---|
| 113 |
any child path, and receive positional parameters, we could rewrite it like this:</para> |
|---|
| 114 |
<example> |
|---|
| 115 |
<title>A <code>default</code> method example</title> |
|---|
| 116 |
<programlisting>class Root: |
|---|
| 117 |
def index(self): |
|---|
| 118 |
return "Hello, world!" |
|---|
| 119 |
index.exposed = True |
|---|
| 120 |
|
|---|
| 121 |
def default(self, *args): |
|---|
| 122 |
return "Extra path info: %s" % repr(args) |
|---|
| 123 |
default.exposed = True</programlisting> |
|---|
| 124 |
</example> |
|---|
| 125 |
<para>This new Root class would handle the URI's "/" and "/index" via the |
|---|
| 126 |
<code>index</code> method, and would handle URI's like "/not/a/valid/path" and |
|---|
| 127 |
"/admin/unknown" via the <code>default</code> method.</para> |
|---|
| 128 |
</section> |
|---|
| 129 |
<section> |
|---|
| 130 |
<title>Traversal examples</title> |
|---|
| 131 |
<para>For those of you who need to see in exactly what order CherryPy will try various |
|---|
| 132 |
handlers, here are some examples, using the application above. We always start by trying |
|---|
| 133 |
to find the longest object path first, and then working backwards until an exposed, |
|---|
| 134 |
callable handler is found:</para> |
|---|
| 135 |
<example> |
|---|
| 136 |
<title>Traversal examples</title> |
|---|
| 137 |
<programlisting>"/admin/user/8192/schedule" |
|---|
| 138 |
Trying to reach cherrypy.root.admin.user.8192.schedule.index... |
|---|
| 139 |
cherrypy.root exists? Yes. |
|---|
| 140 |
.root.admin exists? Yes. |
|---|
| 141 |
.admin.user exists? Yes. |
|---|
| 142 |
.user.8192 exists? No. |
|---|
| 143 |
.user.default is callable and exposed? No. |
|---|
| 144 |
.admin.user is callable and exposed? Yes. Call it. |
|---|
| 145 |
|
|---|
| 146 |
"/admin/search/" |
|---|
| 147 |
Trying to reach cherrypy.root.admin.search.index... |
|---|
| 148 |
cherrypy.root exists? Yes. |
|---|
| 149 |
.root.admin exists? Yes. |
|---|
| 150 |
.admin.search exists? Yes. |
|---|
| 151 |
.search.index exists? Yes. Path exhausted. |
|---|
| 152 |
.search.index is callable and exposed? Yes. Call it. |
|---|
| 153 |
|
|---|
| 154 |
"/admin/unknown" |
|---|
| 155 |
Trying to reach cherrypy.root.admin.unknown.index... |
|---|
| 156 |
cherrypy.root exists? Yes. |
|---|
| 157 |
.root.admin exists? Yes. |
|---|
| 158 |
.admin.unknown exists? No. |
|---|
| 159 |
.admin.default is callable and exposed? No. |
|---|
| 160 |
.root.admin is callable and exposed? No. |
|---|
| 161 |
.root.default is callable and exposed? Yes. Call it.</programlisting> |
|---|
| 162 |
</example> |
|---|
| 163 |
</section> |
|---|
| 164 |
</section> |
|---|
| 165 |
</section> |
|---|