Changeset 1244
- Timestamp:
- 08/15/06 08:34:27
- Files:
-
- branches/cp3-wsgi-remix (copied) (copied from trunk/cherrypy)
- branches/cp3-wsgi-remix/__init__.py (modified) (1 diff)
- branches/cp3-wsgi-remix/_cprequest.py (modified) (1 diff)
- branches/cp3-wsgi-remix/_cptree.py (modified) (2 diffs)
- branches/cp3-wsgi-remix/_cpwsgi.py (modified) (3 diffs)
- branches/cp3-wsgi-remix/_cpwsgiserver.py (modified) (2 diffs)
- branches/cp3-wsgi-remix/test/helper.py (modified) (1 diff)
- branches/cp3-wsgi-remix/test/test.py (modified) (1 diff)
- branches/cp3-wsgi-remix/test/test_wsgiapps.py (moved) (moved from trunk/cherrypy/test/test_wsgiapp.py) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
branches/cp3-wsgi-remix/__init__.py
r1243 r1244 8 8 from _cperror import WrongConfigValue, TimeoutError 9 9 import config 10 11 from _cpwsgi import Application 10 12 11 13 import _cptools branches/cp3-wsgi-remix/_cprequest.py
r1243 r1244 169 169 self.app = cherrypy.tree.apps[r] 170 170 else: 171 self.script_name = self. app.script_name171 self.script_name = self.wsgi_environ.get('SCRIPT_NAME', '') 172 172 173 173 # path_info should be the path from the 174 174 # app root (script_name) to the handler. 175 self.path_info = self. path[len(self.script_name.rstrip("/")):]175 self.path_info = self.wsgi_environ.get('PATH_INFO', '') 176 176 177 177 # Loop to allow for InternalRedirect. branches/cp3-wsgi-remix/_cptree.py
r1156 r1244 1 import logging 2 import sys 3 4 from cherrypy import config 5 6 7 class Application: 8 """A CherryPy Application.""" 9 10 def __init__(self, root, script_name="", conf=None): 11 self.access_log = log = logging.getLogger("cherrypy.access.%s" % id(self)) 12 log.setLevel(logging.INFO) 13 14 self.error_log = log = logging.getLogger("cherrypy.error.%s" % id(self)) 15 log.setLevel(logging.DEBUG) 16 17 self.root = root 18 self.script_name = script_name 19 self.conf = {} 20 if conf: 21 self.merge(conf) 22 23 def merge(self, conf): 24 """Merge the given config into self.config.""" 25 config.merge(self.conf, conf) 26 27 # Create log handlers as specified in config. 28 rootconf = self.conf.get("/", {}) 29 config._configure_builtin_logging(rootconf, self.access_log, "log_access_file") 30 config._configure_builtin_logging(rootconf, self.error_log) 31 32 def guess_abs_path(self): 33 """Guess the absolute URL from server.socket_host and script_name. 34 35 When inside a request, the abs_path can be formed via: 36 cherrypy.request.base + (cherrypy.request.app.script_name or "/") 37 38 However, outside of the request we must guess, hoping the deployer 39 set socket_host and socket_port correctly. 40 """ 41 port = int(config.get('server.socket_port', 80)) 42 if port in (443, 8443): 43 scheme = "https://" 44 else: 45 scheme = "http://" 46 host = config.get('server.socket_host', '') 47 if port != 80: 48 host += ":%s" % port 49 return scheme + host + self.script_name 1 from cherrypy._cperror import format_exc, bare_error 2 from cherrypy._cpwsgi import Application, HostedWSGI 3 from cherrypy import NotFound 4 from cherrypy.lib import http 50 5 51 6 52 7 class Tree: 53 """A registry of CherryPyapplications, mounted at diverse points."""8 """A dispatcher of WSGI applications, mounted at diverse points.""" 54 9 55 10 def __init__(self): 56 11 self.apps = {} 57 12 58 def mount(self, root, script_name="", conf=None): 59 """Mount a new app from a root object, script_name, and conf.""" 13 def mount(self, app, script_name="", conf=None, wrap=True): 14 """Mount a new app at script_name using configuration in conf. 15 16 An application can be one of: 17 1) A standard cherrypy.Application - left as is. 18 2) A "root" object - wrapped in an Application instance. 19 3) A WSGI callable - optionally wrapped in a HostedWSGI instance. 20 21 If wrap == True, a WSGI callable will be wrapped in a cherrypy.Application 22 instance, allowing the use of tools with the WSGI application. 23 """ 24 60 25 # Next line both 1) strips trailing slash and 2) maps "/" -> "". 61 26 script_name = script_name.rstrip("/") 62 app = Application(root, script_name, conf) 27 28 # Leave Application objects alone 29 if isinstance(app, Application): 30 pass 31 # Handle "root" objects... 32 elif not callable(app): 33 app = Application(app, script_name, conf) 34 # Handle WSGI callables 35 elif callable(app) and wrap: 36 app = HostedWSGI(app) 37 # In all other cases leave the app intact (no wrapping) 38 63 39 self.apps[script_name] = app 64 40 65 41 # If mounted at "", add favicon.ico 66 if script_name == "" and root and not hasattr(root, "favicon_ico"):42 if script_name == "" and app and not hasattr(app, "favicon_ico"): 67 43 import os 68 44 from cherrypy import tools 69 45 favicon = os.path.join(os.getcwd(), os.path.dirname(__file__), 70 46 "favicon.ico") 71 root.favicon_ico = tools.staticfile.handler(favicon)47 app.favicon_ico = tools.staticfile.handler(favicon) 72 48 73 49 return app … … 111 87 return http.urljoin(script_name, path) 112 88 89 def dispatch(self, environ, start_response): 90 """Dispatch to mounted WSGI applications.""" 91 script_name = environ.get("SCRIPT_NAME", '').rstrip('/') 92 path_info = environ.get("PATH_INFO", '') 93 94 mount_points = self.apps.keys() 95 mount_points.sort() 96 mount_points.reverse() 97 98 for mp in mount_points: 99 if path_info.startswith(mp): 100 environ['SCRIPT_NAME'] = script_name + mp 101 environ['PATH_INFO'] = path_info[len(mp):] 102 app = self.apps[mp] 103 return app(environ, start_response) 104 raise NotFound branches/cp3-wsgi-remix/_cpwsgi.py
r1230 r1244 1 1 """A WSGI application interface (see PEP 333).""" 2 2 import logging 3 3 import sys 4 4 5 import cherrypy 5 from cherrypy import _cpwsgiserver 6 from cherrypy import _cpwsgiserver, config 6 7 from cherrypy._cperror import format_exc, bare_error 7 8 from cherrypy.lib import http … … 26 27 yield translatedHeader, environ[cgiName] 27 28 28 29 def _wsgi_callable(environ, start_response, app=None): 30 request = None 31 try: 32 env = environ.get 33 local = http.Host('', int(env('SERVER_PORT', 80)), 34 env('SERVER_NAME', '')) 35 remote = http.Host(env('REMOTE_ADDR', ''), 36 int(env('REMOTE_PORT', -1)), 37 env('REMOTE_HOST', '')) 38 request = cherrypy.engine.request(local, remote, env('wsgi.url_scheme')) 39 40 # LOGON_USER is served by IIS, and is the name of the 41 # user after having been mapped to a local account. 42 # Both IIS and Apache set REMOTE_USER, when possible. 43 request.login = env('LOGON_USER') or env('REMOTE_USER') or None 44 45 request.multithread = environ['wsgi.multithread'] 46 request.multiprocess = environ['wsgi.multiprocess'] 47 request.wsgi_environ = environ 48 49 if app: 50 request.app = app 51 52 path = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '') 53 response = request.run(environ['REQUEST_METHOD'], path, 54 environ.get('QUERY_STRING'), 55 environ.get('SERVER_PROTOCOL'), 56 translate_headers(environ), 57 environ['wsgi.input']) 58 s, h, b = response.status, response.header_list, response.body 59 exc = None 60 except (KeyboardInterrupt, SystemExit), ex: 29 def _init_request(environ): 30 """Initialize and return the cherrypy.request object.""" 31 env = environ.get 32 local = http.Host('', int(env('SERVER_PORT', 80)), 33 env('SERVER_NAME', '')) 34 remote = http.Host(env('REMOTE_ADDR', ''), 35 int(env('REMOTE_PORT', -1)), 36 env('REMOTE_HOST', '')) 37 request = cherrypy.engine.request(local, remote, env('wsgi.url_scheme')) 38 return request 39 40 class Application: 41 """A CherryPy WSGI Application.""" 42 43 def __init__(self, root, script_name="", conf=None): 44 self.access_log = log = logging.getLogger("cherrypy.access.%s" % id(self)) 45 log.setLevel(logging.INFO) 46 47 self.error_log = log = logging.getLogger("cherrypy.error.%s" % id(self)) 48 log.setLevel(logging.DEBUG) 49 50 self.root = root 51 self.script_name = script_name 52 self.conf = {} 53 if conf: 54 self.merge(conf) 55 56 def __call__(self, environ, start_response): 57 if not getattr(cherrypy.request, 'initialized', False): 58 request = _init_request(environ) 59 else: 60 request = cherrypy.request 61 61 try: 62 63 env = environ.get 64 # LOGON_USER is served by IIS, and is the name of the 65 # user after having been mapped to a local account. 66 # Both IIS and Apache set REMOTE_USER, when possible. 67 request.login = env('LOGON_USER') or env('REMOTE_USER') or None 68 69 request.multithread = environ['wsgi.multithread'] 70 request.multiprocess = environ['wsgi.multiprocess'] 71 request.wsgi_environ = environ 72 request.app = self 73 74 path = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '') 75 response = request.run(environ['REQUEST_METHOD'], path, 76 environ.get('QUERY_STRING'), 77 environ.get('SERVER_PROTOCOL'), 78 translate_headers(environ), 79 environ['wsgi.input']) 80 s, h, b = response.status, response.header_list, response.body 81 exc = None 82 except (KeyboardInterrupt, SystemExit), ex: 83 try: 84 if request: 85 request.close() 86 except: 87 cherrypy.log(traceback=True) 88 request = None 89 raise ex 90 except: 91 if cherrypy.config.get("throw_errors", False): 92 raise 93 tb = format_exc() 94 cherrypy.log(tb) 95 if not cherrypy.config.get("show_tracebacks", False): 96 tb = "" 97 s, h, b = bare_error(tb) 98 exc = sys.exc_info() 99 100 try: 101 start_response(s, h, exc) 102 for chunk in b: 103 # WSGI requires all data to be of type "str". This coercion should 104 # not take any time at all if chunk is already of type "str". 105 # If it's unicode, it could be a big performance hit (x ~500). 106 if not isinstance(chunk, str): 107 chunk = chunk.encode("ISO-8859-1") 108 yield chunk 62 109 if request: 63 110 request.close() 111 request = None 112 except (KeyboardInterrupt, SystemExit), ex: 113 try: 114 if request: 115 request.close() 116 except: 117 cherrypy.log(traceback=True) 118 request = None 119 raise ex 64 120 except: 65 121 cherrypy.log(traceback=True) 66 request = None 67 raise ex 68 except: 69 if cherrypy.config.get("throw_errors", False): 70 raise 71 tb = format_exc() 72 cherrypy.log(tb) 73 if not cherrypy.config.get("show_tracebacks", False): 74 tb = "" 75 s, h, b = bare_error(tb) 76 exc = sys.exc_info() 77 78 try: 79 start_response(s, h, exc) 80 for chunk in b: 81 # WSGI requires all data to be of type "str". This coercion should 82 # not take any time at all if chunk is already of type "str". 83 # If it's unicode, it could be a big performance hit (x ~500). 84 if not isinstance(chunk, str): 85 chunk = chunk.encode("ISO-8859-1") 86 yield chunk 87 if request: 88 request.close() 89 request = None 90 except (KeyboardInterrupt, SystemExit), ex: 91 try: 92 if request: 93 request.close() 94 except: 95 cherrypy.log(traceback=True) 96 request = None 97 raise ex 98 except: 99 cherrypy.log(traceback=True) 100 try: 101 if request: 102 request.close() 103 except: 104 cherrypy.log(traceback=True) 105 request = None 106 s, h, b = bare_error() 107 # CherryPy test suite expects bare_error body to be output, 108 # so don't call start_response (which, according to PEP 333, 109 # may raise its own error at that point). 110 for chunk in b: 111 if not isinstance(chunk, str): 112 chunk = chunk.encode("ISO-8859-1") 113 yield chunk 114 115 def wsgiApp(environ, start_response): 116 """The WSGI 'application object' for CherryPy. 117 118 Use this as the same WSGI callable for all your CP apps. 119 """ 120 return _wsgi_callable(environ, start_response) 121 122 def make_app(app): 123 """Factory for making separate WSGI 'application objects' for each CP app. 124 125 Example: 126 # 'app' will be a CherryPy application object 127 app = cherrypy.tree.mount(Root(), "/", localconf) 128 129 # 'wsgi_app' will be a WSGI application 130 wsgi_app = _cpwsgi.make_app(app) 131 """ 132 def single_app(environ, start_response): 133 return _wsgi_callable(environ, start_response, app) 134 return single_app 135 136 122 try: 123 if request: 124 request.close() 125 except: 126 cherrypy.log(traceback=True) 127 request = None 128 s, h, b = bare_error() 129 # CherryPy test suite expects bare_error body to be output, 130 # so don't call start_response (which, according to PEP 333, 131 # may raise its own error at that point). 132 for chunk in b: 133 if not isinstance(chunk, str): 134 chunk = chunk.encode("ISO-8859-1") 135 yield chunk 136 137 def merge(self, conf): 138 """Merge the given config into self.config.""" 139 config.merge(self.conf, conf) 140 141 # Create log handlers as specified in config. 142 rootconf = self.conf.get("/", {}) 143 config._configure_builtin_logging(rootconf, self.access_log, "log_access_file") 144 config._configure_builtin_logging(rootconf, self.error_log) 145 146 def guess_abs_path(self): 147 """Guess the absolute URL from server.socket_host and script_name. 148 149 When inside a request, the abs_path can be formed via: 150 cherrypy.request.base + (cherrypy.request.app.script_name or "/") 151 152 However, outside of the request we must guess, hoping the deployer 153 set socket_host and socket_port correctly. 154 """ 155 port = int(config.get('server.socket_port', 80)) 156 if port in (443, 8443): 157 scheme = "https://" 158 else: 159 scheme = "http://" 160 host = config.get('server.socket_host', '') 161 if port != 80: 162 host += ":%s" % port 163 return scheme + host + self.script_name 164 165 class HostedWSGI(object): 166 def __init__(self, app): 167 self.app = app 168 self._cp_config = {'tools.wsgiapp.on': True, 169 'tools.wsgiapp.app': app, 170 } 171 172 def __call__(self, environ, start_response): 173 return self.app(environ, start_response) 137 174 138 175 # Server components # … … 197 234 conf('server.socket_port')) 198 235 199 app s = [(base, wsgiApp) for base in cherrypy.tree.apps]236 app = cherrypy.tree.dispatch 200 237 201 238 s = _cpwsgiserver.CherryPyWSGIServer 202 s.__init__(self, bind_addr, app s,239 s.__init__(self, bind_addr, app, 203 240 conf('server.thread_pool'), 204 241 conf('server.socket_host'), branches/cp3-wsgi-remix/_cpwsgiserver.py
r1237 r1244 88 88 path = "%2F".join(atoms) 89 89 90 for mount_point, wsgi_app in self.server.mount_points: 91 if path == "*": 92 # This means, of course, that the first wsgi_app will 93 # always handle a URI of "*". 94 self.environ["SCRIPT_NAME"] = "" 95 self.environ["PATH_INFO"] = "*" 96 self.wsgi_app = wsgi_app 97 break 98 # The mount_points list should be sorted by length, descending. 99 if path.startswith(mount_point): 100 self.environ["SCRIPT_NAME"] = mount_point 101 self.environ["PATH_INFO"] = path[len(mount_point):] 102 self.wsgi_app = wsgi_app 103 break 104 else: 105 self.abort("404 Not Found") 106 return 90 self.wsgi_app = self.server.app 91 self.environ['SCRIPT_NAME'] = '' 92 self.environ['PATH_INFO'] = path 107 93 108 94 # Note that, like wsgiref and most other WSGI servers, … … 288 274 self.requests = Queue.Queue(max) 289 275 290 if callable(wsgi_app): 291 # We've been handed a single wsgi_app, in CP-2.1 style. 292 # Assume it's mounted at "". 293 self.mount_points = [("", wsgi_app)] 294 else: 295 # We've been handed a list of (mount_point, wsgi_app) tuples, 296 # so that the server can call different wsgi_apps, and also 297 # correctly set SCRIPT_NAME. 298 self.mount_points = wsgi_app 299 self.mount_points.sort() 300 self.mount_points.reverse() 276 self.app = wsgi_app 301 277 302 278 self.bind_addr = bind_addr branches/cp3-wsgi-remix/test/helper.py
r1219 r1244 116 116 setup() 117 117 118 # The setup functions probably mounted new apps.119 # Tell our server about them.120 apps = []121 for base, app in cherrypy.tree.apps.iteritems():122 if base == "/":123 base = ""124 if conf.get("profiling.on", False):125 apps.append((base, profiler.make_app(_cpwsgi.wsgiApp)))126 ## apps.append((base, profiler.make_app(_cpwsgi.wsgiApp, aggregate=True)))127 else:128 apps.append((base, _cpwsgi.wsgiApp))129 ## # We could use the following line, but it breaks test_tutorials130 ## apps.append((base, _cpwsgi.make_app(app)))131 apps.sort()132 apps.reverse()133 118 for s in cherrypy.server.httpservers: 134 s. mount_points = apps119 s.app = cherrypy.tree.dispatch 135 120 136 121 suite = CPTestLoader.loadTestsFromName(testmod) branches/cp3-wsgi-remix/test/test.py
r1171 r1244 322 322 ## 'test_states', 323 323 'test_xmlrpc', 324 'test_wsgiapp ',324 'test_wsgiapps', 325 325 ] 326 326 CommandLineParser(testList).run() branches/cp3-wsgi-remix/test/test_wsgiapps.py
r1243 r1244 19 19 for k in keys: 20 20 yield '%s: %s\n' % (k,environ[k]) 21 22 def reversing_middleware(app): 23 def _app(environ, start_response): 24 results = app(environ, start_response) 25 if not isinstance(results, basestring): 26 results = "".join(results) 27 results = list(results) 28 results.reverse() 29 return "".join(results) 30 return _app 21 31 22 32 class Root: … … 42 52 }} 43 53 cherrypy.tree.mount(HostedWSGI(), '/hosted/app0', conf0) 44 54 cherrypy.tree.mount(test_app, '/hosted/app1', wrap=False) 55 56 app = reversing_middleware(cherrypy.Application(Root())) 57 cherrypy.tree.mount(app, '/hosted/app2', wrap=False) 45 58 46 59 import helper … … 56 69 self.assertBody("I'm a regular CherryPy page handler!") 57 70 58 def test_02_ tools(self):71 def test_02_wrapped_wsgi(self): 59 72 self.getPage("/hosted/app0") 60 73 self.assertHeader("Content-Type", "text/plain") 61 74 self.assertInBody(self.wsgi_output) 62 75 63 def test_0 4_static_subdir(self):76 def test_03_static_subdir(self): 64 77 self.getPage("/hosted/app0/static/index.html") 65 78 self.assertStatus('200 OK') 66 79 self.assertHeader('Content-Type', 'text/html') 67 80 self.assertBody('Hello, world\r\n') 81 82 def test_04_pure_wsgi(self): 83 self.getPage("/hosted/app1") 84 self.assertHeader("Content-Type", "text/plain") 85 self.assertInBody(self.wsgi_output) 86 87 def test_05_wrapped_cp_app(self): 88 self.getPage("/hosted/app2/") 89 body = list("I'm a regular CherryPy page handler!") 90 body.reverse() 91 body = "".join(body) 92 self.assertInBody(body) 68 93 69 94 if __name__ == '__main__':

