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

root/branches/598-sendall/cherrypy/_cprequest.py

Revision 1834 (checked in by fumanchu, 1 year ago)

New Request.stage attribute to help with debugging.

  • Property svn:eol-style set to native
Line 
1
2 import Cookie
3 import os
4 import sys
5 import time
6 import types
7
8 import cherrypy
9 from cherrypy import _cpcgifs, _cpconfig
10 from cherrypy._cperror import format_exc, bare_error
11 from cherrypy.lib import http
12
13
14 class Hook(object):
15     """A callback and its metadata: failsafe, priority, and kwargs."""
16    
17     __metaclass__ = cherrypy._AttributeDocstrings
18    
19     callback = None
20     callback__doc = """
21     The bare callable that this Hook object is wrapping, which will
22     be called when the Hook is called."""
23    
24     failsafe = False
25     failsafe__doc = """
26     If True, the callback is guaranteed to run even if other callbacks
27     from the same call point raise exceptions."""
28    
29     priority = 50
30     priority__doc = """
31     Defines the order of execution for a list of Hooks. Priority numbers
32     should be limited to the closed interval [0, 100], but values outside
33     this range are acceptable, as are fractional values."""
34    
35     kwargs = {}
36     kwargs__doc = """
37     A set of keyword arguments that will be passed to the
38     callable on each call."""
39    
40     def __init__(self, callback, failsafe=None, priority=None, **kwargs):
41         self.callback = callback
42        
43         if failsafe is None:
44             failsafe = getattr(callback, "failsafe", False)
45         self.failsafe = failsafe
46        
47         if priority is None:
48             priority = getattr(callback, "priority", 50)
49         self.priority = priority
50        
51         self.kwargs = kwargs
52    
53     def __cmp__(self, other):
54         return cmp(self.priority, other.priority)
55    
56     def __call__(self):
57         """Run self.callback(**self.kwargs)."""
58         return self.callback(**self.kwargs)
59    
60     def __repr__(self):
61         cls = self.__class__
62         return ("%s.%s(callback=%r, failsafe=%r, priority=%r, %s)"
63                 % (cls.__module__, cls.__name__, self.callback,
64                    self.failsafe, self.priority,
65                    ", ".join(['%s=%r' % (k, v)
66                               for k, v in self.kwargs.iteritems()])))
67
68
69 class HookMap(dict):
70     """A map of call points to lists of callbacks (Hook objects)."""
71    
72     def __new__(cls, points=None):
73         d = dict.__new__(cls)
74         for p in points or []:
75             d[p] = []
76         return d
77    
78     def __init__(self, *a, **kw):
79         pass
80    
81     def attach(self, point, callback, failsafe=None, priority=None, **kwargs):
82         """Append a new Hook made from the supplied arguments."""
83         self[point].append(Hook(callback, failsafe, priority, **kwargs))
84    
85     def run(self, point):
86         """Execute all registered Hooks (callbacks) for the given point."""
87         exc = None
88         hooks = self[point]
89         hooks.sort()
90         for hook in hooks:
91             # Some hooks are guaranteed to run even if others at
92             # the same hookpoint fail. We will still log the failure,
93             # but proceed on to the next hook. The only way
94             # to stop all processing from one of these hooks is
95             # to raise SystemExit and stop the whole server.
96             if exc is None or hook.failsafe:
97                 try:
98                     hook()
99                 except (KeyboardInterrupt, SystemExit):
100                     raise
101                 except (cherrypy.HTTPError, cherrypy.HTTPRedirect,
102                         cherrypy.InternalRedirect):
103                     exc = sys.exc_info()[1]
104                 except:
105                     exc = sys.exc_info()[1]
106                     cherrypy.log(traceback=True)
107         if exc:
108             raise
109    
110     def __copy__(self):
111         newmap = self.__class__()
112         # We can't just use 'update' because we want copies of the
113         # mutable values (each is a list) as well.
114         for k, v in self.iteritems():
115             newmap[k] = v[:]
116         return newmap
117     copy = __copy__
118    
119     def __repr__(self):
120         cls = self.__class__
121         return "%s.%s(points=%r)" % (cls.__module__, cls.__name__, self.keys())
122
123
124 # Config namespace handlers
125
126 def hooks_namespace(k, v):
127     """Attach bare hooks declared in config."""
128     # Use split again to allow multiple hooks for a single
129     # hookpoint per path (e.g. "hooks.before_handler.1").
130     # Little-known fact you only get from reading source ;)
131     hookpoint = k.split(".", 1)[0]
132     if isinstance(v, basestring):
133         v = cherrypy.lib.attributes(v)
134     if not isinstance(v, Hook):
135         v = Hook(v)
136     cherrypy.request.hooks[hookpoint].append(v)
137
138 def request_namespace(k, v):
139     """Attach request attributes declared in config."""
140     setattr(cherrypy.request, k, v)
141
142 def response_namespace(k, v):
143     """Attach response attributes declared in config."""
144     setattr(cherrypy.response, k, v)
145
146 def error_page_namespace(k, v):
147     """Attach error pages declared in config."""
148     cherrypy.request.error_page[int(k)] = v
149
150
151 hookpoints = ['on_start_resource', 'before_request_body',
152               'before_handler', 'before_finalize',
153               'on_end_resource', 'on_end_request',
154               'before_error_response', 'after_error_response']
155
156
157 class Request(object):
158     """An HTTP request.
159     
160     This object represents the metadata of an HTTP request message;
161     that is, it contains attributes which describe the environment
162     in which the request URL, headers, and body were sent (if you
163     want tools to interpret the headers and body, those are elsewhere,
164     mostly in Tools). This 'metadata' consists of socket data,
165     transport characteristics, and the Request-Line. This object
166     also contains data regarding the configuration in effect for
167     the given URL, and the execution plan for generating a response.
168     """
169    
170     __metaclass__ = cherrypy._AttributeDocstrings
171    
172     prev = None
173     prev__doc = """
174     The previous Request object (if any). This should be None
175     unless we are processing an InternalRedirect."""
176    
177     # Conversation/connection attributes
178     local = http.Host("127.0.0.1", 80)
179     local__doc = \
180         "An http.Host(ip, port, hostname) object for the server socket."
181    
182     remote = http.Host("127.0.0.1", 1111)
183     remote__doc = \
184         "An http.Host(ip, port, hostname) object for the client socket."
185    
186     scheme = "http"
187     scheme__doc = """
188     The protocol used between client and server. In most cases,
189     this will be either 'http' or 'https'."""
190    
191     server_protocol = "HTTP/1.1"
192     server_protocol__doc = """
193     The HTTP version for which the HTTP server is at least
194     conditionally compliant."""
195    
196     base = ""
197     base__doc = """The (scheme://host) portion of the requested URL."""
198    
199     # Request-Line attributes
200     request_line = ""
201     request_line__doc = """
202     The complete Request-Line received from the client. This is a
203     single string consisting of the request method, URI, and protocol
204     version (joined by spaces). Any final CRLF is removed."""
205    
206     method = "GET"
207     method__doc = """
208     Indicates the HTTP method to be performed on the resource identified
209     by the Request-URI. Common methods include GET, HEAD, POST, PUT, and
210     DELETE. CherryPy allows any extension method; however, various HTTP
211     servers and gateways may restrict the set of allowable methods.
212     CherryPy applications SHOULD restrict the set (on a per-URI basis)."""
213    
214     query_string = ""
215     query_string__doc = """
216     The query component of the Request-URI, a string of information to be
217     interpreted by the resource. The query portion of a URI follows the
218     path component, and is separated by a '?'. For example, the URI
219     'http://www.cherrypy.org/wiki?a=3&b=4' has the query component,
220     'a=3&b=4'."""
221    
222     protocol = (1, 1)
223     protocol__doc = """The HTTP protocol version corresponding to the set
224         of features which should be allowed in the response. If BOTH
225         the client's request message AND the server's level of HTTP
226         compliance is HTTP/1.1, this attribute will be the tuple (1, 1).
227         If either is 1.0, this attribute will be the tuple (1, 0).
228         Lower HTTP protocol versions are not explicitly supported."""
229    
230     params = {}
231     params__doc = """
232     A dict which combines query string (GET) and request entity (POST)
233     variables. This is populated in two stages: GET params are added
234     before the 'on_start_resource' hook, and POST params are added
235     between the 'before_request_body' and 'before_handler' hooks."""
236    
237     # Message attributes
238     header_list = []
239     header_list__doc = """
240     A list of the HTTP request headers as (name, value) tuples.
241     In general, you should use request.headers (a dict) instead."""
242    
243     headers = http.HeaderMap()
244     headers__doc = """
245     A dict-like object containing the request headers. Keys are header
246     names (in Title-Case format); however, you may get and set them in
247     a case-insensitive manner. That is, headers['Content-Type'] and
248     headers['content-type'] refer to the same value. Values are header
249     values (decoded according to RFC 2047 if necessary). See also:
250     http.HeaderMap, http.HeaderElement."""
251    
252     cookie = Cookie.SimpleCookie()
253     cookie__doc = """See help(Cookie)."""
254    
255     rfile = None
256     rfile__doc = """
257     If the request included an entity (body), it will be available
258     as a stream in this attribute. However, the rfile will normally
259     be read for you between the 'before_request_body' hook and the
260     'before_handler' hook, and the resulting string is placed into
261     either request.params or the request.body attribute.
262     
263     You may disable the automatic consumption of the rfile by setting
264     request.process_request_body to False, either in config for the desired
265     path, or in an 'on_start_resource' or 'before_request_body' hook.
266     
267     WARNING: In almost every case, you should not attempt to read from the
268     rfile stream after CherryPy's automatic mechanism has read it. If you
269     turn off the automatic parsing of rfile, you should read exactly the
270     number of bytes specified in request.headers['Content-Length'].
271     Ignoring either of these warnings may result in a hung request thread
272     or in corruption of the next (pipelined) request.
273     """
274    
275     process_request_body = True
276     process_request_body__doc = """
277     If True, the rfile (if any) is automatically read and parsed,
278     and the result placed into request.params or request.body."""
279    
280     methods_with_bodies = ("POST", "PUT")
281     methods_with_bodies__doc = """
282     A sequence of HTTP methods for which CherryPy will automatically
283     attempt to read a body from the rfile."""
284    
285     body = None
286     body__doc = """
287     If the request Content-Type is 'application/x-www-form-urlencoded'
288     or multipart, this will be None. Otherwise, this will contain the
289     request entity body as a string; this value is set between the
290     'before_request_body' and 'before_handler' hooks (assuming that
291     process_request_body is True)."""
292    
293     body_params = None
294     body_params__doc = """
295     If the request Content-Type is 'application/x-www-form-urlencoded' or
296     multipart, this will be a dict of the params pulled from the entity
297     body; that is, it will be the portion of request.params that come
298     from the message body (sometimes called "POST params", although they
299     can be sent with various HTTP method verbs). This value is set between
300     the 'before_request_body' and 'before_handler' hooks (assuming that
301     process_request_body is True)."""
302    
303     # Dispatch attributes
304     dispatch = cherrypy.dispatch.Dispatcher()
305     dispatch__doc = """
306     The object which looks up the 'page handler' callable and collects
307     config for the current request based on the path_info, other
308     request attributes, and the application architecture. The core
309     calls the dispatcher as early as possible, passing it a 'path_info'
310     argument.
311     
312     The default dispatcher discovers the page handler by matching path_info
313     to a hierarchical arrangement of objects, starting at request.app.root.
314     See help(cherrypy.dispatch) for more information."""
315    
316     script_name = ""
317     script_name__doc = """
318     The 'mount point' of the application which is handling this request.
319     
320     This attribute MUST NOT end in a slash. If the script_name refers to
321     the root of the URI, it MUST be an empty string (not "/").
322     """
323    
324     path_info = "/"
325     path_info__doc = """
326     The 'relative path' portion of the Request-URI. This is relative
327     to the script_name ('mount point') of the application which is
328     handling this request."""
329
330     login = None
331     login__doc = """
332     When authentication is used during the request processing this is
333     set to 'False' if it failed and to the 'username' value if it succeeded.
334     The default 'None' implies that no authentication happened."""
335    
336     # Note that cherrypy.url uses "if request.app:" to determine whether
337     # the call is during a real HTTP request or not. So leave this None.
338     app = None
339     app__doc = \
340         """The cherrypy.Application object which is handling this request."""
341    
342     handler = None
343     handler__doc = """
344     The function, method, or other callable which CherryPy will call to
345     produce the response. The discovery of the handler and the arguments
346     it will receive are determined by the request.dispatch object.
347     By default, the handler is discovered by walking a tree of objects
348     starting at request.app.root, and is then passed all HTTP params
349     (from the query string and POST body) as keyword arguments."""
350    
351     toolmaps = {}
352     toolmaps__doc = """
353     A nested dict of all Toolboxes and Tools in effect for this request,
354     of the form: {Toolbox.namespace: {Tool.name: config dict}}."""
355    
356     config = None
357     config__doc = """
358     A flat dict of all configuration entries which apply to the
359     current request. These entries are collected from global config,
360     application config (based on request.path_info), and from handler
361     config (exactly how is governed by the request.dispatch object in
362     effect for this request; by default, handler config can be attached
363     anywhere in the tree between request.app.root and the final handler,
364     and inherits downward)."""
365    
366     is_index = None
367     is_index__doc = """
368     This will be True if the current request is mapped to an 'index'
369     resource handler (also, a 'default' handler if path_info ends with
370     a slash). The value may be used to automatically redirect the
371     user-agent to a 'more canonical' URL which either adds or removes
372     the trailing slash. See cherrypy.tools.trailing_slash."""
373    
374     hooks = HookMap(hookpoints)
375     hooks__doc = """
376     A HookMap (dict-like object) of the form: {hookpoint: [hook, ...]}.
377     Each key is a str naming the hook point, and each value is a list
378     of hooks which will be called at that hook point during this request.
379     The list of hooks is generally populated as early as possible (mostly
380     from Tools specified in config), but may be extended at any time.
381     See also: _cprequest.Hook, _cprequest.HookMap, and cherrypy.tools."""
382    
383     error_response = cherrypy.HTTPError(500).set_response
384     error_response__doc = """
385     The no-arg callable which will handle unexpected, untrapped errors
386     during request processing. This is not used for expected exceptions
387     (like NotFound, HTTPError, or HTTPRedirect) which are raised in
388     response to expected conditions (those should be customized either
389     via request.error_page or by overriding HTTPError.set_response).
390     By default, error_response uses HTTPError(500) to return a generic
391     error response to the user-agent."""
392    
393     error_page = {}
394     error_page__doc = """
395     A dict of {error code: response filename} pairs. The named response
396     files should be Python string-formatting templates, and can expect by
397     default to receive the format values with the mapping keys 'status',
398     'message', 'traceback', and 'version'. The set of format mappings
399     can be extended by overriding HTTPError.set_response."""
400    
401     show_tracebacks = True
402     show_tracebacks__doc = """
403     If True, unexpected errors encountered during request processing will
404     include a traceback in the response body."""
405    
406     throws = (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect)
407     throws__doc = \
408         """The sequence of exceptions which Request.run does not trap."""
409    
410     throw_errors = False
411     throw_errors__doc = """
412     If True, Request.run will not trap any errors (except HTTPRedirect and
413     HTTPError, which are more properly called 'exceptions', not errors)."""
414    
415     closed = False
416     closed__doc = """
417     True once the close method has been called, False otherwise."""
418    
419     stage = None
420     stage__doc = """
421     A string containing the stage reached in the request-handling process.
422     This is useful when debugging a live server with hung requests."""
423    
424     namespaces = _cpconfig.NamespaceSet(
425         **{"hooks": hooks_namespace,
426            "request": request_namespace,
427            "response": response_namespace,
428            "error_page": error_page_namespace,
429            "tools": cherrypy.tools,
430            })
431    
432     def __init__(self, local_host, remote_host, scheme="http",
433                  server_protocol="HTTP/1.1"):
434         """Populate a new Request object.
435         
436         local_host should be an http.Host object with the server info.
437         remote_host should be an http.Host object with the client info.
438         scheme should be a string, either "http" or "https".
439         """
440         self.local = local_host
441         self.remote = remote_host
442         self.scheme = scheme
443         self.server_protocol = server_protocol
444        
445         self.closed = False
446        
447         # Put a *copy* of the class error_page into self.
448         self.error_page = self.error_page.copy()
449        
450         # Put a *copy* of the class namespaces into self.
451         self.namespaces = self.namespaces.copy()
452        
453         self.stage = None
454    
455     def close(self):
456         """Run cleanup code. (Core)"""
457         if not self.closed:
458             self.closed = True
459             self.stage = 'on_end_request'
460             self.hooks.run('on_end_request')
461             self.stage = 'close'
462    
463     def run(self, method, path, query_string, req_protocol, headers, rfile):
464         """Process the Request. (Core)
465         
466         method, path, query_string, and req_protocol should be pulled directly
467             from the Request-Line (e.g. "GET /path?key=val HTTP/1.0").
468         path should be %XX-unquoted, but query_string should not be.
469         headers should be a list of (name, value) tuples.
470         rfile should be a file-like object containing the HTTP request entity.
471         
472         When run() is done, the returned object should have 3 attributes:
473           status, e.g. "200 OK"
474           header_list, a list of (name, value) tuples
475           body, an iterable yielding strings
476         
477         Consumer code (HTTP servers) should then access these response
478         attributes to build the outbound stream.
479         
480         """
481         self.stage = 'run'
482         try:
483             self.error_response = cherrypy.HTTPError(500).set_response
484            
485             self.method = method
486             path = path or "/"
487             self.query_string = query_string or ''
488            
489             # Compare request and server HTTP protocol versions, in case our
490             # server does not support the requested protocol. Limit our output
491             # to min(req, server). We want the following output:
492             #     request    server     actual written   supported response
493             #     protocol   protocol  response protocol    feature set
494             # a     1.0        1.0           1.0                1.0
495             # b     1.0        1.1           1.1                1.0
496             # c     1.1        1.0           1.0                1.0
497             # d     1.1        1.1           1.1                1.1
498             # Notice that, in (b), the response will be "HTTP/1.1" even though
499             # the client only understands 1.0. RFC 2616 10.5.6 says we should
500             # only return 505 if the _major_ version is different.
501             rp = int(req_protocol[5]), int(req_protocol[7])
502             sp = int(self.server_protocol[5]), int(self.server_protocol[7])
503             self.protocol = min(rp, sp)
504            
505             # Rebuild first line of the request (e.g. "GET /path HTTP/1.0").
506             url = path
507             if query_string:
508                 url += '?' + query_string
509             self.request_line = '%s %s %s' % (method, url, req_protocol)
510            
511             self.header_list = list(headers)
512             self.rfile = rfile
513             self.headers = http.HeaderMap()
514             self.cookie = Cookie.SimpleCookie()
515             self.handler = None
516            
517             # path_info should be the path from the
518             # app root (script_name) to the handler.
519             self.script_name = self.app.script_name
520             self.path_info = pi = path[len(self.script_name):]
521            
522             self.stage = 'respond'
523             self.respond(pi)
524            
525         except self.throws:
526             raise
527         except:
528             if self.throw_errors:
529                 raise
530             else:
531                 # Failure in setup, error handler or finalize. Bypass them.
532                 # Can't use handle_error because we may not have hooks yet.
533                 cherrypy.log(traceback=True)
534                 if self.show_tracebacks:
535                     body = format_exc()
536                 else:
537                     body = ""
538                 r = bare_error(body)
539                 response = cherrypy.response
540                 response.status, response.header_list, response.body = r
541        
542         if self.method == "HEAD":
543             # HEAD requests MUST NOT return a message-body in the response.
544             cherrypy.response.body = []
545        
546         cherrypy.log.access()
547        
548         if cherrypy.response.timed_out:
549             raise cherrypy.TimeoutError()
550        
551         return cherrypy.response
552    
553     def respond(self, path_info):
554         """Generate a response for the resource at self.path_info. (Core)"""
555         try:
556             try:
557                 try:
558                     if self.app is None:
559                         raise cherrypy.NotFound()
560                    
561                     # Get the 'Host' header, so we can HTTPRedirect properly.
562                     self.stage = 'process_headers'
563                     self.process_headers()
564                    
565                     # Make a copy of the class hooks
566                     self.hooks = self.__class__.hooks.copy()
567                     self.toolmaps = {}
568                     self.stage = 'get_resource'
569                     self.get_resource(path_info)
570                     self.namespaces(self.config)
571                    
572                     self.stage = 'on_start_resource'
573                     self.hooks.run('on_start_resource')
574                    
575                     if self.process_request_body:
576                         if self.method not in self.methods_with_bodies:
577                             self.process_request_body = False
578                    
579                     self.stage = 'before_request_body'
580                     self.hooks.run('before_request_body')
581                     if self.process_request_body:
582                         self.process_body()
583                    
584                     self.stage = 'before_handler'
585                     self.hooks.run('before_handler')
586                     if self.handler:
587                         self.stage = 'handler'
588                         cherrypy.response.body = self.handler()
589                    
590                     self