OLD | NEW |
(Empty) | |
| 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 header
s |
| 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.enco
de(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 |
| 955 |
| 956 |
OLD | NEW |