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

Source Code for Module cherrypy._cprequest

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