Ticket #559: wsgins.patch
-
__init__.py
old new 15 15 from cherrypy import _cptree 16 16 tree = _cptree.Tree() 17 17 from cherrypy._cptree import Application 18 from cherrypy import _cpwsgi as wsgi 18 19 from cherrypy import _cpengine 19 20 engine = _cpengine.Engine() 20 21 from cherrypy import _cpserver -
_cptree.py
old new 1 from cherrypy import _cpconfig, _cplogging, _cpwsgi 1 """CherryPy Application and Tree objects.""" 2 2 3 import sys 4 import cherrypy 5 from cherrypy import _cpconfig, _cplogging 6 from cherrypy._cperror import format_exc, bare_error 7 from cherrypy.lib import http 3 8 9 4 10 class Application(object): 5 11 """A CherryPy Application. 6 12 … … 67 73 host += ":%s" % port 68 74 return scheme + host + self.script_name 69 75 76 def wsgiapp(self, environ, start_response): 77 return wsgi_handler(environ, start_response, app=self) 78 70 79 def __call__(self, environ, start_response): 71 return _cpwsgi._wsgi_callable(environ, start_response, app=self)80 return self.wsgiapp(environ, start_response) 72 81 73 82 83 headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization', 84 'CONTENT_LENGTH': 'Content-Length', 85 'CONTENT_TYPE': 'Content-Type', 86 'REMOTE_HOST': 'Remote-Host', 87 'REMOTE_ADDR': 'Remote-Addr', 88 } 89 90 def translate_headers(environ): 91 """Translate CGI-environ header names to HTTP header names.""" 92 for cgiName in environ: 93 # We assume all incoming header keys are uppercase already. 94 if cgiName in headerNames: 95 yield headerNames[cgiName], environ[cgiName] 96 elif cgiName[:5] == "HTTP_": 97 # Hackish attempt at recovering original header names. 98 translatedHeader = cgiName[5:].replace("_", "-") 99 yield translatedHeader, environ[cgiName] 100 101 102 def wsgi_handler(environ, start_response, app): 103 request = None 104 try: 105 env = environ.get 106 local = http.Host('', int(env('SERVER_PORT', 80)), 107 env('SERVER_NAME', '')) 108 remote = http.Host(env('REMOTE_ADDR', ''), 109 int(env('REMOTE_PORT', -1)), 110 env('REMOTE_HOST', '')) 111 request = cherrypy.engine.request(local, remote, 112 env('wsgi.url_scheme'), 113 env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")) 114 115 # LOGON_USER is served by IIS, and is the name of the 116 # user after having been mapped to a local account. 117 # Both IIS and Apache set REMOTE_USER, when possible. 118 request.login = env('LOGON_USER') or env('REMOTE_USER') or None 119 120 request.multithread = environ['wsgi.multithread'] 121 request.multiprocess = environ['wsgi.multiprocess'] 122 request.wsgi_environ = environ 123 124 request.app = app 125 126 path = env('SCRIPT_NAME', '') + env('PATH_INFO', '') 127 response = request.run(environ['REQUEST_METHOD'], path, 128 env('QUERY_STRING'), 129 env('SERVER_PROTOCOL'), 130 translate_headers(environ), 131 environ['wsgi.input']) 132 s, h, b = response.status, response.header_list, response.body 133 exc = None 134 except (KeyboardInterrupt, SystemExit), ex: 135 try: 136 if request: 137 request.close() 138 except: 139 cherrypy.log(traceback=True) 140 request = None 141 raise ex 142 except: 143 if request and request.throw_errors: 144 raise 145 tb = format_exc() 146 cherrypy.log(tb) 147 if request and not request.show_tracebacks: 148 tb = "" 149 s, h, b = bare_error(tb) 150 exc = sys.exc_info() 151 152 try: 153 start_response(s, h, exc) 154 for chunk in b: 155 # WSGI requires all data to be of type "str". This coercion should 156 # not take any time at all if chunk is already of type "str". 157 # If it's unicode, it could be a big performance hit (x ~500). 158 if not isinstance(chunk, str): 159 chunk = chunk.encode("ISO-8859-1") 160 yield chunk 161 if request: 162 request.close() 163 request = None 164 except (KeyboardInterrupt, SystemExit), ex: 165 try: 166 if request: 167 request.close() 168 except: 169 cherrypy.log(traceback=True) 170 request = None 171 raise ex 172 except: 173 cherrypy.log(traceback=True) 174 try: 175 if request: 176 request.close() 177 except: 178 cherrypy.log(traceback=True) 179 request = None 180 s, h, b = bare_error() 181 # CherryPy test suite expects bare_error body to be output, 182 # so don't call start_response (which, according to PEP 333, 183 # may raise its own error at that point). 184 for chunk in b: 185 if not isinstance(chunk, str): 186 chunk = chunk.encode("ISO-8859-1") 187 yield chunk 188 189 74 190 class Tree(object): 75 191 """A registry of CherryPy applications, mounted at diverse points. 76 192 -
_cpwsgi.py
old new 1 1 """WSGI interface (see PEP 333).""" 2 2 3 import sys 4 import cherrypy 3 import cherrypy as _cherrypy 5 4 from cherrypy import _cpwsgiserver 6 from cherrypy._cperror import format_exc, bare_error 7 from cherrypy.lib import http 5 from cherrypy.lib import http as _http 8 6 9 7 10 headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization', 11 'CONTENT_LENGTH': 'Content-Length', 12 'CONTENT_TYPE': 'Content-Type', 13 'REMOTE_HOST': 'Remote-Host', 14 'REMOTE_ADDR': 'Remote-Addr', 15 } 16 17 def translate_headers(environ): 18 """Translate CGI-environ header names to HTTP header names.""" 19 for cgiName in environ: 20 # We assume all incoming header keys are uppercase already. 21 if cgiName in headerNames: 22 yield headerNames[cgiName], environ[cgiName] 23 elif cgiName[:5] == "HTTP_": 24 # Hackish attempt at recovering original header names. 25 translatedHeader = cgiName[5:].replace("_", "-") 26 yield translatedHeader, environ[cgiName] 27 28 29 def _wsgi_callable(environ, start_response, app): 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, 39 env('wsgi.url_scheme'), 40 env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")) 41 42 # LOGON_USER is served by IIS, and is the name of the 43 # user after having been mapped to a local account. 44 # Both IIS and Apache set REMOTE_USER, when possible. 45 request.login = env('LOGON_USER') or env('REMOTE_USER') or None 46 47 request.multithread = environ['wsgi.multithread'] 48 request.multiprocess = environ['wsgi.multiprocess'] 49 request.wsgi_environ = environ 50 51 request.app = app 52 53 path = env('SCRIPT_NAME', '') + env('PATH_INFO', '') 54 response = request.run(environ['REQUEST_METHOD'], path, 55 env('QUERY_STRING'), 56 env('SERVER_PROTOCOL'), 57 translate_headers(environ), 58 environ['wsgi.input']) 59 s, h, b = response.status, response.header_list, response.body 60 exc = None 61 except (KeyboardInterrupt, SystemExit), ex: 62 try: 63 if request: 64 request.close() 65 except: 66 cherrypy.log(traceback=True) 67 request = None 68 raise ex 69 except: 70 if request and request.throw_errors: 71 raise 72 tb = format_exc() 73 cherrypy.log(tb) 74 if request and not request.show_tracebacks: 75 tb = "" 76 s, h, b = bare_error(tb) 77 exc = sys.exc_info() 8 class Pipeline(_cherrypy.Application): 9 """A CherryPy Application plus configurable WSGI middleware. 78 10 79 try: 80 start_response(s, h, exc) 81 for chunk in b: 82 # WSGI requires all data to be of type "str". This coercion should 83 # not take any time at all if chunk is already of type "str". 84 # If it's unicode, it could be a big performance hit (x ~500). 85 if not isinstance(chunk, str): 86 chunk = chunk.encode("ISO-8859-1") 87 yield chunk 88 if request: 89 request.close() 90 request = None 91 except (KeyboardInterrupt, SystemExit), ex: 92 try: 93 if request: 94 request.close() 95 except: 96 cherrypy.log(traceback=True) 97 request = None 98 raise ex 99 except: 100 cherrypy.log(traceback=True) 101 try: 102 if request: 103 request.close() 104 except: 105 cherrypy.log(traceback=True) 106 request = None 107 s, h, b = bare_error() 108 # CherryPy test suite expects bare_error body to be output, 109 # so don't call start_response (which, according to PEP 333, 110 # may raise its own error at that point). 111 for chunk in b: 112 if not isinstance(chunk, str): 113 chunk = chunk.encode("ISO-8859-1") 114 yield chunk 11 An instance of this class may also be used as a WSGI callable 12 (WSGI application object) for itself. 13 14 root: the top-most container of page handlers for this app. 15 script_name: the URL "mount point" for this app; for example, 16 if script_name is "/my/cool/app", then the URL 17 "http://my.domain.tld/my/cool/app/page1" might be handled 18 by a "page1" method on the root object. If script_name is 19 explicitly set to None, then CherryPy will attempt to provide 20 it each time from request.wsgi_environ['SCRIPT_NAME']. 21 conf: a dict of {path: pathconf} pairs, where 'pathconf' is itself 22 a dict of {key: value} pairs. 23 24 pipeline: a list of (name, wsgiapp) pairs. The 'wsgiapp' MUST be a 25 constructor that takes an initial, positional wsgiapp argument, 26 plus optional keyword arguments, and return a WSGI application 27 that takes environ and start_response arguments. The 'name' can be 28 any you choose, and will correspond to keys in self.config. 29 pipeconfig: a dict whose keys match names listed in the pipeline. Each 30 value is a further dict which will be passed to the corresponding 31 named WSGI callable (from the pipeline) as keyword arguments. 32 """ 33 34 def __init__(self, root, script_name=""): 35 _cherrypy.Application.__init__(self, root, script_name="") 36 self.pipeline = [] 37 self._head = None 38 self.pipeconfig = {} 39 self.namespaces["wsgi"] = self.wsgi_namespace_handler 40 41 def wsgi_namespace_handler(self, k, v): 42 """Config handler for the 'wsgi' namespace.""" 43 if k == "pipeline": 44 self.pipeline = v 45 else: 46 name, arg = k.split(".", 1) 47 bucket = self.pipeconfig.setdefault(name, {}) 48 bucket[arg] = v 49 50 def __call__(self, environ, start_response): 51 if self._head is None: 52 self._head = self.wsgiapp 53 pipe = self.pipeline[:] 54 pipe.reverse() 55 for name, callable in pipe: 56 conf = self.pipeconfig.get(name, {}) 57 self._head = callable(self._head, **conf) 58 return self._head(environ, start_response) 115 59 116 60 117 61 # Server components # … … 120 64 class CPHTTPRequest(_cpwsgiserver.HTTPRequest): 121 65 122 66 def parse_request(self): 123 mhs = cherrypy.server.max_request_header_size67 mhs = _cherrypy.server.max_request_header_size 124 68 if mhs > 0: 125 self.rfile = http.SizeCheckWrapper(self.rfile, mhs)69 self.rfile = _http.SizeCheckWrapper(self.rfile, mhs) 126 70 127 71 try: 128 72 _cpwsgiserver.HTTPRequest.parse_request(self) 129 except http.MaxSizeExceeded:73 except _http.MaxSizeExceeded: 130 74 self.simple_response("413 Request Entity Too Large") 131 cherrypy.log(traceback=True)75 _cherrypy.log(traceback=True) 132 76 133 77 def decode_chunked(self): 134 78 """Decode the 'chunked' transfer coding.""" 135 if isinstance(self.rfile, http.SizeCheckWrapper):79 if isinstance(self.rfile, _http.SizeCheckWrapper): 136 80 self.rfile = self.rfile.rfile 137 mbs = cherrypy.server.max_request_body_size81 mbs = _cherrypy.server.max_request_body_size 138 82 if mbs > 0: 139 self.rfile = http.SizeCheckWrapper(self.rfile, mbs)83 self.rfile = _http.SizeCheckWrapper(self.rfile, mbs) 140 84 try: 141 85 return _cpwsgiserver.HTTPRequest.decode_chunked(self) 142 except http.MaxSizeExceeded:86 except _http.MaxSizeExceeded: 143 87 self.simple_response("413 Request Entity Too Large") 144 cherrypy.log(traceback=True)88 _cherrypy.log(traceback=True) 145 89 return False 146 90 147 91 … … 163 107 ConnectionClass = CPHTTPConnection 164 108 165 109 def __init__(self): 166 server = cherrypy.server110 server = _cherrypy.server 167 111 sockFile = server.socket_file 168 112 if sockFile: 169 113 bind_addr = sockFile … … 173 117 s = _cpwsgiserver.CherryPyWSGIServer 174 118 # We could just pass cherrypy.tree, but by passing tree.apps, 175 119 # we get correct SCRIPT_NAMEs as early as possible. 176 s.__init__(self, bind_addr, cherrypy.tree.apps.items(),120 s.__init__(self, bind_addr, _cherrypy.tree.apps.items(), 177 121 server.thread_pool, 178 122 server.socket_host, 179 123 request_queue_size = server.socket_queue_size, -
test/test_wsgi_ns.py
old new 1 from cherrypy.test import test 2 test.prefer_parent_path() 3 4 5 def setup_server(): 6 7 import cherrypy 8 from cherrypy import _cpwsgi 9 10 class ChangeCase(object): 11 12 def __init__(self, app, to=None): 13 self.app = app 14 self.to = to 15 16 def __call__(self, environ, start_response): 17 res = ''.join(self.app(environ, start_response)) 18 return [getattr(res, self.to)()] 19 20 21 class Root(object): 22 23 def index(self): 24 return "HeLlO WoRlD!" 25 index.exposed = True 26 27 28 root_conf = {'wsgi.pipeline': [('changecase', ChangeCase)], 29 'wsgi.changecase.to': 'upper', 30 } 31 32 cherrypy.config.update({'environment': 'test_suite'}) 33 34 app = cherrypy.wsgi.Pipeline(Root()) 35 cherrypy.tree.mount(app, conf={'/': root_conf}) 36 37 38 from cherrypy.test import helper 39 40 41 class WSGI_Namespace_Test(helper.CPWebCase): 42 43 def test_01_standard_app(self): 44 self.getPage("/") 45 self.assertBody("HELLO WORLD!") 46 47 if __name__ == '__main__': 48 setup_server() 49 helper.testmain() 50

