1 """CherryPy Application and Tree objects."""
5 from cherrypy
._cpcompat
import ntou
6 from cherrypy
import _cpconfig
, _cplogging
, _cprequest
, _cpwsgi
, tools
7 from cherrypy
.lib
import httputil
10 class Application(object):
11 """A CherryPy Application.
13 Servers and gateways should not instantiate Request objects directly.
14 Instead, they should ask an Application object for a request object.
16 An instance of this class may also be used as a WSGI callable
17 (WSGI application object) for itself.
21 """The top-most container of page handlers for this app. Handlers should
22 be arranged in a hierarchy of attributes, matching the expected URI
23 hierarchy; the default dispatcher then searches this hierarchy for a
24 matching handler. When using a dispatcher other than the default,
25 this value may be None."""
28 """A dict of {path: pathconf} pairs, where 'pathconf' is itself a dict
29 of {key: value} pairs."""
31 namespaces
= _cpconfig
.NamespaceSet()
32 toolboxes
= {'tools': cherrypy
.tools
}
35 """A LogManager instance. See _cplogging."""
38 """A CPWSGIApp instance. See _cpwsgi."""
40 request_class
= _cprequest
.Request
41 response_class
= _cprequest
.Response
45 def __init__(self
, root
, script_name
="", config
=None):
46 self
.log
= _cplogging
.LogManager(id(self
), cherrypy
.log
.logger_root
)
48 self
.script_name
= script_name
49 self
.wsgiapp
= _cpwsgi
.CPWSGIApp(self
)
51 self
.namespaces
= self
.namespaces
.copy()
52 self
.namespaces
["log"] = lambda k
, v
: setattr(self
.log
, k
, v
)
53 self
.namespaces
["wsgi"] = self
.wsgiapp
.namespace_handler
55 self
.config
= self
.__class
__.config
.copy()
60 return "%s.%s(%r, %r)" % (self
.__module
__, self
.__class
__.__name
__,
61 self
.root
, self
.script_name
)
63 script_name_doc
= """The URI "mount point" for this app. A mount point is that portion of
64 the URI which is constant for all URIs that are serviced by this
65 application; it does not include scheme, host, or proxy ("virtual host")
68 For example, if script_name is "/my/cool/app", then the URL
69 "http://www.example.com/my/cool/app/page1" might be handled by a
70 "page1" method on the root object.
72 The value of script_name MUST NOT end in a slash. If the script_name
73 refers to the root of the URI, it MUST be an empty string (not "/").
75 If script_name is explicitly set to None, then the script_name will be
76 provided for each call from request.wsgi_environ['SCRIPT_NAME'].
78 def _get_script_name(self
):
79 if self
._script
_name
is None:
80 # None signals that the script name should be pulled from WSGI environ.
81 return cherrypy
.serving
.request
.wsgi_environ
['SCRIPT_NAME'].rstrip("/")
82 return self
._script
_name
83 def _set_script_name(self
, value
):
85 value
= value
.rstrip("/")
86 self
._script
_name
= value
87 script_name
= property(fget
=_get_script_name
, fset
=_set_script_name
,
90 def merge(self
, config
):
91 """Merge the given config into self.config."""
92 _cpconfig
.merge(self
.config
, config
)
94 # Handle namespaces specified in config.
95 self
.namespaces(self
.config
.get("/", {}))
97 def find_config(self
, path
, key
, default
=None):
98 """Return the most-specific value for key along path, or default."""
101 nodeconf
= self
.config
.get(trail
, {})
106 lastslash
= trail
.rfind("/")
109 elif lastslash
== 0 and trail
!= "/":
112 trail
= trail
[:lastslash
]
116 def get_serving(self
, local
, remote
, scheme
, sproto
):
117 """Create and return a Request and Response object."""
118 req
= self
.request_class(local
, remote
, scheme
, sproto
)
121 for name
, toolbox
in self
.toolboxes
.items():
122 req
.namespaces
[name
] = toolbox
124 resp
= self
.response_class()
125 cherrypy
.serving
.load(req
, resp
)
126 cherrypy
.engine
.timeout_monitor
.acquire()
127 cherrypy
.engine
.publish('acquire_thread')
131 def release_serving(self
):
132 """Release the current serving (request and response)."""
133 req
= cherrypy
.serving
.request
135 cherrypy
.engine
.timeout_monitor
.release()
140 cherrypy
.log(traceback
=True, severity
=40)
142 cherrypy
.serving
.clear()
144 def __call__(self
, environ
, start_response
):
145 return self
.wsgiapp(environ
, start_response
)
149 """A registry of CherryPy applications, mounted at diverse points.
151 An instance of this class may also be used as a WSGI callable
152 (WSGI application object), in which case it dispatches to all
158 A dict of the form {script name: application}, where "script name"
159 is a string declaring the URI mount point (no trailing slash), and
160 "application" is an instance of cherrypy.Application (or an arbitrary
161 WSGI callable if you happen to be using a WSGI server)."""
166 def mount(self
, root
, script_name
="", config
=None):
167 """Mount a new app from a root object, script_name, and config.
170 An instance of a "controller class" (a collection of page
171 handler methods) which represents the root of the application.
172 This may also be an Application instance, or None if using
173 a dispatcher other than the default.
176 A string containing the "mount point" of the application.
177 This should start with a slash, and be the path portion of the
178 URL at which to mount the given root. For example, if root.index()
179 will handle requests to "http://www.example.com:8080/dept/app1/",
180 then the script_name argument would be "/dept/app1".
182 It MUST NOT end in a slash. If the script_name refers to the
183 root of the URI, it MUST be an empty string (not "/").
186 A file or dict containing application config.
188 if script_name
is None:
190 "The 'script_name' argument may not be None. Application "
191 "objects may, however, possess a script_name of None (in "
192 "order to inpect the WSGI environ for SCRIPT_NAME upon each "
193 "request). You cannot mount such Applications on this Tree; "
194 "you must pass them to a WSGI server interface directly.")
196 # Next line both 1) strips trailing slash and 2) maps "/" -> "".
197 script_name
= script_name
.rstrip("/")
199 if isinstance(root
, Application
):
201 if script_name
!= "" and script_name
!= app
.script_name
:
202 raise ValueError("Cannot specify a different script name and "
203 "pass an Application instance to cherrypy.mount")
204 script_name
= app
.script_name
206 app
= Application(root
, script_name
)
208 # If mounted at "", add favicon.ico
209 if (script_name
== "" and root
is not None
210 and not hasattr(root
, "favicon_ico")):
211 favicon
= os
.path
.join(os
.getcwd(), os
.path
.dirname(__file__
),
213 root
.favicon_ico
= tools
.staticfile
.handler(favicon
)
218 self
.apps
[script_name
] = app
222 def graft(self
, wsgi_callable
, script_name
=""):
223 """Mount a wsgi callable at the given script_name."""
224 # Next line both 1) strips trailing slash and 2) maps "/" -> "".
225 script_name
= script_name
.rstrip("/")
226 self
.apps
[script_name
] = wsgi_callable
228 def script_name(self
, path
=None):
229 """The script_name of the app at the given path, or None.
231 If path is None, cherrypy.request is used.
235 request
= cherrypy
.serving
.request
236 path
= httputil
.urljoin(request
.script_name
,
238 except AttributeError:
242 if path
in self
.apps
:
248 # Move one node up the tree and try again.
249 path
= path
[:path
.rfind("/")]
251 def __call__(self
, environ
, start_response
):
252 # If you're calling this, then you're probably setting SCRIPT_NAME
253 # to '' (some WSGI servers always set SCRIPT_NAME to '').
254 # Try to look up the app using the full path.
256 if environ
.get(ntou('wsgi.version')) == (ntou('u'), 0):
257 env1x
= _cpwsgi
.downgrade_wsgi_ux_to_1x(environ
)
258 path
= httputil
.urljoin(env1x
.get('SCRIPT_NAME', ''),
259 env1x
.get('PATH_INFO', ''))
260 sn
= self
.script_name(path
or "/")
262 start_response('404 Not Found', [])
267 # Correct the SCRIPT_NAME and PATH_INFO environ entries.
268 environ
= environ
.copy()
269 if environ
.get(u
'wsgi.version') == (u
'u', 0):
270 # Python 2/WSGI u.0: all strings MUST be of type unicode
271 enc
= environ
[u
'wsgi.url_encoding']
272 environ
[u
'SCRIPT_NAME'] = sn
.decode(enc
)
273 environ
[u
'PATH_INFO'] = path
[len(sn
.rstrip("/")):].decode(enc
)
275 # Python 2/WSGI 1.x: all strings MUST be of type str
276 environ
['SCRIPT_NAME'] = sn
277 environ
['PATH_INFO'] = path
[len(sn
.rstrip("/")):]
278 return app(environ
, start_response
)