Removed spurious static_path.
[smonitor.git] / monitor / cherrypy / _cprequest.py
blobae5e89716589908f3e347470888502c2c91df6f5
2 import os
3 import sys
4 import time
5 import warnings
7 import cherrypy
8 from cherrypy._cpcompat import basestring, copykeys, ntob, unicodestr
9 from cherrypy._cpcompat import SimpleCookie, CookieError
10 from cherrypy import _cpreqbody, _cpconfig
11 from cherrypy._cperror import format_exc, bare_error
12 from cherrypy.lib import httputil, file_generator
15 class Hook(object):
16 """A callback and its metadata: failsafe, priority, and kwargs."""
18 callback = None
19 """
20 The bare callable that this Hook object is wrapping, which will
21 be called when the Hook is called."""
23 failsafe = False
24 """
25 If True, the callback is guaranteed to run even if other callbacks
26 from the same call point raise exceptions."""
28 priority = 50
29 """
30 Defines the order of execution for a list of Hooks. Priority numbers
31 should be limited to the closed interval [0, 100], but values outside
32 this range are acceptable, as are fractional values."""
34 kwargs = {}
35 """
36 A set of keyword arguments that will be passed to the
37 callable on each call."""
39 def __init__(self, callback, failsafe=None, priority=None, **kwargs):
40 self.callback = callback
42 if failsafe is None:
43 failsafe = getattr(callback, "failsafe", False)
44 self.failsafe = failsafe
46 if priority is None:
47 priority = getattr(callback, "priority", 50)
48 self.priority = priority
50 self.kwargs = kwargs
52 def __cmp__(self, other):
53 return cmp(self.priority, other.priority)
55 def __call__(self):
56 """Run self.callback(**self.kwargs)."""
57 return self.callback(**self.kwargs)
59 def __repr__(self):
60 cls = self.__class__
61 return ("%s.%s(callback=%r, failsafe=%r, priority=%r, %s)"
62 % (cls.__module__, cls.__name__, self.callback,
63 self.failsafe, self.priority,
64 ", ".join(['%s=%r' % (k, v)
65 for k, v in self.kwargs.items()])))
68 class HookMap(dict):
69 """A map of call points to lists of callbacks (Hook objects)."""
71 def __new__(cls, points=None):
72 d = dict.__new__(cls)
73 for p in points or []:
74 d[p] = []
75 return d
77 def __init__(self, *a, **kw):
78 pass
80 def attach(self, point, callback, failsafe=None, priority=None, **kwargs):
81 """Append a new Hook made from the supplied arguments."""
82 self[point].append(Hook(callback, failsafe, priority, **kwargs))
84 def run(self, point):
85 """Execute all registered Hooks (callbacks) for the given point."""
86 exc = None
87 hooks = self[point]
88 hooks.sort()
89 for hook in hooks:
90 # Some hooks are guaranteed to run even if others at
91 # the same hookpoint fail. We will still log the failure,
92 # but proceed on to the next hook. The only way
93 # to stop all processing from one of these hooks is
94 # to raise SystemExit and stop the whole server.
95 if exc is None or hook.failsafe:
96 try:
97 hook()
98 except (KeyboardInterrupt, SystemExit):
99 raise
100 except (cherrypy.HTTPError, cherrypy.HTTPRedirect,
101 cherrypy.InternalRedirect):
102 exc = sys.exc_info()[1]
103 except:
104 exc = sys.exc_info()[1]
105 cherrypy.log(traceback=True, severity=40)
106 if exc:
107 raise
109 def __copy__(self):
110 newmap = self.__class__()
111 # We can't just use 'update' because we want copies of the
112 # mutable values (each is a list) as well.
113 for k, v in self.items():
114 newmap[k] = v[:]
115 return newmap
116 copy = __copy__
118 def __repr__(self):
119 cls = self.__class__
120 return "%s.%s(points=%r)" % (cls.__module__, cls.__name__, copykeys(self))
123 # Config namespace handlers
125 def hooks_namespace(k, v):
126 """Attach bare hooks declared in config."""
127 # Use split again to allow multiple hooks for a single
128 # hookpoint per path (e.g. "hooks.before_handler.1").
129 # Little-known fact you only get from reading source ;)
130 hookpoint = k.split(".", 1)[0]
131 if isinstance(v, basestring):
132 v = cherrypy.lib.attributes(v)
133 if not isinstance(v, Hook):
134 v = Hook(v)
135 cherrypy.serving.request.hooks[hookpoint].append(v)
137 def request_namespace(k, v):
138 """Attach request attributes declared in config."""
139 # Provides config entries to set request.body attrs (like attempt_charsets).
140 if k[:5] == 'body.':
141 setattr(cherrypy.serving.request.body, k[5:], v)
142 else:
143 setattr(cherrypy.serving.request, k, v)
145 def response_namespace(k, v):
146 """Attach response attributes declared in config."""
147 # Provides config entries to set default response headers
148 # http://cherrypy.org/ticket/889
149 if k[:8] == 'headers.':
150 cherrypy.serving.response.headers[k.split('.', 1)[1]] = v
151 else:
152 setattr(cherrypy.serving.response, k, v)
154 def error_page_namespace(k, v):
155 """Attach error pages declared in config."""
156 if k != 'default':
157 k = int(k)
158 cherrypy.serving.request.error_page[k] = v
161 hookpoints = ['on_start_resource', 'before_request_body',
162 'before_handler', 'before_finalize',
163 'on_end_resource', 'on_end_request',
164 'before_error_response', 'after_error_response']
167 class Request(object):
168 """An HTTP request.
170 This object represents the metadata of an HTTP request message;
171 that is, it contains attributes which describe the environment
172 in which the request URL, headers, and body were sent (if you
173 want tools to interpret the headers and body, those are elsewhere,
174 mostly in Tools). This 'metadata' consists of socket data,
175 transport characteristics, and the Request-Line. This object
176 also contains data regarding the configuration in effect for
177 the given URL, and the execution plan for generating a response.
180 prev = None
182 The previous Request object (if any). This should be None
183 unless we are processing an InternalRedirect."""
185 # Conversation/connection attributes
186 local = httputil.Host("127.0.0.1", 80)
187 "An httputil.Host(ip, port, hostname) object for the server socket."
189 remote = httputil.Host("127.0.0.1", 1111)
190 "An httputil.Host(ip, port, hostname) object for the client socket."
192 scheme = "http"
194 The protocol used between client and server. In most cases,
195 this will be either 'http' or 'https'."""
197 server_protocol = "HTTP/1.1"
199 The HTTP version for which the HTTP server is at least
200 conditionally compliant."""
202 base = ""
203 """The (scheme://host) portion of the requested URL.
204 In some cases (e.g. when proxying via mod_rewrite), this may contain
205 path segments which cherrypy.url uses when constructing url's, but
206 which otherwise are ignored by CherryPy. Regardless, this value
207 MUST NOT end in a slash."""
209 # Request-Line attributes
210 request_line = ""
212 The complete Request-Line received from the client. This is a
213 single string consisting of the request method, URI, and protocol
214 version (joined by spaces). Any final CRLF is removed."""
216 method = "GET"
218 Indicates the HTTP method to be performed on the resource identified
219 by the Request-URI. Common methods include GET, HEAD, POST, PUT, and
220 DELETE. CherryPy allows any extension method; however, various HTTP
221 servers and gateways may restrict the set of allowable methods.
222 CherryPy applications SHOULD restrict the set (on a per-URI basis)."""
224 query_string = ""
226 The query component of the Request-URI, a string of information to be
227 interpreted by the resource. The query portion of a URI follows the
228 path component, and is separated by a '?'. For example, the URI
229 'http://www.cherrypy.org/wiki?a=3&b=4' has the query component,
230 'a=3&b=4'."""
232 query_string_encoding = 'utf8'
234 The encoding expected for query string arguments after % HEX HEX decoding).
235 If a query string is provided that cannot be decoded with this encoding,
236 404 is raised (since technically it's a different URI). If you want
237 arbitrary encodings to not error, set this to 'Latin-1'; you can then
238 encode back to bytes and re-decode to whatever encoding you like later.
241 protocol = (1, 1)
242 """The HTTP protocol version corresponding to the set
243 of features which should be allowed in the response. If BOTH
244 the client's request message AND the server's level of HTTP
245 compliance is HTTP/1.1, this attribute will be the tuple (1, 1).
246 If either is 1.0, this attribute will be the tuple (1, 0).
247 Lower HTTP protocol versions are not explicitly supported."""
249 params = {}
251 A dict which combines query string (GET) and request entity (POST)
252 variables. This is populated in two stages: GET params are added
253 before the 'on_start_resource' hook, and POST params are added
254 between the 'before_request_body' and 'before_handler' hooks."""
256 # Message attributes
257 header_list = []
259 A list of the HTTP request headers as (name, value) tuples.
260 In general, you should use request.headers (a dict) instead."""
262 headers = httputil.HeaderMap()
264 A dict-like object containing the request headers. Keys are header
265 names (in Title-Case format); however, you may get and set them in
266 a case-insensitive manner. That is, headers['Content-Type'] and
267 headers['content-type'] refer to the same value. Values are header
268 values (decoded according to :rfc:`2047` if necessary). See also:
269 httputil.HeaderMap, httputil.HeaderElement."""
271 cookie = SimpleCookie()
272 """See help(Cookie)."""
274 rfile = None
276 If the request included an entity (body), it will be available
277 as a stream in this attribute. However, the rfile will normally
278 be read for you between the 'before_request_body' hook and the
279 'before_handler' hook, and the resulting string is placed into
280 either request.params or the request.body attribute.
282 You may disable the automatic consumption of the rfile by setting
283 request.process_request_body to False, either in config for the desired
284 path, or in an 'on_start_resource' or 'before_request_body' hook.
286 WARNING: In almost every case, you should not attempt to read from the
287 rfile stream after CherryPy's automatic mechanism has read it. If you
288 turn off the automatic parsing of rfile, you should read exactly the
289 number of bytes specified in request.headers['Content-Length'].
290 Ignoring either of these warnings may result in a hung request thread
291 or in corruption of the next (pipelined) request.
294 process_request_body = True
296 If True, the rfile (if any) is automatically read and parsed,
297 and the result placed into request.params or request.body."""
299 methods_with_bodies = ("POST", "PUT")
301 A sequence of HTTP methods for which CherryPy will automatically
302 attempt to read a body from the rfile."""
304 body = None
306 If the request Content-Type is 'application/x-www-form-urlencoded'
307 or multipart, this will be None. Otherwise, this will be an instance
308 of :class:`RequestBody<cherrypy._cpreqbody.RequestBody>` (which you
309 can .read()); this value is set between the 'before_request_body' and
310 'before_handler' hooks (assuming that process_request_body is True)."""
312 # Dispatch attributes
313 dispatch = cherrypy.dispatch.Dispatcher()
315 The object which looks up the 'page handler' callable and collects
316 config for the current request based on the path_info, other
317 request attributes, and the application architecture. The core
318 calls the dispatcher as early as possible, passing it a 'path_info'
319 argument.
321 The default dispatcher discovers the page handler by matching path_info
322 to a hierarchical arrangement of objects, starting at request.app.root.
323 See help(cherrypy.dispatch) for more information."""
325 script_name = ""
327 The 'mount point' of the application which is handling this request.
329 This attribute MUST NOT end in a slash. If the script_name refers to
330 the root of the URI, it MUST be an empty string (not "/").
333 path_info = "/"
335 The 'relative path' portion of the Request-URI. This is relative
336 to the script_name ('mount point') of the application which is
337 handling this request."""
339 login = None
341 When authentication is used during the request processing this is
342 set to 'False' if it failed and to the 'username' value if it succeeded.
343 The default 'None' implies that no authentication happened."""
345 # Note that cherrypy.url uses "if request.app:" to determine whether
346 # the call is during a real HTTP request or not. So leave this None.
347 app = None
348 """The cherrypy.Application object which is handling this request."""
350 handler = None
352 The function, method, or other callable which CherryPy will call to
353 produce the response. The discovery of the handler and the arguments
354 it will receive are determined by the request.dispatch object.
355 By default, the handler is discovered by walking a tree of objects
356 starting at request.app.root, and is then passed all HTTP params
357 (from the query string and POST body) as keyword arguments."""
359 toolmaps = {}
361 A nested dict of all Toolboxes and Tools in effect for this request,
362 of the form: {Toolbox.namespace: {Tool.name: config dict}}."""
364 config = None
366 A flat dict of all configuration entries which apply to the
367 current request. These entries are collected from global config,
368 application config (based on request.path_info), and from handler
369 config (exactly how is governed by the request.dispatch object in
370 effect for this request; by default, handler config can be attached
371 anywhere in the tree between request.app.root and the final handler,
372 and inherits downward)."""
374 is_index = None
376 This will be True if the current request is mapped to an 'index'
377 resource handler (also, a 'default' handler if path_info ends with
378 a slash). The value may be used to automatically redirect the
379 user-agent to a 'more canonical' URL which either adds or removes
380 the trailing slash. See cherrypy.tools.trailing_slash."""
382 hooks = HookMap(hookpoints)
384 A HookMap (dict-like object) of the form: {hookpoint: [hook, ...]}.
385 Each key is a str naming the hook point, and each value is a list
386 of hooks which will be called at that hook point during this request.
387 The list of hooks is generally populated as early as possible (mostly
388 from Tools specified in config), but may be extended at any time.
389 See also: _cprequest.Hook, _cprequest.HookMap, and cherrypy.tools."""
391 error_response = cherrypy.HTTPError(500).set_response
393 The no-arg callable which will handle unexpected, untrapped errors
394 during request processing. This is not used for expected exceptions
395 (like NotFound, HTTPError, or HTTPRedirect) which are raised in
396 response to expected conditions (those should be customized either
397 via request.error_page or by overriding HTTPError.set_response).
398 By default, error_response uses HTTPError(500) to return a generic
399 error response to the user-agent."""
401 error_page = {}
403 A dict of {error code: response filename or callable} pairs.
405 The error code must be an int representing a given HTTP error code,
406 or the string 'default', which will be used if no matching entry
407 is found for a given numeric code.
409 If a filename is provided, the file should contain a Python string-
410 formatting template, and can expect by default to receive format
411 values with the mapping keys %(status)s, %(message)s, %(traceback)s,
412 and %(version)s. The set of format mappings can be extended by
413 overriding HTTPError.set_response.
415 If a callable is provided, it will be called by default with keyword
416 arguments 'status', 'message', 'traceback', and 'version', as for a
417 string-formatting template. The callable must return a string or iterable of
418 strings which will be set to response.body. It may also override headers or
419 perform any other processing.
421 If no entry is given for an error code, and no 'default' entry exists,
422 a default template will be used.
425 show_tracebacks = True
427 If True, unexpected errors encountered during request processing will
428 include a traceback in the response body."""
430 show_mismatched_params = True
432 If True, mismatched parameters encountered during PageHandler invocation
433 processing will be included in the response body."""
435 throws = (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect)
436 """The sequence of exceptions which Request.run does not trap."""
438 throw_errors = False
440 If True, Request.run will not trap any errors (except HTTPRedirect and
441 HTTPError, which are more properly called 'exceptions', not errors)."""
443 closed = False
444 """True once the close method has been called, False otherwise."""
446 stage = None
448 A string containing the stage reached in the request-handling process.
449 This is useful when debugging a live server with hung requests."""
451 namespaces = _cpconfig.NamespaceSet(
452 **{"hooks": hooks_namespace,
453 "request": request_namespace,
454 "response": response_namespace,
455 "error_page": error_page_namespace,
456 "tools": cherrypy.tools,
459 def __init__(self, local_host, remote_host, scheme="http",
460 server_protocol="HTTP/1.1"):
461 """Populate a new Request object.
463 local_host should be an httputil.Host object with the server info.
464 remote_host should be an httputil.Host object with the client info.
465 scheme should be a string, either "http" or "https".
467 self.local = local_host
468 self.remote = remote_host
469 self.scheme = scheme
470 self.server_protocol = server_protocol
472 self.closed = False
474 # Put a *copy* of the class error_page into self.
475 self.error_page = self.error_page.copy()
477 # Put a *copy* of the class namespaces into self.
478 self.namespaces = self.namespaces.copy()
480 self.stage = None
482 def close(self):
483 """Run cleanup code. (Core)"""
484 if not self.closed:
485 self.closed = True
486 self.stage = 'on_end_request'
487 self.hooks.run('on_end_request')
488 self.stage = 'close'
490 def run(self, method, path, query_string, req_protocol, headers, rfile):
491 """Process the Request. (Core)
493 method, path, query_string, and req_protocol should be pulled directly
494 from the Request-Line (e.g. "GET /path?key=val HTTP/1.0").
496 path
497 This should be %XX-unquoted, but query_string should not be.
498 They both MUST be byte strings, not unicode strings.
500 headers
501 A list of (name, value) tuples.
503 rfile
504 A file-like object containing the HTTP request entity.
506 When run() is done, the returned object should have 3 attributes:
508 * status, e.g. "200 OK"
509 * header_list, a list of (name, value) tuples
510 * body, an iterable yielding strings
512 Consumer code (HTTP servers) should then access these response
513 attributes to build the outbound stream.
516 response = cherrypy.serving.response
517 self.stage = 'run'
518 try:
519 self.error_response = cherrypy.HTTPError(500).set_response
521 self.method = method
522 path = path or "/"
523 self.query_string = query_string or ''
524 self.params = {}
526 # Compare request and server HTTP protocol versions, in case our
527 # server does not support the requested protocol. Limit our output
528 # to min(req, server). We want the following output:
529 # request server actual written supported response
530 # protocol protocol response protocol feature set
531 # a 1.0 1.0 1.0 1.0
532 # b 1.0 1.1 1.1 1.0
533 # c 1.1 1.0 1.0 1.0
534 # d 1.1 1.1 1.1 1.1
535 # Notice that, in (b), the response will be "HTTP/1.1" even though
536 # the client only understands 1.0. RFC 2616 10.5.6 says we should
537 # only return 505 if the _major_ version is different.
538 rp = int(req_protocol[5]), int(req_protocol[7])
539 sp = int(self.server_protocol[5]), int(self.server_protocol[7])
540 self.protocol = min(rp, sp)
541 response.headers.protocol = self.protocol
543 # Rebuild first line of the request (e.g. "GET /path HTTP/1.0").
544 url = path
545 if query_string:
546 url += '?' + query_string
547 self.request_line = '%s %s %s' % (method, url, req_protocol)
549 self.header_list = list(headers)
550 self.headers = httputil.HeaderMap()
552 self.rfile = rfile
553 self.body = None
555 self.cookie = SimpleCookie()
556 self.handler = None
558 # path_info should be the path from the
559 # app root (script_name) to the handler.
560 self.script_name = self.app.script_name
561 self.path_info = pi = path[len(self.script_name):]
563 self.stage = 'respond'
564 self.respond(pi)
566 except self.throws:
567 raise
568 except:
569 if self.throw_errors:
570 raise
571 else:
572 # Failure in setup, error handler or finalize. Bypass them.
573 # Can't use handle_error because we may not have hooks yet.
574 cherrypy.log(traceback=True, severity=40)
575 if self.show_tracebacks:
576 body = format_exc()
577 else:
578 body = ""
579 r = bare_error(body)
580 response.output_status, response.header_list, response.body = r
582 if self.method == "HEAD":
583 # HEAD requests MUST NOT return a message-body in the response.
584 response.body = []
586 try:
587 cherrypy.log.access()
588 except:
589 cherrypy.log.error(traceback=True)
591 if response.timed_out:
592 raise cherrypy.TimeoutError()
594 return response
596 # Uncomment for stage debugging
597 # stage = property(lambda self: self._stage, lambda self, v: print(v))
599 def respond(self, path_info):
600 """Generate a response for the resource at self.path_info. (Core)"""
601 response = cherrypy.serving.response
602 try:
603 try:
604 try:
605 if self.app is None:
606 raise cherrypy.NotFound()
608 # Get the 'Host' header, so we can HTTPRedirect properly.
609 self.stage = 'process_headers'
610 self.process_headers()
612 # Make a copy of the class hooks
613 self.hooks = self.__class__.hooks.copy()
614 self.toolmaps = {}
616 self.stage = 'get_resource'
617 self.get_resource(path_info)
619 self.body = _cpreqbody.RequestBody(
620 self.rfile, self.headers, request_params=self.params)
622 self.namespaces(self.config)
624 self.stage = 'on_start_resource'
625 self.hooks.run('on_start_resource')
627 # Parse the querystring
628 self.stage = 'process_query_string'
629 self.process_query_string()
631 # Process the body
632 if self.process_request_body:
633 if self.method not in self.methods_with_bodies:
634 self.process_request_body = False
635 self.stage = 'before_request_body'
636 self.hooks.run('before_request_body')
637 if self.process_request_body:
638 self.body.process()
640 # Run the handler
641 self.stage = 'before_handler'
642 self.hooks.run('before_handler')
643 if self.handler:
644 self.stage = 'handler'
645 response.body = self.handler()
647 # Finalize
648 self.stage = 'before_finalize'
649 self.hooks.run('before_finalize')
650 response.finalize()
651 except (cherrypy.HTTPRedirect, cherrypy.HTTPError):
652 inst = sys.exc_info()[1]
653 inst.set_response()
654 self.stage = 'before_finalize (HTTPError)'
655 self.hooks.run('before_finalize')
656 response.finalize()
657 finally:
658 self.stage = 'on_end_resource'
659 self.hooks.run('on_end_resource')
660 except self.throws:
661 raise
662 except:
663 if self.throw_errors:
664 raise
665 self.handle_error()
667 def process_query_string(self):
668 """Parse the query string into Python structures. (Core)"""
669 try:
670 p = httputil.parse_query_string(
671 self.query_string, encoding=self.query_string_encoding)
672 except UnicodeDecodeError:
673 raise cherrypy.HTTPError(
674 404, "The given query string could not be processed. Query "
675 "strings for this resource must be encoded with %r." %
676 self.query_string_encoding)
678 # Python 2 only: keyword arguments must be byte strings (type 'str').
679 for key, value in p.items():
680 if isinstance(key, unicode):
681 del p[key]
682 p[key.encode(self.query_string_encoding)] = value
683 self.params.update(p)
685 def process_headers(self):
686 """Parse HTTP header data into Python structures. (Core)"""
687 # Process the headers into self.headers
688 headers = self.headers
689 for name, value in self.header_list:
690 # Call title() now (and use dict.__method__(headers))
691 # so title doesn't have to be called twice.
692 name = name.title()
693 value = value.strip()
695 # Warning: if there is more than one header entry for cookies (AFAIK,
696 # only Konqueror does that), only the last one will remain in headers
697 # (but they will be correctly stored in request.cookie).
698 if "=?" in value:
699 dict.__setitem__(headers, name, httputil.decode_TEXT(value))
700 else:
701 dict.__setitem__(headers, name, value)
703 # Handle cookies differently because on Konqueror, multiple
704 # cookies come on different lines with the same key
705 if name == 'Cookie':
706 try:
707 self.cookie.load(value)
708 except CookieError:
709 msg = "Illegal cookie name %s" % value.split('=')[0]
710 raise cherrypy.HTTPError(400, msg)
712 if not dict.__contains__(headers, 'Host'):
713 # All Internet-based HTTP/1.1 servers MUST respond with a 400
714 # (Bad Request) status code to any HTTP/1.1 request message
715 # which lacks a Host header field.
716 if self.protocol >= (1, 1):
717 msg = "HTTP/1.1 requires a 'Host' request header."
718 raise cherrypy.HTTPError(400, msg)
719 host = dict.get(headers, 'Host')
720 if not host:
721 host = self.local.name or self.local.ip
722 self.base = "%s://%s" % (self.scheme, host)
724 def get_resource(self, path):
725 """Call a dispatcher (which sets self.handler and .config). (Core)"""
726 # First, see if there is a custom dispatch at this URI. Custom
727 # dispatchers can only be specified in app.config, not in _cp_config
728 # (since custom dispatchers may not even have an app.root).
729 dispatch = self.app.find_config(path, "request.dispatch", self.dispatch)
731 # dispatch() should set self.handler and self.config
732 dispatch(path)
734 def handle_error(self):
735 """Handle the last unanticipated exception. (Core)"""
736 try:
737 self.hooks.run("before_error_response")
738 if self.error_response:
739 self.error_response()
740 self.hooks.run("after_error_response")
741 cherrypy.serving.response.finalize()
742 except cherrypy.HTTPRedirect:
743 inst = sys.exc_info()[1]
744 inst.set_response()
745 cherrypy.serving.response.finalize()
747 # ------------------------- Properties ------------------------- #
749 def _get_body_params(self):
750 warnings.warn(
751 "body_params is deprecated in CherryPy 3.2, will be removed in "
752 "CherryPy 3.3.",
753 DeprecationWarning
755 return self.body.params
756 body_params = property(_get_body_params,
757 doc= """
758 If the request Content-Type is 'application/x-www-form-urlencoded' or
759 multipart, this will be a dict of the params pulled from the entity
760 body; that is, it will be the portion of request.params that come
761 from the message body (sometimes called "POST params", although they
762 can be sent with various HTTP method verbs). This value is set between
763 the 'before_request_body' and 'before_handler' hooks (assuming that
764 process_request_body is True).
766 Deprecated in 3.2, will be removed for 3.3 in favor of
767 :attr:`request.body.params<cherrypy._cprequest.RequestBody.params>`.""")
770 class ResponseBody(object):
771 """The body of the HTTP response (the response entity)."""
773 def __get__(self, obj, objclass=None):
774 if obj is None:
775 # When calling on the class instead of an instance...
776 return self
777 else:
778 return obj._body
780 def __set__(self, obj, value):
781 # Convert the given value to an iterable object.
782 if isinstance(value, basestring):
783 # strings get wrapped in a list because iterating over a single
784 # item list is much faster than iterating over every character
785 # in a long string.
786 if value:
787 value = [value]
788 else:
789 # [''] doesn't evaluate to False, so replace it with [].
790 value = []
791 # Don't use isinstance here; io.IOBase which has an ABC takes
792 # 1000 times as long as, say, isinstance(value, str)
793 elif hasattr(value, 'read'):
794 value = file_generator(value)
795 elif value is None:
796 value = []
797 obj._body = value
800 class Response(object):
801 """An HTTP Response, including status, headers, and body."""
803 status = ""
804 """The HTTP Status-Code and Reason-Phrase."""
806 header_list = []
808 A list of the HTTP response headers as (name, value) tuples.
809 In general, you should use response.headers (a dict) instead. This
810 attribute is generated from response.headers and is not valid until
811 after the finalize phase."""
813 headers = httputil.HeaderMap()
815 A dict-like object containing the response headers. Keys are header
816 names (in Title-Case format); however, you may get and set them in
817 a case-insensitive manner. That is, headers['Content-Type'] and
818 headers['content-type'] refer to the same value. Values are header
819 values (decoded according to :rfc:`2047` if necessary).
821 .. seealso:: classes :class:`HeaderMap`, :class:`HeaderElement`
824 cookie = SimpleCookie()
825 """See help(Cookie)."""
827 body = ResponseBody()
828 """The body (entity) of the HTTP response."""
830 time = None
831 """The value of time.time() when created. Use in HTTP dates."""
833 timeout = 300
834 """Seconds after which the response will be aborted."""
836 timed_out = False
838 Flag to indicate the response should be aborted, because it has
839 exceeded its timeout."""
841 stream = False
842 """If False, buffer the response body."""
844 def __init__(self):
845 self.status = None
846 self.header_list = None
847 self._body = []
848 self.time = time.time()
850 self.headers = httputil.HeaderMap()
851 # Since we know all our keys are titled strings, we can
852 # bypass HeaderMap.update and get a big speed boost.
853 dict.update(self.headers, {
854 "Content-Type": 'text/html',
855 "Server": "CherryPy/" + cherrypy.__version__,
856 "Date": httputil.HTTPDate(self.time),
858 self.cookie = SimpleCookie()
860 def collapse_body(self):
861 """Collapse self.body to a single string; replace it and return it."""
862 if isinstance(self.body, basestring):
863 return self.body
865 newbody = ''.join([chunk for chunk in self.body])
867 self.body = newbody
868 return newbody
870 def finalize(self):
871 """Transform headers (and cookies) into self.header_list. (Core)"""
872 try:
873 code, reason, _ = httputil.valid_status(self.status)
874 except ValueError:
875 raise cherrypy.HTTPError(500, sys.exc_info()[1].args[0])
877 headers = self.headers
879 self.output_status = ntob(str(code), 'ascii') + ntob(" ") + headers.encode(reason)
881 if self.stream:
882 # The upshot: wsgiserver will chunk the response if
883 # you pop Content-Length (or set it explicitly to None).
884 # Note that lib.static sets C-L to the file's st_size.
885 if dict.get(headers, 'Content-Length') is None:
886 dict.pop(headers, 'Content-Length', None)
887 elif code < 200 or code in (204, 205, 304):
888 # "All 1xx (informational), 204 (no content),
889 # and 304 (not modified) responses MUST NOT
890 # include a message-body."
891 dict.pop(headers, 'Content-Length', None)
892 self.body = ntob("")
893 else:
894 # Responses which are not streamed should have a Content-Length,
895 # but allow user code to set Content-Length if desired.
896 if dict.get(headers, 'Content-Length') is None:
897 content = self.collapse_body()
898 dict.__setitem__(headers, 'Content-Length', len(content))
900 # Transform our header dict into a list of tuples.
901 self.header_list = h = headers.output()
903 cookie = self.cookie.output()
904 if cookie:
905 for line in cookie.split("\n"):
906 if line.endswith("\r"):
907 # Python 2.4 emits cookies joined by LF but 2.5+ by CRLF.
908 line = line[:-1]
909 name, value = line.split(": ", 1)
910 if isinstance(name, unicodestr):
911 name = name.encode("ISO-8859-1")
912 if isinstance(value, unicodestr):
913 value = headers.encode(value)
914 h.append((name, value))
916 def check_timeout(self):
917 """If now > self.time + self.timeout, set self.timed_out.
919 This purposefully sets a flag, rather than raising an error,
920 so that a monitor thread can interrupt the Response thread.
922 if time.time() > self.time + self.timeout:
923 self.timed_out = True