Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/tags/cherrypy-3.0.0/cherrypy/_cpwsgi.py

Revision 1531 (checked in by fumanchu, 2 years ago)

IR now sets Content-Length and closes nextapp.

  • Property svn:eol-style set to native
Line 
1 """WSGI interface (see PEP 333)."""
2
3 import StringIO as _StringIO
4 import sys as _sys
5
6 import cherrypy as _cherrypy
7 from cherrypy import _cperror, wsgiserver
8 from cherrypy.lib import http as _http
9
10
11 #                            Internal Redirect                            #
12
13
14 class InternalRedirector(object):
15     """WSGI middleware which handles cherrypy.InternalRedirect.
16     
17     When cherrypy.InternalRedirect is raised, this middleware traps it,
18     rewrites the WSGI environ using the new path and query_string,
19     and calls the next application again. Because the wsgi.input stream
20     may have already been consumed by the next application, the redirected
21     call will always be of HTTP method "GET", and therefore any params must
22     be passed in the InternalRedirect object's query_string attribute.
23     If you need something more complicated, make and raise your own
24     exception and your own WSGI middleware to trap it. ;)
25     
26     It would be a bad idea to raise InternalRedirect after you've already
27     yielded response content, although an enterprising soul could choose
28     to abuse this.
29     
30     nextapp: the next application callable in the WSGI chain.
31     
32     recursive: if False (the default), each URL (path + qs) will be
33     stored, and, if the same URL is requested again, RuntimeError will
34     be raised. If 'recursive' is True, no such error will be raised.
35     """
36    
37     def __init__(self, nextapp, recursive=False):
38         self.nextapp = nextapp
39         self.recursive = recursive
40    
41     def __call__(self, environ, start_response):
42         return IRResponse(self.nextapp, environ, start_response, self.recursive)
43
44
45 class IRResponse(object):
46    
47     def __init__(self, nextapp, environ, start_response, recursive):
48         self.redirections = []
49         self.recursive = recursive
50         self.environ = environ.copy()
51         self.nextapp = nextapp
52         self.start_response = start_response
53         self.response = None
54         self.iter_response = None
55         self.setapp()
56    
57     def setapp(self):
58         while True:
59             try:
60                 self.response = self.nextapp(self.environ, self.start_response)
61                 self.iter_response = iter(self.response)
62                 return
63             except _cherrypy.InternalRedirect, ir:
64                 self.close()
65                 self.setenv(ir)
66    
67     def setenv(self, ir):
68         env = self.environ
69         if not self.recursive:
70             if ir.path in self.redirections:
71                 raise RuntimeError("InternalRedirector visited the "
72                                    "same URL twice: %r" % ir.path)
73             else:
74                 # Add the *previous* path_info + qs to redirections.
75                 sn = env.get('SCRIPT_NAME', '')
76                 path = env.get('PATH_INFO', '')
77                 qs = env.get('QUERY_STRING', '')
78                 if qs:
79                     qs = "?" + qs
80                 self.redirections.append(sn + path + qs)
81        
82         # Munge environment and try again.
83         env['REQUEST_METHOD'] = "GET"
84         env['PATH_INFO'] = ir.path
85         env['QUERY_STRING'] = ir.query_string
86         env['wsgi.input'] = _StringIO.StringIO()
87         env['CONTENT_LENGTH'] = "0"
88    
89     def close(self):
90         if hasattr(self.response, "close"):
91             self.response.close()
92    
93     def __iter__(self):
94         return self
95    
96     def next(self):
97         while True:
98             try:
99                 return self.iter_response.next()
100             except _cherrypy.InternalRedirect, ir:
101                 self.close()
102                 self.setenv(ir)
103                 self.setapp()
104
105
106
107 #                           WSGI-to-CP Adapter                           #
108
109
110 class AppResponse(object):
111    
112     throws = (KeyboardInterrupt, SystemExit, _cherrypy.InternalRedirect)
113     request = None
114    
115     def __init__(self, environ, start_response, cpapp):
116         try:
117             self.request = self.get_engine_request(environ, cpapp)
118            
119             meth = environ['REQUEST_METHOD']
120             path = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '')
121             qs = environ.get('QUERY_STRING', '')
122             rproto = environ.get('SERVER_PROTOCOL')
123             headers = self.translate_headers(environ)
124             rfile = environ['wsgi.input']
125            
126             response = self.request.run(meth, path, qs, rproto, headers, rfile)
127             s, h, b = response.status, response.header_list, response.body
128             exc = None
129         except self.throws:
130             self.close()
131             raise
132         except:
133             if getattr(self.request, "throw_errors", False):
134                 self.close()
135                 raise
136            
137             tb = _cperror.format_exc()
138             _cherrypy.log(tb)
139             if not getattr(self.request, "show_tracebacks", True):
140                 tb = ""
141             s, h, b = _cperror.bare_error(tb)
142             exc = _sys.exc_info()
143        
144         self.iter_response = iter(b)
145        
146         try:
147             start_response(s, h, exc)
148         except self.throws:
149             self.close()
150             raise
151         except:
152             if getattr(self.request, "throw_errors", False):
153                 self.close()
154                 raise
155            
156             _cherrypy.log(traceback=True)
157             self.close()
158            
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             s, h, b = _cperror.bare_error()
163             self.iter_response = iter(b)
164    
165     def __iter__(self):
166         return self
167    
168     def next(self):
169         try:
170             chunk = self.iter_response.next()
171             # WSGI requires all data to be of type "str". This coercion should
172             # not take any time at all if chunk is already of type "str".
173             # If it's unicode, it could be a big performance hit (x ~500).
174             if not isinstance(chunk, str):
175                 chunk = chunk.encode("ISO-8859-1")
176             return chunk
177         except self.throws:
178             raise
179         except StopIteration:
180             raise
181         except:
182             if getattr(self.request, "throw_errors", False):
183                 raise
184            
185             _cherrypy.log(traceback=True)
186            
187             # CherryPy test suite expects bare_error body to be output,
188             # so don't call start_response (which, according to PEP 333,
189             # may raise its own error at that point).
190             s, h, b = _cperror.bare_error()
191             self.iter_response = iter([])
192             return "".join(b)
193    
194     def close(self):
195         if hasattr(self.request, "close"):
196             try:
197                 self.request.close()
198             except:
199                 _cherrypy.log(traceback=True)
200    
201     def get_engine_request(self, environ, cpapp):
202         """Return a Request object from the CherryPy Engine using environ."""
203         env = environ.get
204        
205         local = _http.Host('', int(env('SERVER_PORT', 80)),
206                            env('SERVER_NAME', ''))
207         remote = _http.Host(env('REMOTE_ADDR', ''),
208                             int(env('REMOTE_PORT', -1)),
209                             env('REMOTE_HOST', ''))
210         scheme = env('wsgi.url_scheme')
211         sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")
212         request = _cherrypy.engine.request(local, remote, scheme, sproto)
213        
214         # LOGON_USER is served by IIS, and is the name of the
215         # user after having been mapped to a local account.
216         # Both IIS and Apache set REMOTE_USER, when possible.
217         request.login = env('LOGON_USER') or env('REMOTE_USER') or None
218         request.multithread = environ['wsgi.multithread']
219         request.multiprocess = environ['wsgi.multiprocess']
220         request.wsgi_environ = environ
221         request.app = cpapp
222         request.prev = env('cherrypy.request')
223         environ['cherrypy.request'] = request
224         return request
225    
226     headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization',
227                    'CONTENT_LENGTH': 'Content-Length',
228                    'CONTENT_TYPE': 'Content-Type',
229                    'REMOTE_HOST': 'Remote-Host',
230                    'REMOTE_ADDR': 'Remote-Addr',
231                    }
232    
233     def translate_headers(self, environ):
234         """Translate CGI-environ header names to HTTP header names."""
235         for cgiName in environ:
236             # We assume all incoming header keys are uppercase already.
237             if cgiName in self.headerNames:
238                 yield self.headerNames[cgiName], environ[cgiName]
239             elif cgiName[:5] == "HTTP_":
240                 # Hackish attempt at recovering original header names.
241                 translatedHeader = cgiName[5:].replace("_", "-")
242                 yield translatedHeader, environ[cgiName]
243
244
245 class CPWSGIApp(object):
246     """A WSGI application object for a CherryPy Application.
247     
248     pipeline: a list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
249         constructor that takes an initial, positional 'nextapp' argument,
250         plus optional keyword arguments, and returns a WSGI application
251         (that takes environ and start_response arguments). The 'name' can
252         be any you choose, and will correspond to keys in self.config.
253     
254     head: rather than nest all apps in the pipeline on each call, it's only
255         done the first time, and the result is memoized into self.head. Set
256         this to None again if you change self.pipeline after calling self.
257     
258     config: a dict whose keys match names listed in the pipeline. Each
259         value is a further dict which will be passed to the corresponding
260         named WSGI callable (from the pipeline) as keyword arguments.
261     """
262    
263     pipeline = [('iredir', InternalRedirector)]
264     head = None
265     config = {}
266    
267     response_class = AppResponse
268    
269     def __init__(self, cpapp, pipeline=None):
270         self.cpapp = cpapp
271         self.pipeline = self.pipeline[:]
272         if pipeline:
273             self.pipeline.extend(pipeline)
274         self.config = self.config.copy()
275    
276     def tail(self, environ, start_response):
277         """WSGI application callable for the actual CherryPy application.
278         
279         You probably shouldn't call this; call self.__call__ instead,
280         so that any WSGI middleware in self.pipeline can run first.
281         """
282         return self.response_class(environ, start_response, self.cpapp)
283    
284     def __call__(self, environ, start_response):
285         head = self.head
286         if head is None:
287             # Create and nest the WSGI apps in our pipeline (in reverse order).
288             # Then memoize the result in self.head.
289             head = self.tail
290             for name, callable in self.pipeline[::-1]:
291                 conf = self.config.get(name, {})
292                 head = callable(head, **conf)
293             self.head = head
294         return head(environ, start_response)
295    
296     def namespace_handler(self, k, v):
297         """Config handler for the 'wsgi' namespace."""
298         if k == "pipeline":
299             # Note this allows multiple 'wsgi.pipeline' config entries
300             # (but each entry will be processed in a 'random' order).
301             # It should also allow developers to set default middleware
302             # in code (passed to self.__init__) that deployers can add to
303             # (but not remove) via config.
304             self.pipeline.extend(v)
305         else:
306             name, arg = k.split(".", 1)
307             bucket = self.config.setdefault(name, {})
308             bucket[arg] = v
309
310
311
312 #                            Server components                            #
313
314
315 class CPHTTPRequest(wsgiserver.HTTPRequest):
316    
317     def parse_request(self):
318         mhs = _cherrypy.server.max_request_header_size
319         if mhs > 0:
320             self.rfile = _http.SizeCheckWrapper(self.rfile, mhs)
321        
322         try:
323             wsgiserver.HTTPRequest.parse_request(self)
324         except _http.MaxSizeExceeded:
325             self.simple_response("413 Request Entity Too Large")
326             _cherrypy.log(traceback=True)
327    
328     def decode_chunked(self):
329         """Decode the 'chunked' transfer coding."""
330         if isinstance(self.rfile, _http.SizeCheckWrapper):
331             self.rfile = self.rfile.rfile
332         mbs = _cherrypy.server.max_request_body_size
333         if mbs > 0:
334             self.rfile = _http.SizeCheckWrapper(self.rfile, mbs)
335         try:
336             return wsgiserver.HTTPRequest.decode_chunked(self)
337         except _http.MaxSizeExceeded:
338             self.simple_response("413 Request Entity Too Large")
339             _cherrypy.log(traceback=True)
340             return False
341
342
343 class CPHTTPConnection(wsgiserver.HTTPConnection):
344    
345     RequestHandlerClass = CPHTTPRequest
346
347
348 class CPWSGIServer(wsgiserver.CherryPyWSGIServer):
349    
350     """Wrapper for wsgiserver.CherryPyWSGIServer.
351     
352     wsgiserver has been designed to not reference CherryPy in any way,
353     so that it can be used in other frameworks and applications. Therefore,
354     we wrap it here, so we can set our own mount points from cherrypy.tree.
355     
356     """
357    
358     ConnectionClass = CPHTTPConnection
359    
360     def __init__(self):
361         server = _cherrypy.server
362         sockFile = server.socket_file
363         if sockFile:
364             bind_addr = sockFile
365         else:
366             bind_addr = (server.socket_host, server.socket_port)
367        
368         s = wsgiserver.CherryPyWSGIServer
369         # We could just pass cherrypy.tree, but by passing tree.apps,
370         # we get correct SCRIPT_NAMEs as early as possible.
371         s.__init__(self, bind_addr, _cherrypy.tree.apps.items(),
372                    server.thread_pool,
373                    server.socket_host,
374                    request_queue_size = server.socket_queue_size,
375                    timeout = server.socket_timeout,
376                    )
377         self.protocol = server.protocol_version
378         self.ssl_certificate = server.ssl_certificate
379         self.ssl_private_key = server.ssl_private_key
380
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets