Bundled cherrypy.
[smonitor.git] / monitor / cherrypy / _cptree.py
blob67ce5465979d35bc312de93f7eca4cff7ee02ae6
1 """CherryPy Application and Tree objects."""
3 import os
4 import cherrypy
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.
18 """
20 root = None
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."""
27 config = {}
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}
34 log = None
35 """A LogManager instance. See _cplogging."""
37 wsgiapp = None
38 """A CPWSGIApp instance. See _cpwsgi."""
40 request_class = _cprequest.Request
41 response_class = _cprequest.Response
43 relative_urls = False
45 def __init__(self, root, script_name="", config=None):
46 self.log = _cplogging.LogManager(id(self), cherrypy.log.logger_root)
47 self.root = 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()
56 if config:
57 self.merge(config)
59 def __repr__(self):
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")
66 portions of the URI.
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'].
77 """
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):
84 if value:
85 value = value.rstrip("/")
86 self._script_name = value
87 script_name = property(fget=_get_script_name, fset=_set_script_name,
88 doc=script_name_doc)
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."""
99 trail = path or "/"
100 while trail:
101 nodeconf = self.config.get(trail, {})
103 if key in nodeconf:
104 return nodeconf[key]
106 lastslash = trail.rfind("/")
107 if lastslash == -1:
108 break
109 elif lastslash == 0 and trail != "/":
110 trail = "/"
111 else:
112 trail = trail[:lastslash]
114 return default
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)
119 req.app = self
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')
129 return req, resp
131 def release_serving(self):
132 """Release the current serving (request and response)."""
133 req = cherrypy.serving.request
135 cherrypy.engine.timeout_monitor.release()
137 try:
138 req.close()
139 except:
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)
148 class Tree(object):
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
153 mounted apps.
156 apps = {}
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)."""
163 def __init__(self):
164 self.apps = {}
166 def mount(self, root, script_name="", config=None):
167 """Mount a new app from a root object, script_name, and config.
169 root
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.
175 script_name
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 "/").
185 config
186 A file or dict containing application config.
188 if script_name is None:
189 raise TypeError(
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):
200 app = root
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
205 else:
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__),
212 "favicon.ico")
213 root.favicon_ico = tools.staticfile.handler(favicon)
215 if config:
216 app.merge(config)
218 self.apps[script_name] = app
220 return 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.
233 if path is None:
234 try:
235 request = cherrypy.serving.request
236 path = httputil.urljoin(request.script_name,
237 request.path_info)
238 except AttributeError:
239 return None
241 while True:
242 if path in self.apps:
243 return path
245 if path == "":
246 return None
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.
255 env1x = environ
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 "/")
261 if sn is None:
262 start_response('404 Not Found', [])
263 return []
265 app = self.apps[sn]
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)
274 else:
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)