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

