Changeset 1430
- Timestamp:
- 11/16/06 13:10:21
- Files:
-
- trunk/cherrypy/_cptree.py (modified) (3 diffs)
- trunk/cherrypy/_cpwsgi.py (modified) (2 diffs)
- trunk/cherrypy/test/test_wsgi_ns.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cherrypy/_cptree.py
r1420 r1430 2 2 3 3 import os 4 import sys5 4 import cherrypy 6 from cherrypy import _cpconfig, _cplogging, tools 7 from cherrypy._cperror import format_exc, bare_error 8 from cherrypy.lib import http 5 from cherrypy import _cpconfig, _cplogging, _cpwsgi, tools 9 6 10 7 … … 30 27 self.root = root 31 28 self.script_name = script_name 29 self.wsgiapp = _cpwsgi.CPWSGIApp(self) 32 30 self.namespaces = {"log": lambda k, v: setattr(self.log, k, v), 31 "wsgi": self.wsgiapp.namespace_handler, 33 32 } 34 33 self.config = {} … … 50 49 _cpconfig._call_namespaces(self.config.get("/", {}), self.namespaces) 51 50 52 def wsgiapp(self, environ, start_response):53 # This is here instead of __call__ because it's really hard54 # to overwrite special methods like __call__ per instance.55 return wsgi_handler(environ, start_response, app=self)56 57 51 def __call__(self, environ, start_response): 58 52 return self.wsgiapp(environ, start_response) 59 60 61 headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization',62 'CONTENT_LENGTH': 'Content-Length',63 'CONTENT_TYPE': 'Content-Type',64 'REMOTE_HOST': 'Remote-Host',65 'REMOTE_ADDR': 'Remote-Addr',66 }67 68 def translate_headers(environ):69 """Translate CGI-environ header names to HTTP header names."""70 for cgiName in environ:71 # We assume all incoming header keys are uppercase already.72 if cgiName in headerNames:73 yield headerNames[cgiName], environ[cgiName]74 elif cgiName[:5] == "HTTP_":75 # Hackish attempt at recovering original header names.76 translatedHeader = cgiName[5:].replace("_", "-")77 yield translatedHeader, environ[cgiName]78 79 80 def wsgi_handler(environ, start_response, app):81 request = None82 try:83 env = environ.get84 local = http.Host('', int(env('SERVER_PORT', 80)),85 env('SERVER_NAME', ''))86 remote = http.Host(env('REMOTE_ADDR', ''),87 int(env('REMOTE_PORT', -1)),88 env('REMOTE_HOST', ''))89 request = cherrypy.engine.request(local, remote,90 env('wsgi.url_scheme'),91 env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1"))92 93 # LOGON_USER is served by IIS, and is the name of the94 # user after having been mapped to a local account.95 # Both IIS and Apache set REMOTE_USER, when possible.96 request.login = env('LOGON_USER') or env('REMOTE_USER') or None97 98 request.multithread = environ['wsgi.multithread']99 request.multiprocess = environ['wsgi.multiprocess']100 request.wsgi_environ = environ101 102 request.app = app103 104 path = env('SCRIPT_NAME', '') + env('PATH_INFO', '')105 response = request.run(environ['REQUEST_METHOD'], path,106 env('QUERY_STRING', ''),107 env('SERVER_PROTOCOL'),108 translate_headers(environ),109 environ['wsgi.input'])110 s, h, b = response.status, response.header_list, response.body111 exc = None112 except (KeyboardInterrupt, SystemExit), ex:113 try:114 if request:115 request.close()116 except:117 cherrypy.log(traceback=True)118 request = None119 raise ex120 except:121 if request and request.throw_errors:122 raise123 tb = format_exc()124 cherrypy.log(tb)125 if request and not request.show_tracebacks:126 tb = ""127 s, h, b = bare_error(tb)128 exc = sys.exc_info()129 130 try:131 start_response(s, h, exc)132 for chunk in b:133 # WSGI requires all data to be of type "str". This coercion should134 # not take any time at all if chunk is already of type "str".135 # If it's unicode, it could be a big performance hit (x ~500).136 if not isinstance(chunk, str):137 chunk = chunk.encode("ISO-8859-1")138 yield chunk139 if request:140 request.close()141 request = None142 except (KeyboardInterrupt, SystemExit), ex:143 try:144 if request:145 request.close()146 except:147 cherrypy.log(traceback=True)148 request = None149 raise ex150 except:151 cherrypy.log(traceback=True)152 try:153 if request:154 request.close()155 except:156 cherrypy.log(traceback=True)157 request = None158 s, h, b = bare_error()159 # CherryPy test suite expects bare_error body to be output,160 # so don't call start_response (which, according to PEP 333,161 # may raise its own error at that point).162 for chunk in b:163 if not isinstance(chunk, str):164 chunk = chunk.encode("ISO-8859-1")165 yield chunk166 53 167 54 trunk/cherrypy/_cpwsgi.py
r1394 r1430 1 1 """WSGI interface (see PEP 333).""" 2 2 3 import sys as _sys 4 3 5 import cherrypy as _cherrypy 4 from cherrypy import _cp wsgiserver6 from cherrypy import _cperror, _cpwsgiserver 5 7 from cherrypy.lib import http as _http 6 8 7 9 8 class pipeline(list):9 """A n ordered list of configurable WSGI middleware.10 11 self: a list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a12 constructor that takes an initial, positional wsgiappargument,10 class CPWSGIApp(object): 11 """A WSGI application object for a CherryPy Application. 12 13 pipeline: a list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a 14 constructor that takes an initial, positional 'nextapp' argument, 13 15 plus optional keyword arguments, and returns a WSGI application 14 16 (that takes environ and start_response arguments). The 'name' can 15 17 be any you choose, and will correspond to keys in self.config. 18 19 head: rather than nest all apps in the pipeline on each call, it's only 20 done the first time, and the result is memoized into self.head. Set 21 this to None again if you change self.pipeline after calling self. 22 16 23 config: a dict whose keys match names listed in the pipeline. Each 17 24 value is a further dict which will be passed to the corresponding … … 19 26 """ 20 27 21 def __new__(cls, app, members=None, key="wsgi"): 22 return list.__new__(cls) 23 24 def __init__(self, app, members=None, key="wsgi"): 25 self.app = app 26 if members: 27 self.extend(members) 28 self.head = None 29 self.tail = None 30 self.config = {} 31 self.key = key 32 app.namespaces[key] = self.namespace_handler 33 app.wsgi_pipeline = self 28 pipeline = [] 29 head = None 30 config = {} 31 32 throws = (KeyboardInterrupt, SystemExit) 33 34 headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization', 35 'CONTENT_LENGTH': 'Content-Length', 36 'CONTENT_TYPE': 'Content-Type', 37 'REMOTE_HOST': 'Remote-Host', 38 'REMOTE_ADDR': 'Remote-Addr', 39 } 40 41 def translate_headers(self, environ): 42 """Translate CGI-environ header names to HTTP header names.""" 43 for cgiName in environ: 44 # We assume all incoming header keys are uppercase already. 45 if cgiName in self.headerNames: 46 yield self.headerNames[cgiName], environ[cgiName] 47 elif cgiName[:5] == "HTTP_": 48 # Hackish attempt at recovering original header names. 49 translatedHeader = cgiName[5:].replace("_", "-") 50 yield translatedHeader, environ[cgiName] 51 52 def __init__(self, cpapp, pipeline=None): 53 self.cpapp = cpapp 54 self.pipeline = self.pipeline[:] 55 if pipeline: 56 self.pipeline.extend(pipeline) 57 self.config = self.config.copy() 58 59 def get_request(self, environ): 60 env = environ.get 61 62 local = _http.Host('', int(env('SERVER_PORT', 80)), 63 env('SERVER_NAME', '')) 64 remote = _http.Host(env('REMOTE_ADDR', ''), 65 int(env('REMOTE_PORT', -1)), 66 env('REMOTE_HOST', '')) 67 scheme = env('wsgi.url_scheme') 68 sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1") 69 request = _cherrypy.engine.request(local, remote, scheme, sproto) 70 71 # LOGON_USER is served by IIS, and is the name of the 72 # user after having been mapped to a local account. 73 # Both IIS and Apache set REMOTE_USER, when possible. 74 request.login = env('LOGON_USER') or env('REMOTE_USER') or None 75 request.multithread = environ['wsgi.multithread'] 76 request.multiprocess = environ['wsgi.multiprocess'] 77 request.wsgi_environ = environ 78 request.app = self.cpapp 79 return request 80 81 def tail(self, environ, start_response): 82 """WSGI application callable for the actual CherryPy application. 83 84 You probably shouldn't call this; call self.__call__ instead, 85 so that any WSGI middleware in self.pipeline can run first. 86 """ 87 request = None 88 try: 89 request = self.get_request(environ) 90 91 meth = environ['REQUEST_METHOD'] 92 path = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '') 93 qs = environ.get('QUERY_STRING', '') 94 rproto = environ.get('SERVER_PROTOCOL') 95 headers = self.translate_headers(environ) 96 rfile = environ['wsgi.input'] 97 98 response = request.run(meth, path, qs, rproto, headers, rfile) 99 s, h, b = response.status, response.header_list, response.body 100 exc = None 101 except self.throws, ex: 102 self._close_req(request) 103 raise ex 104 except: 105 if request and request.throw_errors: 106 raise 107 108 tb = _cperror.format_exc() 109 _cherrypy.log(tb) 110 if request and not request.show_tracebacks: 111 tb = "" 112 s, h, b = _cperror.bare_error(tb) 113 114 exc = _sys.exc_info() 115 116 try: 117 start_response(s, h, exc) 118 for chunk in b: 119 # WSGI requires all data to be of type "str". This coercion should 120 # not take any time at all if chunk is already of type "str". 121 # If it's unicode, it could be a big performance hit (x ~500). 122 if not isinstance(chunk, str): 123 chunk = chunk.encode("ISO-8859-1") 124 yield chunk 125 self._close_req(request) 126 except self.throws, ex: 127 self._close_req(request) 128 raise ex 129 except: 130 _cherrypy.log(traceback=True) 131 self._close_req(request) 132 133 # CherryPy test suite expects bare_error body to be output, 134 # so don't call start_response (which, according to PEP 333, 135 # may raise its own error at that point). 136 s, h, b = _cperror.bare_error() 137 for chunk in b: 138 if not isinstance(chunk, str): 139 chunk = chunk.encode("ISO-8859-1") 140 yield chunk 141 142 def _close_req(self, request): 143 if hasattr(request, "close"): 144 try: 145 request.close() 146 except: 147 _cherrypy.log(traceback=True) 148 149 def __call__(self, environ, start_response): 150 head = self.head 151 if head is None: 152 # Create and nest the WSGI apps in our pipeline (in reverse order). 153 head = self.tail 154 for name, callable in self.pipeline[::-1]: 155 conf = self.config.get(name, {}) 156 head = callable(head, **conf) 157 self.head = head 158 return head(environ, start_response) 34 159 35 160 def namespace_handler(self, k, v): 36 """Config handler for ournamespace."""161 """Config handler for the 'wsgi' namespace.""" 37 162 if k == "pipeline": 38 # Note this allows multiple entries to be aggregated (but also 39 # note dicts are essentially unordered). It should also allow 40 # developers to set default middleware in code (passed to 41 # pipeline.__init__) that deployers can add to but not remove. 42 self.extend(v) 43 44 if self: 45 # If self is empty, there's no need to replace app.wsgiapp. 46 # Also note we're grabbing app.wsgiapp, not app.__call__, 47 # so we can "play nice" with other Application-manglers 48 # (hopefully, they'll do the same). 49 self.tail = self.app.wsgiapp 50 self.app.wsgiapp = self.__call__ 163 # Note this allows multiple 'wsgi.pipeline' config entries 164 # (but each entry will be processed in a 'random' order). 165 # It should also allow developers to set default middleware 166 # in code (passed to self.__init__) that deployers can add to 167 # (but not remove) via config. 168 self.pipeline.extend(v) 51 169 else: 52 170 name, arg = k.split(".", 1) 53 171 bucket = self.config.setdefault(name, {}) 54 172 bucket[arg] = v 55 56 def __call__(self, environ, start_response):57 if not self.head:58 # This class may be used without calling namespace_handler,59 # in which case self.tail may still be None.60 self.head = self.tail or self.app.wsgiapp61 pipe = self[:]62 pipe.reverse()63 for name, callable in pipe:64 conf = self.config.get(name, {})65 self.head = callable(self.head, **conf)66 return self.head(environ, start_response)67 68 def __repr__(self):69 return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,70 list(self))71 173 72 174 trunk/cherrypy/test/test_wsgi_ns.py
r1418 r1430 39 39 40 40 app = cherrypy.Application(Root()) 41 p = cherrypy.wsgi.pipeline(app, [('changecase', ChangeCase)])42 p.config['changecase'] = {'to': 'upper'}41 app.wsgiapp.pipeline.append(('changecase', ChangeCase)) 42 app.wsgiapp.config['changecase'] = {'to': 'upper'} 43 43 cherrypy.tree.mount(app, config={'/': root_conf}) 44 45 # If we do not supply any middleware in code, pipeline is much cleaner:46 # app = cherrypy.Application(Root())47 # cherrypy.wsgi.pipeline(app)48 # cherrypy.tree.mount(app, config={'/': root_conf})49 44 50 45

