1import sys 2import time 3import collections 4import operator 5from http.cookies import SimpleCookie, CookieError 6 7import uuid 8 9from more_itertools import consume 10 11import cherrypy 12from cherrypy._cpcompat import ntob 13from cherrypy import _cpreqbody 14from cherrypy._cperror import format_exc, bare_error 15from cherrypy.lib import httputil, reprconf, encoding 16 17 18class Hook(object): 19 20 """A callback and its metadata: failsafe, priority, and kwargs.""" 21 22 callback = None 23 """ 24 The bare callable that this Hook object is wrapping, which will 25 be called when the Hook is called.""" 26 27 failsafe = False 28 """ 29 If True, the callback is guaranteed to run even if other callbacks 30 from the same call point raise exceptions.""" 31 32 priority = 50 33 """ 34 Defines the order of execution for a list of Hooks. Priority numbers 35 should be limited to the closed interval [0, 100], but values outside 36 this range are acceptable, as are fractional values.""" 37 38 kwargs = {} 39 """ 40 A set of keyword arguments that will be passed to the 41 callable on each call.""" 42 43 def __init__(self, callback, failsafe=None, priority=None, **kwargs): 44 self.callback = callback 45 46 if failsafe is None: 47 failsafe = getattr(callback, 'failsafe', False) 48 self.failsafe = failsafe 49 50 if priority is None: 51 priority = getattr(callback, 'priority', 50) 52 self.priority = priority 53 54 self.kwargs = kwargs 55 56 def __lt__(self, other): 57 """ 58 Hooks sort by priority, ascending, such that 59 hooks of lower priority are run first. 60 """ 61 return self.priority < other.priority 62 63 def __call__(self): 64 """Run self.callback(**self.kwargs).""" 65 return self.callback(**self.kwargs) 66 67 def __repr__(self): 68 cls = self.__class__ 69 return ('%s.%s(callback=%r, failsafe=%r, priority=%r, %s)' 70 % (cls.__module__, cls.__name__, self.callback, 71 self.failsafe, self.priority, 72 ', '.join(['%s=%r' % (k, v) 73 for k, v in self.kwargs.items()]))) 74 75 76class HookMap(dict): 77 78 """A map of call points to lists of callbacks (Hook objects).""" 79 80 def __new__(cls, points=None): 81 d = dict.__new__(cls) 82 for p in points or []: 83 d[p] = [] 84 return d 85 86 def __init__(self, *a, **kw): 87 pass 88 89 def attach(self, point, callback, failsafe=None, priority=None, **kwargs): 90 """Append a new Hook made from the supplied arguments.""" 91 self[point].append(Hook(callback, failsafe, priority, **kwargs)) 92 93 def run(self, point): 94 """Execute all registered Hooks (callbacks) for the given point.""" 95 self.run_hooks(iter(sorted(self[point]))) 96 97 @classmethod 98 def run_hooks(cls, hooks): 99 """Execute the indicated hooks, trapping errors. 100 101 Hooks with ``.failsafe == True`` are guaranteed to run 102 even if others at the same hookpoint fail. In this case, 103 log the failure and proceed on to the next hook. The only 104 way to stop all processing from one of these hooks is 105 to raise a BaseException like SystemExit or 106 KeyboardInterrupt and stop the whole server. 107 """ 108 assert isinstance(hooks, collections.abc.Iterator) 109 quiet_errors = ( 110 cherrypy.HTTPError, 111 cherrypy.HTTPRedirect, 112 cherrypy.InternalRedirect, 113 ) 114 safe = filter(operator.attrgetter('failsafe'), hooks) 115 for hook in hooks: 116 try: 117 hook() 118 except quiet_errors: 119 cls.run_hooks(safe) 120 raise 121 except Exception: 122 cherrypy.log(traceback=True, severity=40) 123 cls.run_hooks(safe) 124 raise 125 126 def __copy__(self): 127 newmap = self.__class__() 128 # We can't just use 'update' because we want copies of the 129 # mutable values (each is a list) as well. 130 for k, v in self.items(): 131 newmap[k] = v[:] 132 return newmap 133 copy = __copy__ 134 135 def __repr__(self): 136 cls = self.__class__ 137 return '%s.%s(points=%r)' % ( 138 cls.__module__, 139 cls.__name__, 140 list(self) 141 ) 142 143 144# Config namespace handlers 145 146def hooks_namespace(k, v): 147 """Attach bare hooks declared in config.""" 148 # Use split again to allow multiple hooks for a single 149 # hookpoint per path (e.g. "hooks.before_handler.1"). 150 # Little-known fact you only get from reading source ;) 151 hookpoint = k.split('.', 1)[0] 152 if isinstance(v, str): 153 v = cherrypy.lib.reprconf.attributes(v) 154 if not isinstance(v, Hook): 155 v = Hook(v) 156 cherrypy.serving.request.hooks[hookpoint].append(v) 157 158 159def request_namespace(k, v): 160 """Attach request attributes declared in config.""" 161 # Provides config entries to set request.body attrs (like 162 # attempt_charsets). 163 if k[:5] == 'body.': 164 setattr(cherrypy.serving.request.body, k[5:], v) 165 else: 166 setattr(cherrypy.serving.request, k, v) 167 168 169def response_namespace(k, v): 170 """Attach response attributes declared in config.""" 171 # Provides config entries to set default response headers 172 # http://cherrypy.org/ticket/889 173 if k[:8] == 'headers.': 174 cherrypy.serving.response.headers[k.split('.', 1)[1]] = v 175 else: 176 setattr(cherrypy.serving.response, k, v) 177 178 179def error_page_namespace(k, v): 180 """Attach error pages declared in config.""" 181 if k != 'default': 182 k = int(k) 183 cherrypy.serving.request.error_page[k] = v 184 185 186hookpoints = ['on_start_resource', 'before_request_body', 187 'before_handler', 'before_finalize', 188 'on_end_resource', 'on_end_request', 189 'before_error_response', 'after_error_response'] 190 191 192class Request(object): 193 194 """An HTTP request. 195 196 This object represents the metadata of an HTTP request message; 197 that is, it contains attributes which describe the environment 198 in which the request URL, headers, and body were sent (if you 199 want tools to interpret the headers and body, those are elsewhere, 200 mostly in Tools). This 'metadata' consists of socket data, 201 transport characteristics, and the Request-Line. This object 202 also contains data regarding the configuration in effect for 203 the given URL, and the execution plan for generating a response. 204 """ 205 206 prev = None 207 """ 208 The previous Request object (if any). This should be None 209 unless we are processing an InternalRedirect.""" 210 211 # Conversation/connection attributes 212 local = httputil.Host('127.0.0.1', 80) 213 'An httputil.Host(ip, port, hostname) object for the server socket.' 214 215 remote = httputil.Host('127.0.0.1', 1111) 216 'An httputil.Host(ip, port, hostname) object for the client socket.' 217 218 scheme = 'http' 219 """ 220 The protocol used between client and server. In most cases, 221 this will be either 'http' or 'https'.""" 222 223 server_protocol = 'HTTP/1.1' 224 """ 225 The HTTP version for which the HTTP server is at least 226 conditionally compliant.""" 227 228 base = '' 229 """The (scheme://host) portion of the requested URL. 230 In some cases (e.g. when proxying via mod_rewrite), this may contain 231 path segments which cherrypy.url uses when constructing url's, but 232 which otherwise are ignored by CherryPy. Regardless, this value 233 MUST NOT end in a slash.""" 234 235 # Request-Line attributes 236 request_line = '' 237 """ 238 The complete Request-Line received from the client. This is a 239 single string consisting of the request method, URI, and protocol 240 version (joined by spaces). Any final CRLF is removed.""" 241 242 method = 'GET' 243 """ 244 Indicates the HTTP method to be performed on the resource identified 245 by the Request-URI. Common methods include GET, HEAD, POST, PUT, and 246 DELETE. CherryPy allows any extension method; however, various HTTP 247 servers and gateways may restrict the set of allowable methods. 248 CherryPy applications SHOULD restrict the set (on a per-URI basis).""" 249 250 query_string = '' 251 """ 252 The query component of the Request-URI, a string of information to be 253 interpreted by the resource. The query portion of a URI follows the 254 path component, and is separated by a '?'. For example, the URI 255 'http://www.cherrypy.org/wiki?a=3&b=4' has the query component, 256 'a=3&b=4'.""" 257 258 query_string_encoding = 'utf8' 259 """ 260 The encoding expected for query string arguments after % HEX HEX decoding). 261 If a query string is provided that cannot be decoded with this encoding, 262 404 is raised (since technically it's a different URI). If you want 263 arbitrary encodings to not error, set this to 'Latin-1'; you can then 264 encode back to bytes and re-decode to whatever encoding you like later. 265 """ 266 267 protocol = (1, 1) 268 """The HTTP protocol version corresponding to the set 269 of features which should be allowed in the response. If BOTH 270 the client's request message AND the server's level of HTTP 271 compliance is HTTP/1.1, this attribute will be the tuple (1, 1). 272 If either is 1.0, this attribute will be the tuple (1, 0). 273 Lower HTTP protocol versions are not explicitly supported.""" 274 275 params = {} 276 """ 277 A dict which combines query string (GET) and request entity (POST) 278 variables. This is populated in two stages: GET params are added 279 before the 'on_start_resource' hook, and POST params are added 280 between the 'before_request_body' and 'before_handler' hooks.""" 281 282 # Message attributes 283 header_list = [] 284 """ 285 A list of the HTTP request headers as (name, value) tuples. 286 In general, you should use request.headers (a dict) instead.""" 287 288 headers = httputil.HeaderMap() 289 """ 290 A dict-like object containing the request headers. Keys are header 291 names (in Title-Case format); however, you may get and set them in 292 a case-insensitive manner. That is, headers['Content-Type'] and 293 headers['content-type'] refer to the same value. Values are header 294 values (decoded according to :rfc:`2047` if necessary). See also: 295 httputil.HeaderMap, httputil.HeaderElement.""" 296 297 cookie = SimpleCookie() 298 """See help(Cookie).""" 299 300 rfile = None 301 """ 302 If the request included an entity (body), it will be available 303 as a stream in this attribute. However, the rfile will normally 304 be read for you between the 'before_request_body' hook and the 305 'before_handler' hook, and the resulting string is placed into 306 either request.params or the request.body attribute. 307 308 You may disable the automatic consumption of the rfile by setting 309 request.process_request_body to False, either in config for the desired 310 path, or in an 'on_start_resource' or 'before_request_body' hook. 311 312 WARNING: In almost every case, you should not attempt to read from the 313 rfile stream after CherryPy's automatic mechanism has read it. If you 314 turn off the automatic parsing of rfile, you should read exactly the 315 number of bytes specified in request.headers['Content-Length']. 316 Ignoring either of these warnings may result in a hung request thread 317 or in corruption of the next (pipelined) request. 318 """ 319 320 process_request_body = True 321 """ 322 If True, the rfile (if any) is automatically read and parsed, 323 and the result placed into request.params or request.body.""" 324 325 methods_with_bodies = ('POST', 'PUT', 'PATCH') 326 """ 327 A sequence of HTTP methods for which CherryPy will automatically 328 attempt to read a body from the rfile. If you are going to change 329 this property, modify it on the configuration (recommended) 330 or on the "hook point" `on_start_resource`. 331 """ 332 333 body = None 334 """ 335 If the request Content-Type is 'application/x-www-form-urlencoded' 336 or multipart, this will be None. Otherwise, this will be an instance 337 of :class:`RequestBody<cherrypy._cpreqbody.RequestBody>` (which you 338 can .read()); this value is set between the 'before_request_body' and 339 'before_handler' hooks (assuming that process_request_body is True).""" 340 341 # Dispatch attributes 342 dispatch = cherrypy.dispatch.Dispatcher() 343 """ 344 The object which looks up the 'page handler' callable and collects 345 config for the current request based on the path_info, other 346 request attributes, and the application architecture. The core 347 calls the dispatcher as early as possible, passing it a 'path_info' 348 argument. 349 350 The default dispatcher discovers the page handler by matching path_info 351 to a hierarchical arrangement of objects, starting at request.app.root. 352 See help(cherrypy.dispatch) for more information.""" 353 354 script_name = '' 355 """ 356 The 'mount point' of the application which is handling this request. 357 358 This attribute MUST NOT end in a slash. If the script_name refers to 359 the root of the URI, it MUST be an empty string (not "/"). 360 """ 361 362 path_info = '/' 363 """ 364 The 'relative path' portion of the Request-URI. This is relative 365 to the script_name ('mount point') of the application which is 366 handling this request.""" 367 368 login = None 369 """ 370 When authentication is used during the request processing this is 371 set to 'False' if it failed and to the 'username' value if it succeeded. 372 The default 'None' implies that no authentication happened.""" 373 374 # Note that cherrypy.url uses "if request.app:" to determine whether 375 # the call is during a real HTTP request or not. So leave this None. 376 app = None 377 """The cherrypy.Application object which is handling this request.""" 378 379 handler = None 380 """ 381 The function, method, or other callable which CherryPy will call to 382 produce the response. The discovery of the handler and the arguments 383 it will receive are determined by the request.dispatch object. 384 By default, the handler is discovered by walking a tree of objects 385 starting at request.app.root, and is then passed all HTTP params 386 (from the query string and POST body) as keyword arguments.""" 387 388 toolmaps = {} 389 """ 390 A nested dict of all Toolboxes and Tools in effect for this request, 391 of the form: {Toolbox.namespace: {Tool.name: config dict}}.""" 392 393 config = None 394 """ 395 A flat dict of all configuration entries which apply to the 396 current request. These entries are collected from global config, 397 application config (based on request.path_info), and from handler 398 config (exactly how is governed by the request.dispatch object in 399 effect for this request; by default, handler config can be attached 400 anywhere in the tree between request.app.root and the final handler, 401 and inherits downward).""" 402 403 is_index = None 404 """ 405 This will be True if the current request is mapped to an 'index' 406 resource handler (also, a 'default' handler if path_info ends with 407 a slash). The value may be used to automatically redirect the 408 user-agent to a 'more canonical' URL which either adds or removes 409 the trailing slash. See cherrypy.tools.trailing_slash.""" 410 411 hooks = HookMap(hookpoints) 412 """ 413 A HookMap (dict-like object) of the form: {hookpoint: [hook, ...]}. 414 Each key is a str naming the hook point, and each value is a list 415 of hooks which will be called at that hook point during this request. 416 The list of hooks is generally populated as early as possible (mostly 417 from Tools specified in config), but may be extended at any time. 418 See also: _cprequest.Hook, _cprequest.HookMap, and cherrypy.tools.""" 419 420 error_response = cherrypy.HTTPError(500).set_response 421 """ 422 The no-arg callable which will handle unexpected, untrapped errors 423 during request processing. This is not used for expected exceptions 424 (like NotFound, HTTPError, or HTTPRedirect) which are raised in 425 response to expected conditions (those should be customized either 426 via request.error_page or by overriding HTTPError.set_response). 427 By default, error_response uses HTTPError(500) to return a generic 428 error response to the user-agent.""" 429 430 error_page = {} 431 """ 432 A dict of {error code: response filename or callable} pairs. 433 434 The error code must be an int representing a given HTTP error code, 435 or the string 'default', which will be used if no matching entry 436 is found for a given numeric code. 437 438 If a filename is provided, the file should contain a Python string- 439 formatting template, and can expect by default to receive format 440 values with the mapping keys %(status)s, %(message)s, %(traceback)s, 441 and %(version)s. The set of format mappings can be extended by 442 overriding HTTPError.set_response. 443 444 If a callable is provided, it will be called by default with keyword 445 arguments 'status', 'message', 'traceback', and 'version', as for a 446 string-formatting template. The callable must return a string or 447 iterable of strings which will be set to response.body. It may also 448 override headers or perform any other processing. 449 450 If no entry is given for an error code, and no 'default' entry exists, 451 a default template will be used. 452 """ 453 454 show_tracebacks = True 455 """ 456 If True, unexpected errors encountered during request processing will 457 include a traceback in the response body.""" 458 459 show_mismatched_params = True 460 """ 461 If True, mismatched parameters encountered during PageHandler invocation 462 processing will be included in the response body.""" 463 464 throws = (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect) 465 """The sequence of exceptions which Request.run does not trap.""" 466 467 throw_errors = False 468 """ 469 If True, Request.run will not trap any errors (except HTTPRedirect and 470 HTTPError, which are more properly called 'exceptions', not errors).""" 471 472 closed = False 473 """True once the close method has been called, False otherwise.""" 474 475 stage = None 476 """ 477 A string containing the stage reached in the request-handling process. 478 This is useful when debugging a live server with hung requests.""" 479 480 unique_id = None 481 """A lazy object generating and memorizing UUID4 on ``str()`` render.""" 482 483 namespaces = reprconf.NamespaceSet( 484 **{'hooks': hooks_namespace, 485 'request': request_namespace, 486 'response': response_namespace, 487 'error_page': error_page_namespace, 488 'tools': cherrypy.tools, 489 }) 490 491 def __init__(self, local_host, remote_host, scheme='http', 492 server_protocol='HTTP/1.1'): 493 """Populate a new Request object. 494 495 local_host should be an httputil.Host object with the server info. 496 remote_host should be an httputil.Host object with the client info. 497 scheme should be a string, either "http" or "https". 498 """ 499 self.local = local_host 500 self.remote = remote_host 501 self.scheme = scheme 502 self.server_protocol = server_protocol 503 504 self.closed = False 505 506 # Put a *copy* of the class error_page into self. 507 self.error_page = self.error_page.copy() 508 509 # Put a *copy* of the class namespaces into self. 510 self.namespaces = self.namespaces.copy() 511 512 self.stage = None 513 514 self.unique_id = LazyUUID4() 515 516 def close(self): 517 """Run cleanup code. (Core)""" 518 if not self.closed: 519 self.closed = True 520 self.stage = 'on_end_request' 521 self.hooks.run('on_end_request') 522 self.stage = 'close' 523 524 def run(self, method, path, query_string, req_protocol, headers, rfile): 525 r"""Process the Request. (Core) 526 527 method, path, query_string, and req_protocol should be pulled directly 528 from the Request-Line (e.g. "GET /path?key=val HTTP/1.0"). 529 530 path 531 This should be %XX-unquoted, but query_string should not be. 532 533 When using Python 2, they both MUST be byte strings, 534 not unicode strings. 535 536 When using Python 3, they both MUST be unicode strings, 537 not byte strings, and preferably not bytes \x00-\xFF 538 disguised as unicode. 539 540 headers 541 A list of (name, value) tuples. 542 543 rfile 544 A file-like object containing the HTTP request entity. 545 546 When run() is done, the returned object should have 3 attributes: 547 548 * status, e.g. "200 OK" 549 * header_list, a list of (name, value) tuples 550 * body, an iterable yielding strings 551 552 Consumer code (HTTP servers) should then access these response 553 attributes to build the outbound stream. 554 555 """ 556 response = cherrypy.serving.response 557 self.stage = 'run' 558 try: 559 self.error_response = cherrypy.HTTPError(500).set_response 560 561 self.method = method 562 path = path or '/' 563 self.query_string = query_string or '' 564 self.params = {} 565 566 # Compare request and server HTTP protocol versions, in case our 567 # server does not support the requested protocol. Limit our output 568 # to min(req, server). We want the following output: 569 # request server actual written supported response 570 # protocol protocol response protocol feature set 571 # a 1.0 1.0 1.0 1.0 572 # b 1.0 1.1 1.1 1.0 573 # c 1.1 1.0 1.0 1.0 574 # d 1.1 1.1 1.1 1.1 575 # Notice that, in (b), the response will be "HTTP/1.1" even though 576 # the client only understands 1.0. RFC 2616 10.5.6 says we should 577 # only return 505 if the _major_ version is different. 578 rp = int(req_protocol[5]), int(req_protocol[7]) 579 sp = int(self.server_protocol[5]), int(self.server_protocol[7]) 580 self.protocol = min(rp, sp) 581 response.headers.protocol = self.protocol 582 583 # Rebuild first line of the request (e.g. "GET /path HTTP/1.0"). 584 url = path 585 if query_string: 586 url += '?' + query_string 587 self.request_line = '%s %s %s' % (method, url, req_protocol) 588 589 self.header_list = list(headers) 590 self.headers = httputil.HeaderMap() 591 592 self.rfile = rfile 593 self.body = None 594 595 self.cookie = SimpleCookie() 596 self.handler = None 597 598 # path_info should be the path from the 599 # app root (script_name) to the handler. 600 self.script_name = self.app.script_name 601 self.path_info = pi = path[len(self.script_name):] 602 603 self.stage = 'respond' 604 self.respond(pi) 605 606 except self.throws: 607 raise 608 except Exception: 609 if self.throw_errors: 610 raise 611 else: 612 # Failure in setup, error handler or finalize. Bypass them. 613 # Can't use handle_error because we may not have hooks yet. 614 cherrypy.log(traceback=True, severity=40) 615 if self.show_tracebacks: 616 body = format_exc() 617 else: 618 body = '' 619 r = bare_error(body) 620 response.output_status, response.header_list, response.body = r 621 622 if self.method == 'HEAD': 623 # HEAD requests MUST NOT return a message-body in the response. 624 response.body = [] 625 626 try: 627 cherrypy.log.access() 628 except Exception: 629 cherrypy.log.error(traceback=True) 630 631 return response 632 633 def respond(self, path_info): 634 """Generate a response for the resource at self.path_info. (Core)""" 635 try: 636 try: 637 try: 638 self._do_respond(path_info) 639 except (cherrypy.HTTPRedirect, cherrypy.HTTPError): 640 inst = sys.exc_info()[1] 641 inst.set_response() 642 self.stage = 'before_finalize (HTTPError)' 643 self.hooks.run('before_finalize') 644 cherrypy.serving.response.finalize() 645 finally: 646 self.stage = 'on_end_resource' 647 self.hooks.run('on_end_resource') 648 except self.throws: 649 raise 650 except Exception: 651 if self.throw_errors: 652 raise 653 self.handle_error() 654 655 def _do_respond(self, path_info): 656 response = cherrypy.serving.response 657 658 if self.app is None: 659 raise cherrypy.NotFound() 660 661 self.hooks = self.__class__.hooks.copy() 662 self.toolmaps = {} 663 664 # Get the 'Host' header, so we can HTTPRedirect properly. 665 self.stage = 'process_headers' 666 self.process_headers() 667 668 self.stage = 'get_resource' 669 self.get_resource(path_info) 670 671 self.body = _cpreqbody.RequestBody( 672 self.rfile, self.headers, request_params=self.params) 673 674 self.namespaces(self.config) 675 676 self.stage = 'on_start_resource' 677 self.hooks.run('on_start_resource') 678 679 # Parse the querystring 680 self.stage = 'process_query_string' 681 self.process_query_string() 682 683 # Process the body 684 if self.process_request_body: 685 if self.method not in self.methods_with_bodies: 686 self.process_request_body = False 687 self.stage = 'before_request_body' 688 self.hooks.run('before_request_body') 689 if self.process_request_body: 690 self.body.process() 691 692 # Run the handler 693 self.stage = 'before_handler' 694 self.hooks.run('before_handler') 695 if self.handler: 696 self.stage = 'handler' 697 response.body = self.handler() 698 699 # Finalize 700 self.stage = 'before_finalize' 701 self.hooks.run('before_finalize') 702 response.finalize() 703 704 def process_query_string(self): 705 """Parse the query string into Python structures. (Core)""" 706 try: 707 p = httputil.parse_query_string( 708 self.query_string, encoding=self.query_string_encoding) 709 except UnicodeDecodeError: 710 raise cherrypy.HTTPError( 711 404, 'The given query string could not be processed. Query ' 712 'strings for this resource must be encoded with %r.' % 713 self.query_string_encoding) 714 715 self.params.update(p) 716 717 def process_headers(self): 718 """Parse HTTP header data into Python structures. (Core)""" 719 # Process the headers into self.headers 720 headers = self.headers 721 for name, value in self.header_list: 722 # Call title() now (and use dict.__method__(headers)) 723 # so title doesn't have to be called twice. 724 name = name.title() 725 value = value.strip() 726 727 headers[name] = httputil.decode_TEXT_maybe(value) 728 729 # Some clients, notably Konquoror, supply multiple 730 # cookies on different lines with the same key. To 731 # handle this case, store all cookies in self.cookie. 732 if name == 'Cookie': 733 try: 734 self.cookie.load(value) 735 except CookieError as exc: 736 raise cherrypy.HTTPError(400, str(exc)) 737 738 if not dict.__contains__(headers, 'Host'): 739 # All Internet-based HTTP/1.1 servers MUST respond with a 400 740 # (Bad Request) status code to any HTTP/1.1 request message 741 # which lacks a Host header field. 742 if self.protocol >= (1, 1): 743 msg = "HTTP/1.1 requires a 'Host' request header." 744 raise cherrypy.HTTPError(400, msg) 745 host = dict.get(headers, 'Host') 746 if not host: 747 host = self.local.name or self.local.ip 748 self.base = '%s://%s' % (self.scheme, host) 749 750 def get_resource(self, path): 751 """Call a dispatcher (which sets self.handler and .config). (Core)""" 752 # First, see if there is a custom dispatch at this URI. Custom 753 # dispatchers can only be specified in app.config, not in _cp_config 754 # (since custom dispatchers may not even have an app.root). 755 dispatch = self.app.find_config( 756 path, 'request.dispatch', self.dispatch) 757 758 # dispatch() should set self.handler and self.config 759 dispatch(path) 760 761 def handle_error(self): 762 """Handle the last unanticipated exception. (Core)""" 763 try: 764 self.hooks.run('before_error_response') 765 if self.error_response: 766 self.error_response() 767 self.hooks.run('after_error_response') 768 cherrypy.serving.response.finalize() 769 except cherrypy.HTTPRedirect: 770 inst = sys.exc_info()[1] 771 inst.set_response() 772 cherrypy.serving.response.finalize() 773 774 775class ResponseBody(object): 776 777 """The body of the HTTP response (the response entity).""" 778 779 unicode_err = ('Page handlers MUST return bytes. Use tools.encode ' 780 'if you wish to return unicode.') 781 782 def __get__(self, obj, objclass=None): 783 if obj is None: 784 # When calling on the class instead of an instance... 785 return self 786 else: 787 return obj._body 788 789 def __set__(self, obj, value): 790 # Convert the given value to an iterable object. 791 if isinstance(value, str): 792 raise ValueError(self.unicode_err) 793 elif isinstance(value, list): 794 # every item in a list must be bytes... 795 if any(isinstance(item, str) for item in value): 796 raise ValueError(self.unicode_err) 797 798 obj._body = encoding.prepare_iter(value) 799 800 801class Response(object): 802 803 """An HTTP Response, including status, headers, and body.""" 804 805 status = '' 806 """The HTTP Status-Code and Reason-Phrase.""" 807 808 header_list = [] 809 """ 810 A list of the HTTP response headers as (name, value) tuples. 811 In general, you should use response.headers (a dict) instead. This 812 attribute is generated from response.headers and is not valid until 813 after the finalize phase.""" 814 815 headers = httputil.HeaderMap() 816 """ 817 A dict-like object containing the response headers. Keys are header 818 names (in Title-Case format); however, you may get and set them in 819 a case-insensitive manner. That is, headers['Content-Type'] and 820 headers['content-type'] refer to the same value. Values are header 821 values (decoded according to :rfc:`2047` if necessary). 822 823 .. seealso:: classes :class:`HeaderMap`, :class:`HeaderElement` 824 """ 825 826 cookie = SimpleCookie() 827 """See help(Cookie).""" 828 829 body = ResponseBody() 830 """The body (entity) of the HTTP response.""" 831 832 time = None 833 """The value of time.time() when created. Use in HTTP dates.""" 834 835 stream = False 836 """If False, buffer the response body.""" 837 838 def __init__(self): 839 self.status = None 840 self.header_list = None 841 self._body = [] 842 self.time = time.time() 843 844 self.headers = httputil.HeaderMap() 845 # Since we know all our keys are titled strings, we can 846 # bypass HeaderMap.update and get a big speed boost. 847 dict.update(self.headers, { 848 'Content-Type': 'text/html', 849 'Server': 'CherryPy/' + cherrypy.__version__, 850 'Date': httputil.HTTPDate(self.time), 851 }) 852 self.cookie = SimpleCookie() 853 854 def collapse_body(self): 855 """Collapse self.body to a single string; replace it and return it.""" 856 new_body = b''.join(self.body) 857 self.body = new_body 858 return new_body 859 860 def _flush_body(self): 861 """ 862 Discard self.body but consume any generator such that 863 any finalization can occur, such as is required by 864 caching.tee_output(). 865 """ 866 consume(iter(self.body)) 867 868 def finalize(self): 869 """Transform headers (and cookies) into self.header_list. (Core)""" 870 try: 871 code, reason, _ = httputil.valid_status(self.status) 872 except ValueError: 873 raise cherrypy.HTTPError(500, sys.exc_info()[1].args[0]) 874 875 headers = self.headers 876 877 self.status = '%s %s' % (code, reason) 878 self.output_status = ntob(str(code), 'ascii') + \ 879 b' ' + headers.encode(reason) 880 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._flush_body() 893 self.body = b'' 894 else: 895 # Responses which are not streamed should have a Content-Length, 896 # but allow user code to set Content-Length if desired. 897 if dict.get(headers, 'Content-Length') is None: 898 content = self.collapse_body() 899 dict.__setitem__(headers, 'Content-Length', len(content)) 900 901 # Transform our header dict into a list of tuples. 902 self.header_list = h = headers.output() 903 904 cookie = self.cookie.output() 905 if cookie: 906 for line in cookie.split('\r\n'): 907 name, value = line.split(': ', 1) 908 if isinstance(name, str): 909 name = name.encode('ISO-8859-1') 910 if isinstance(value, str): 911 value = headers.encode(value) 912 h.append((name, value)) 913 914 915class LazyUUID4(object): 916 def __str__(self): 917 """Return UUID4 and keep it for future calls.""" 918 return str(self.uuid4) 919 920 @property 921 def uuid4(self): 922 """Provide unique id on per-request basis using UUID4. 923 924 It's evaluated lazily on render. 925 """ 926 try: 927 self._uuid4 928 except AttributeError: 929 # evaluate on first access 930 self._uuid4 = uuid.uuid4() 931 932 return self._uuid4 933