Package cherrypy :: Module _cprequest
[hide private]
[frames] | no frames]

Source Code for Module cherrypy._cprequest

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