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