| 177 | | |
|---|
| 178 | | request = cherrypy.request |
|---|
| 179 | | request.method = "" |
|---|
| 180 | | request.requestLine = requestLine.strip() |
|---|
| 181 | | self.parseFirstLine() |
|---|
| 182 | | |
|---|
| 183 | | # Prepare cherrypy.request variables |
|---|
| 184 | | request.remoteAddr = clientAddress[0] |
|---|
| 185 | | request.remotePort = clientAddress[1] |
|---|
| 186 | | request.remoteHost = remoteHost |
|---|
| 187 | | request.paramList = [] # Only used for Xml-Rpc |
|---|
| 188 | | request.headers = headers |
|---|
| 189 | | request.headerMap = KeyTitlingDict() |
|---|
| 190 | | request.simpleCookie = Cookie.SimpleCookie() |
|---|
| 191 | | request.rfile = rfile |
|---|
| 192 | | request.scheme = scheme |
|---|
| 193 | | |
|---|
| 194 | | # Prepare cherrypy.response variables |
|---|
| 195 | | cherrypy.response.status = None |
|---|
| 196 | | cherrypy.response.headers = None |
|---|
| 197 | | cherrypy.response.body = None |
|---|
| 198 | | |
|---|
| 199 | | cherrypy.response.headerMap = KeyTitlingDict() |
|---|
| 200 | | cherrypy.response.headerMap.update({ |
|---|
| 201 | | "Content-Type": "text/html", |
|---|
| 202 | | "Server": "CherryPy/" + cherrypy.__version__, |
|---|
| 203 | | "Date": cptools.HTTPDate(), |
|---|
| 204 | | "Set-Cookie": [], |
|---|
| 205 | | "Content-Length": None |
|---|
| 206 | | }) |
|---|
| 207 | | cherrypy.response.simpleCookie = Cookie.SimpleCookie() |
|---|
| 208 | | |
|---|
| 209 | | self.run() |
|---|
| 210 | | |
|---|
| 211 | | if request.method == "HEAD": |
|---|
| | 185 | if cherrypy.profiler: |
|---|
| | 186 | cherrypy.profiler.run(self._run, requestLine, headers, rfile) |
|---|
| | 187 | else: |
|---|
| | 188 | self._run(requestLine, headers, rfile) |
|---|
| | 189 | return cherrypy.response |
|---|
| | 190 | |
|---|
| | 191 | def _run(self, requestLine, headers, rfile): |
|---|
| | 192 | |
|---|
| | 193 | try: |
|---|
| | 194 | self.paramList = [] # Only used for Xml-Rpc |
|---|
| | 195 | self.headers = headers |
|---|
| | 196 | self.headerMap = KeyTitlingDict() |
|---|
| | 197 | self.simpleCookie = Cookie.SimpleCookie() |
|---|
| | 198 | |
|---|
| | 199 | self.rfile = rfile |
|---|
| | 200 | |
|---|
| | 201 | # This has to be done very early in the request process, |
|---|
| | 202 | # because request.path is used for config lookups right away. |
|---|
| | 203 | self.processRequestLine(requestLine) |
|---|
| | 204 | |
|---|
| | 205 | try: |
|---|
| | 206 | applyFilters('onStartResource') |
|---|
| | 207 | |
|---|
| | 208 | try: |
|---|
| | 209 | self.processHeaders() |
|---|
| | 210 | |
|---|
| | 211 | applyFilters('beforeRequestBody') |
|---|
| | 212 | if self.processRequestBody: |
|---|
| | 213 | self.processBody() |
|---|
| | 214 | |
|---|
| | 215 | applyFilters('beforeMain') |
|---|
| | 216 | if cherrypy.response.body is None: |
|---|
| | 217 | self.main() |
|---|
| | 218 | |
|---|
| | 219 | applyFilters('beforeFinalize') |
|---|
| | 220 | cherrypy.response.finalize() |
|---|
| | 221 | except cherrypy.RequestHandled: |
|---|
| | 222 | pass |
|---|
| | 223 | except (cherrypy.HTTPRedirect, cherrypy.HTTPError), inst: |
|---|
| | 224 | # For an HTTPRedirect or HTTPError (including NotFound), |
|---|
| | 225 | # we don't go through the regular mechanism: |
|---|
| | 226 | # we return the redirect or error page immediately |
|---|
| | 227 | inst.set_response() |
|---|
| | 228 | applyFilters('beforeFinalize') |
|---|
| | 229 | cherrypy.response.finalize() |
|---|
| | 230 | finally: |
|---|
| | 231 | applyFilters('onEndResource') |
|---|
| | 232 | except (KeyboardInterrupt, SystemExit): |
|---|
| | 233 | raise |
|---|
| | 234 | except: |
|---|
| | 235 | cherrypy.response.handleError(sys.exc_info()) |
|---|
| | 236 | |
|---|
| | 237 | if self.method == "HEAD": |
|---|
| 405 | | request.paramMap[key] = value |
|---|
| 406 | | |
|---|
| 407 | | |
|---|
| 408 | | # Error handling |
|---|
| 409 | | |
|---|
| 410 | | dbltrace = """ |
|---|
| 411 | | =====First Error===== |
|---|
| 412 | | |
|---|
| 413 | | %s |
|---|
| 414 | | |
|---|
| 415 | | =====Second Error===== |
|---|
| 416 | | |
|---|
| 417 | | %s |
|---|
| 418 | | |
|---|
| 419 | | """ |
|---|
| 420 | | |
|---|
| 421 | | def handleError(exc): |
|---|
| 422 | | """Set status, headers, and body when an unanticipated error occurs.""" |
|---|
| 423 | | try: |
|---|
| 424 | | applyFilters('beforeErrorResponse') |
|---|
| 425 | | |
|---|
| 426 | | # _cpOnError will probably change cherrypy.response.body. |
|---|
| 427 | | # It may also change the headerMap, etc. |
|---|
| 428 | | _cputil.getSpecialAttribute('_cpOnError')() |
|---|
| 429 | | |
|---|
| 430 | | finalize() |
|---|
| 431 | | |
|---|
| 432 | | applyFilters('afterErrorResponse') |
|---|
| 433 | | return |
|---|
| 434 | | except (cherrypy.HTTPRedirect, cherrypy.HTTPError), inst: |
|---|
| | 389 | self.paramMap[key] = value |
|---|
| | 390 | |
|---|
| | 391 | def main(self, path=None): |
|---|
| | 392 | """Obtain and set cherrypy.response.body from a page handler.""" |
|---|
| | 393 | if path is None: |
|---|
| | 394 | path = self.objectPath or self.path |
|---|
| | 395 | |
|---|
| | 396 | while True: |
|---|
| | 397 | try: |
|---|
| | 398 | page_handler, object_path, virtual_path = self.mapPathToObject(path) |
|---|
| | 399 | |
|---|
| | 400 | # Remove "root" from object_path and join it to get objectPath |
|---|
| | 401 | self.objectPath = '/' + '/'.join(object_path[1:]) |
|---|
| | 402 | args = virtual_path + self.paramList |
|---|
| | 403 | body = page_handler(*args, **self.paramMap) |
|---|
| | 404 | cherrypy.response.body = iterable(body) |
|---|
| | 405 | return |
|---|
| | 406 | except cherrypy.InternalRedirect, x: |
|---|
| | 407 | # Try again with the new path |
|---|
| | 408 | path = x.path |
|---|
| | 409 | |
|---|
| | 410 | def mapPathToObject(self, path): |
|---|
| | 411 | """For path, return the corresponding exposed callable (or raise NotFound). |
|---|
| | 412 | |
|---|
| | 413 | path should be a "relative" URL path, like "/app/a/b/c". Leading and |
|---|
| | 414 | trailing slashes are ignored. |
|---|
| | 415 | |
|---|
| | 416 | Traverse path: |
|---|
| | 417 | for /a/b?arg=val, we'll try: |
|---|
| | 418 | root.a.b.index -> redirect to /a/b/?arg=val |
|---|
| | 419 | root.a.b.default(arg='val') -> redirect to /a/b/?arg=val |
|---|
| | 420 | root.a.b(arg='val') |
|---|
| | 421 | root.a.default('b', arg='val') |
|---|
| | 422 | root.default('a', 'b', arg='val') |
|---|
| | 423 | |
|---|
| | 424 | The target method must have an ".exposed = True" attribute. |
|---|
| | 425 | |
|---|
| | 426 | """ |
|---|
| | 427 | |
|---|
| | 428 | # Remove leading and trailing slash |
|---|
| | 429 | tpath = path.strip("/") |
|---|
| | 430 | |
|---|
| | 431 | if not tpath: |
|---|
| | 432 | objectPathList = [] |
|---|
| | 433 | else: |
|---|
| | 434 | objectPathList = tpath.split('/') |
|---|
| | 435 | if objectPathList == ['global']: |
|---|
| | 436 | objectPathList = ['_global'] |
|---|
| | 437 | objectPathList = ['root'] + objectPathList + ['index'] |
|---|
| | 438 | |
|---|
| | 439 | if getattr(cherrypy, "debug", None): |
|---|
| | 440 | cherrypy.log(" Attempting to map path: %s using %s" |
|---|
| | 441 | % (tpath, objectPathList), "DEBUG") |
|---|
| | 442 | |
|---|
| | 443 | # Try successive objects... (and also keep the remaining object list) |
|---|
| | 444 | isFirst = True |
|---|
| | 445 | isSecond = False |
|---|
| | 446 | foundIt = False |
|---|
| | 447 | virtualPathList = [] |
|---|
| | 448 | while objectPathList: |
|---|
| | 449 | if isFirst or isSecond: |
|---|
| | 450 | # Only try this for a.b.index() or a.b() |
|---|
| | 451 | candidate = self.getObjFromPath(objectPathList) |
|---|
| | 452 | if callable(candidate) and getattr(candidate, 'exposed', False): |
|---|
| | 453 | foundIt = True |
|---|
| | 454 | break |
|---|
| | 455 | # Couldn't find the object: pop one from the list and try "default" |
|---|
| | 456 | lastObj = objectPathList.pop() |
|---|
| | 457 | if (not isFirst) or (not tpath): |
|---|
| | 458 | virtualPathList.insert(0, lastObj) |
|---|
| | 459 | objectPathList.append('default') |
|---|
| | 460 | candidate = self.getObjFromPath(objectPathList) |
|---|
| | 461 | if callable(candidate) and getattr(candidate, 'exposed', False): |
|---|
| | 462 | foundIt = True |
|---|
| | 463 | break |
|---|
| | 464 | objectPathList.pop() # Remove "default" |
|---|
| | 465 | if isSecond: |
|---|
| | 466 | isSecond = False |
|---|
| | 467 | if isFirst: |
|---|
| | 468 | isFirst = False |
|---|
| | 469 | isSecond = True |
|---|
| | 470 | |
|---|
| | 471 | # Check results of traversal |
|---|
| | 472 | if not foundIt: |
|---|
| | 473 | if tpath.endswith("favicon.ico"): |
|---|
| | 474 | # Use CherryPy's default favicon.ico. If developers really, |
|---|
| | 475 | # really want no favicon, they can make a dummy method |
|---|
| | 476 | # that raises NotFound. |
|---|
| | 477 | icofile = os.path.join(os.path.dirname(__file__), "favicon.ico") |
|---|
| | 478 | cptools.serveFile(icofile) |
|---|
| | 479 | applyFilters('beforeFinalize') |
|---|
| | 480 | cherrypy.response.finalize() |
|---|
| | 481 | raise cherrypy.RequestHandled() |
|---|
| | 482 | else: |
|---|
| | 483 | # We didn't find anything |
|---|
| | 484 | if getattr(cherrypy, "debug", None): |
|---|
| | 485 | cherrypy.log(" NOT FOUND", "DEBUG") |
|---|
| | 486 | raise cherrypy.NotFound(path) |
|---|
| | 487 | |
|---|
| | 488 | if isFirst: |
|---|
| | 489 | # We found the extra ".index" |
|---|
| | 490 | # Check if the original path had a trailing slash (otherwise, do |
|---|
| | 491 | # a redirect) |
|---|
| | 492 | if path[-1] != '/': |
|---|
| | 493 | atoms = self.browserUrl.split("?", 1) |
|---|
| | 494 | newUrl = atoms.pop(0) + '/' |
|---|
| | 495 | if atoms: |
|---|
| | 496 | newUrl += "?" + atoms[0] |
|---|
| | 497 | if getattr(cherrypy, "debug", None): |
|---|
| | 498 | cherrypy.log(" Found: redirecting to %s" % newUrl, "DEBUG") |
|---|
| | 499 | raise cherrypy.HTTPRedirect(newUrl) |
|---|
| | 500 | |
|---|
| | 501 | if getattr(cherrypy, "debug", None): |
|---|
| | 502 | cherrypy.log(" Found: %s" % candidate, "DEBUG") |
|---|
| | 503 | return candidate, objectPathList, virtualPathList |
|---|
| | 504 | |
|---|
| | 505 | def getObjFromPath(self, objPathList): |
|---|
| | 506 | """For a given objectPathList, return the object (or None). |
|---|
| | 507 | |
|---|
| | 508 | objPathList should be a list of the form: ['root', 'a', 'b', 'index']. |
|---|
| | 509 | """ |
|---|
| | 510 | |
|---|
| | 511 | root = cherrypy |
|---|
| | 512 | for objname in objPathList: |
|---|
| | 513 | # maps virtual filenames to Python identifiers (substitutes '.' for '_') |
|---|
| | 514 | objname = objname.replace('.', '_') |
|---|
| | 515 | if getattr(cherrypy, "debug", None): |
|---|
| | 516 | cherrypy.log(" Trying: %s.%s" % (root, objname), "DEBUG") |
|---|
| | 517 | root = getattr(root, objname, None) |
|---|
| | 518 | if root is None: |
|---|
| | 519 | return None |
|---|
| | 520 | return root |
|---|
| | 521 | |
|---|
| | 522 | |
|---|
| | 523 | general_header_fields = ["Cache-Control", "Connection", "Date", "Pragma", |
|---|
| | 524 | "Trailer", "Transfer-Encoding", "Upgrade", "Via", |
|---|
| | 525 | "Warning"] |
|---|
| | 526 | response_header_fields = ["Accept-Ranges", "Age", "ETag", "Location", |
|---|
| | 527 | "Proxy-Authenticate", "Retry-After", "Server", |
|---|
| | 528 | "Vary", "WWW-Authenticate"] |
|---|
| | 529 | entity_header_fields = ["Allow", "Content-Encoding", "Content-Language", |
|---|
| | 530 | "Content-Length", "Content-Location", "Content-MD5", |
|---|
| | 531 | "Content-Range", "Content-Type", "Expires", |
|---|
| | 532 | "Last-Modified"] |
|---|
| | 533 | |
|---|
| | 534 | _header_order_map = {} |
|---|
| | 535 | for _ in general_header_fields: |
|---|
| | 536 | _header_order_map[_] = 0 |
|---|
| | 537 | for _ in response_header_fields: |
|---|
| | 538 | _header_order_map[_] = 1 |
|---|
| | 539 | for _ in entity_header_fields: |
|---|
| | 540 | _header_order_map[_] = 2 |
|---|
| | 541 | |
|---|
| | 542 | _ie_friendly_error_sizes = {400: 512, 403: 256, 404: 512, 405: 256, |
|---|
| | 543 | 406: 512, 408: 512, 409: 512, 410: 256, |
|---|
| | 544 | 500: 512, 501: 512, 505: 512, |
|---|
| | 545 | } |
|---|
| | 546 | |
|---|
| | 547 | |
|---|
| | 548 | class Response(object): |
|---|
| | 549 | """An HTTP Response.""" |
|---|
| | 550 | |
|---|
| | 551 | def __init__(self): |
|---|
| | 552 | self.status = None |
|---|
| | 553 | self.headers = None |
|---|
| | 554 | self.body = None |
|---|
| | 555 | |
|---|
| | 556 | self.headerMap = KeyTitlingDict() |
|---|
| | 557 | self.headerMap.update({ |
|---|
| | 558 | "Content-Type": "text/html", |
|---|
| | 559 | "Server": "CherryPy/" + cherrypy.__version__, |
|---|
| | 560 | "Date": cptools.HTTPDate(), |
|---|
| | 561 | "Set-Cookie": [], |
|---|
| | 562 | "Content-Length": None |
|---|
| | 563 | }) |
|---|
| | 564 | self.simpleCookie = Cookie.SimpleCookie() |
|---|
| | 565 | |
|---|
| | 566 | def finalize(self): |
|---|
| | 567 | """Transform headerMap (and cookies) into cherrypy.response.headers.""" |
|---|
| | 568 | |
|---|
| | 569 | code, reason, _ = cptools.validStatus(self.status) |
|---|
| | 570 | self.status = "%s %s" % (code, reason) |
|---|
| | 571 | |
|---|
| | 572 | if self.body is None: |
|---|
| | 573 | self.body = [] |
|---|
| | 574 | |
|---|
| | 575 | stream = cherrypy.config.get("streamResponse", False) |
|---|
| | 576 | # OPTIONS requests MUST include a Content-Length of 0 if no body. |
|---|
| | 577 | # Just punt and figure Content-Length for all OPTIONS requests. |
|---|
| | 578 | if cherrypy.request.method == "OPTIONS": |
|---|
| | 579 | stream = False |
|---|
| | 580 | |
|---|
| | 581 | if stream: |
|---|
| | 582 | try: |
|---|
| | 583 | del self.headerMap['Content-Length'] |
|---|
| | 584 | except KeyError: |
|---|
| | 585 | pass |
|---|
| | 586 | else: |
|---|
| | 587 | # Responses which are not streamed should have a Content-Length, |
|---|
| | 588 | # but allow user code to set Content-Length if desired. |
|---|
| | 589 | if self.headerMap.get('Content-Length') is None: |
|---|
| | 590 | content = ''.join([chunk for chunk in self.body]) |
|---|
| | 591 | self.body = [content] |
|---|
| | 592 | self.headerMap['Content-Length'] = len(content) |
|---|
| | 593 | |
|---|
| | 594 | # For some statuses, Internet Explorer 5+ shows "friendly error messages" |
|---|
| | 595 | # instead of our response.body if the body is smaller than a given size. |
|---|
| | 596 | # Fix this by returning a body over that size (by adding whitespace). |
|---|
| | 597 | # See http://support.microsoft.com/kb/q218155/ |
|---|
| | 598 | s = int(self.status.split(" ")[0]) |
|---|
| | 599 | s = _ie_friendly_error_sizes.get(s, 0) |
|---|
| | 600 | if s: |
|---|
| | 601 | s += 1 |
|---|
| | 602 | # Since we are issuing an HTTP error status, we assume that |
|---|
| | 603 | # the entity is short, and we should just collapse it. |
|---|
| | 604 | content = ''.join([chunk for chunk in self.body]) |
|---|
| | 605 | self.body = [content] |
|---|
| | 606 | l = len(content) |
|---|
| | 607 | if l and l < s: |
|---|
| | 608 | # IN ADDITION: the response must be written to IE |
|---|
| | 609 | # in one chunk or it will still get replaced! Bah. |
|---|
| | 610 | self.body = [self.body[0] + (" " * (s - l))] |
|---|
| | 611 | self.headerMap['Content-Length'] = s |
|---|
| | 612 | |
|---|
| | 613 | # Headers |
|---|
| | 614 | headers = [] |
|---|
| | 615 | for key, valueList in self.headerMap.iteritems(): |
|---|
| | 616 | order = _header_order_map.get(key, 3) |
|---|
| | 617 | if not isinstance(valueList, list): |
|---|
| | 618 | valueList = [valueList] |
|---|
| | 619 | for value in valueList: |
|---|
| | 620 | headers.append((order, (key, str(value)))) |
|---|
| | 621 | # RFC 2616: '... it is "good practice" to send general-header fields |
|---|
| | 622 | # first, followed by request-header or response-header fields, and |
|---|
| | 623 | # ending with the entity-header fields.' |
|---|
| | 624 | headers.sort() |
|---|
| | 625 | self.headers = [item[1] for item in headers] |
|---|
| | 626 | |
|---|
| | 627 | cookie = self.simpleCookie.output() |
|---|
| | 628 | if cookie: |
|---|
| | 629 | lines = cookie.split("\n") |
|---|
| | 630 | for line in lines: |
|---|
| | 631 | name, value = line.split(": ", 1) |
|---|
| | 632 | self.headers.append((name, value)) |
|---|
| | 633 | |
|---|
| | 634 | dbltrace = "\n===First Error===\n\n%s\n\n===Second Error===\n\n%s\n\n" |
|---|
| | 635 | |
|---|
| | 636 | def handleError(self, exc): |
|---|
| | 637 | """Set status, headers, and body when an unanticipated error occurs.""" |
|---|
| 444 | | except (KeyboardInterrupt, SystemExit): |
|---|
| 445 | | raise |
|---|
| 446 | | except: |
|---|
| 447 | | # Fall through to the second error handler |
|---|
| 448 | | pass |
|---|
| 449 | | |
|---|
| 450 | | # Failure in _cpOnError, error filter, or finalize. |
|---|
| 451 | | # Bypass them all. |
|---|
| 452 | | defaultOn = (cherrypy.config.get('server.environment') == 'development') |
|---|
| 453 | | if cherrypy.config.get('server.showTracebacks', defaultOn): |
|---|
| 454 | | body = dbltrace % (_cputil.formatExc(exc), _cputil.formatExc()) |
|---|
| 455 | | else: |
|---|
| 456 | | body = "" |
|---|
| 457 | | response = cherrypy.response |
|---|
| 458 | | response.status, response.headers, response.body = bareError(body) |
|---|
| 459 | | |
|---|
| 460 | | def bareError(extrabody=None): |
|---|
| 461 | | """Produce status, headers, body for a critical error. |
|---|
| 462 | | |
|---|
| 463 | | Returns a triple without calling any other questionable functions, |
|---|
| 464 | | so it should be as error-free as possible. Call it from an HTTP server |
|---|
| 465 | | if you get errors after Request() is done. |
|---|
| 466 | | |
|---|
| 467 | | If extrabody is None, a friendly but rather unhelpful error message |
|---|
| 468 | | is set in the body. If extrabody is a string, it will be appended |
|---|
| 469 | | as-is to the body. |
|---|
| 470 | | """ |
|---|
| 471 | | |
|---|
| 472 | | # The whole point of this function is to be a last line-of-defense |
|---|
| 473 | | # in handling errors. That is, it must not raise any errors itself; |
|---|
| 474 | | # it cannot be allowed to fail. Therefore, don't add to it! |
|---|
| 475 | | # In particular, don't call any other CP functions. |
|---|
| 476 | | |
|---|
| 477 | | body = "Unrecoverable error in the server." |
|---|
| 478 | | if extrabody is not None: |
|---|
| 479 | | body += "\n" + extrabody |
|---|
| 480 | | |
|---|
| 481 | | return ("500 Internal Server Error", |
|---|
| 482 | | [('Content-Type', 'text/plain'), |
|---|
| 483 | | ('Content-Length', str(len(body)))], |
|---|
| 484 | | [body]) |
|---|
| 485 | | |
|---|
| 486 | | |
|---|
| 487 | | |
|---|
| 488 | | # Response functions |
|---|
| 489 | | |
|---|
| 490 | | def main(path=None): |
|---|
| 491 | | """Obtain and set cherrypy.response.body from a page handler.""" |
|---|
| 492 | | if path is None: |
|---|
| 493 | | path = cherrypy.request.objectPath or cherrypy.request.path |
|---|
| 494 | | |
|---|
| 495 | | while True: |
|---|
| 496 | | try: |
|---|
| 497 | | page_handler, object_path, virtual_path = mapPathToObject(path) |
|---|
| 498 | | |
|---|
| 499 | | # Remove "root" from object_path and join it to get objectPath |
|---|
| 500 | | cherrypy.request.objectPath = '/' + '/'.join(object_path[1:]) |
|---|
| 501 | | args = virtual_path + cherrypy.request.paramList |
|---|
| 502 | | body = page_handler(*args, **cherrypy.request.paramMap) |
|---|
| 503 | | cherrypy.response.body = iterable(body) |
|---|
| 504 | | return |
|---|
| 505 | | except cherrypy.InternalRedirect, x: |
|---|
| 506 | | # Try again with the new path |
|---|
| 507 | | path = x.path |
|---|
| | 664 | |
|---|
| | 665 | # Failure in _cpOnError, error filter, or finalize. |
|---|
| | 666 | # Bypass them all. |
|---|
| | 667 | defaultOn = (cherrypy.config.get('server.environment') == 'development') |
|---|
| | 668 | if cherrypy.config.get('server.showTracebacks', defaultOn): |
|---|
| | 669 | body = self.dbltrace % (_cputil.formatExc(exc), |
|---|
| | 670 | _cputil.formatExc()) |
|---|
| | 671 | else: |
|---|
| | 672 | body = "" |
|---|
| | 673 | self.setBareError(body) |
|---|
| | 674 | |
|---|
| | 675 | def setBareError(self, body=None): |
|---|
| | 676 | self.status, self.headers, self.body = _cputil.bareError(body) |
|---|
| | 677 | |
|---|
| 524 | | |
|---|
| 525 | | general_header_fields = ["Cache-Control", "Connection", "Date", "Pragma", |
|---|
| 526 | | "Trailer", "Transfer-Encoding", "Upgrade", "Via", |
|---|
| 527 | | "Warning"] |
|---|
| 528 | | response_header_fields = ["Accept-Ranges", "Age", "ETag", "Location", |
|---|
| 529 | | "Proxy-Authenticate", "Retry-After", "Server", |
|---|
| 530 | | "Vary", "WWW-Authenticate"] |
|---|
| 531 | | entity_header_fields = ["Allow", "Content-Encoding", "Content-Language", |
|---|
| 532 | | "Content-Length", "Content-Location", "Content-MD5", |
|---|
| 533 | | "Content-Range", "Content-Type", "Expires", |
|---|
| 534 | | "Last-Modified"] |
|---|
| 535 | | |
|---|
| 536 | | _header_order_map = {} |
|---|
| 537 | | for _ in general_header_fields: |
|---|
| 538 | | _header_order_map[_] = 0 |
|---|
| 539 | | for _ in response_header_fields: |
|---|
| 540 | | _header_order_map[_] = 1 |
|---|
| 541 | | for _ in entity_header_fields: |
|---|
| 542 | | _header_order_map[_] = 2 |
|---|
| 543 | | |
|---|
| 544 | | _ie_friendly_error_sizes = {400: 512, 403: 256, 404: 512, 405: 256, |
|---|
| 545 | | 406: 512, 408: 512, 409: 512, 410: 256, |
|---|
| 546 | | 500: 512, 501: 512, 505: 512, |
|---|
| 547 | | } |
|---|
| 548 | | |
|---|
| 549 | | |
|---|
| 550 | | def finalize(): |
|---|
| 551 | | """Transform headerMap (and cookies) into cherrypy.response.headers.""" |
|---|
| 552 | | |
|---|
| 553 | | response = cherrypy.response |
|---|
| 554 | | |
|---|
| 555 | | code, reason, _ = cptools.validStatus(response.status) |
|---|
| 556 | | response.status = "%s %s" % (code, reason) |
|---|
| 557 | | |
|---|
| 558 | | if response.body is None: |
|---|
| 559 | | response.body = [] |
|---|
| 560 | | |
|---|
| 561 | | stream = cherrypy.config.get("streamResponse", False) |
|---|
| 562 | | # OPTIONS requests MUST include a Content-Length of 0 if no body. |
|---|
| 563 | | # Just punt and figure Content-Length for all OPTIONS requests. |
|---|
| 564 | | if cherrypy.request.method == "OPTIONS": |
|---|
| 565 | | stream = False |
|---|
| 566 | | |
|---|
| 567 | | if stream: |
|---|
| 568 | | try: |
|---|
| 569 | | del response.headerMap['Content-Length'] |
|---|
| 570 | | except KeyError: |
|---|
| 571 | | pass |
|---|
| 572 | | else: |
|---|
| 573 | | # Responses which are not streamed should have a Content-Length, |
|---|
| 574 | | # but allow user code to set Content-Length if desired. |
|---|
| 575 | | if response.headerMap.get('Content-Length') is None: |
|---|
| 576 | | content = ''.join([chunk for chunk in response.body]) |
|---|
| 577 | | response.body = [content] |
|---|
| 578 | | response.headerMap['Content-Length'] = len(content) |
|---|
| 579 | | |
|---|
| 580 | | # For some statuses, Internet Explorer 5+ shows "friendly error messages" |
|---|
| 581 | | # instead of our response.body if the body is smaller than a given size. |
|---|
| 582 | | # Fix this by returning a body over that size (by adding whitespace). |
|---|
| 583 | | # See http://support.microsoft.com/kb/q218155/ |
|---|
| 584 | | s = int(response.status.split(" ")[0]) |
|---|
| 585 | | s = _ie_friendly_error_sizes.get(s, 0) |
|---|
| 586 | | if s: |
|---|
| 587 | | s += 1 |
|---|
| 588 | | # Since we are issuing an HTTP error status, we assume that |
|---|
| 589 | | # the entity is short, and we should just collapse it. |
|---|
| 590 | | content = ''.join([chunk for chunk in response.body]) |
|---|
| 591 | | response.body = [content] |
|---|
| 592 | | l = len(content) |
|---|
| 593 | | if l and l < s: |
|---|
| 594 | | # IN ADDITION: the response must be written to IE |
|---|
| 595 | | # in one chunk or it will still get replaced! Bah. |
|---|
| 596 | | response.body = [response.body[0] + (" " * (s - l))] |
|---|
| 597 | | response.headerMap['Content-Length'] = s |
|---|
| 598 | | |
|---|
| 599 | | # Headers |
|---|
| 600 | | headers = [] |
|---|
| 601 | | for key, valueList in response.headerMap.iteritems(): |
|---|
| 602 | | order = _header_order_map.get(key, 3) |
|---|
| 603 | | if not isinstance(valueList, list): |
|---|
| 604 | | valueList = [valueList] |
|---|
| 605 | | for value in valueList: |
|---|
| 606 | | headers.append((order, (key, str(value)))) |
|---|
| 607 | | # RFC 2616: '... it is "good practice" to send general-header fields |
|---|
| 608 | | # first, followed by request-header or response-header fields, and |
|---|
| 609 | | # ending with the entity-header fields.' |
|---|
| 610 | | headers.sort() |
|---|
| 611 | | response.headers = [item[1] for item in headers] |
|---|
| 612 | | |
|---|
| 613 | | cookie = response.simpleCookie.output() |
|---|
| 614 | | if cookie: |
|---|
| 615 | | lines = cookie.split("\n") |
|---|
| 616 | | for line in lines: |
|---|
| 617 | | name, value = line.split(": ", 1) |
|---|
| 618 | | response.headers.append((name, value)) |
|---|
| 619 | | |
|---|
| | 694 | def flattener(input): |
|---|
| | 695 | """Yield the given input, recursively iterating over each result (if needed).""" |
|---|
| | 696 | for x in input: |
|---|
| | 697 | if not isinstance(x, types.GeneratorType): |
|---|
| | 698 | yield x |
|---|
| | 699 | else: |
|---|
| | 700 | for y in flattener(x): |
|---|
| | 701 | yield y |
|---|
| 642 | | |
|---|
| 643 | | def flattener(input): |
|---|
| 644 | | """Yield the given input, recursively iterating over each result (if needed).""" |
|---|
| 645 | | for x in input: |
|---|
| 646 | | if not isinstance(x, types.GeneratorType): |
|---|
| 647 | | yield x |
|---|
| 648 | | else: |
|---|
| 649 | | for y in flattener(x): |
|---|
| 650 | | yield y |
|---|
| 651 | | |
|---|
| 652 | | |
|---|
| 653 | | # Object lookup |
|---|
| 654 | | |
|---|
| 655 | | def getObjFromPath(objPathList): |
|---|
| 656 | | """For a given objectPathList, return the object (or None). |
|---|
| 657 | | |
|---|
| 658 | | objPathList should be a list of the form: ['root', 'a', 'b', 'index']. |
|---|
| 659 | | """ |
|---|
| 660 | | |
|---|
| 661 | | root = cherrypy |
|---|
| 662 | | for objname in objPathList: |
|---|
| 663 | | # maps virtual filenames to Python identifiers (substitutes '.' for '_') |
|---|
| 664 | | objname = objname.replace('.', '_') |
|---|
| 665 | | if getattr(cherrypy, "debug", None): |
|---|
| 666 | | cherrypy.log(" Trying: %s.%s" % (root, objname), "DEBUG") |
|---|
| 667 | | root = getattr(root, objname, None) |
|---|
| 668 | | if root is None: |
|---|
| 669 | | return None |
|---|
| 670 | | return root |
|---|
| 671 | | |
|---|
| 672 | | def mapPathToObject(path): |
|---|
| 673 | | """For path, return the corresponding exposed callable (or raise NotFound). |
|---|
| 674 | | |
|---|
| 675 | | path should be a "relative" URL path, like "/app/a/b/c". Leading and |
|---|
| 676 | | trailing slashes are ignored. |
|---|
| 677 | | |
|---|
| 678 | | Traverse path: |
|---|
| 679 | | for /a/b?arg=val, we'll try: |
|---|
| 680 | | root.a.b.index -> redirect to /a/b/?arg=val |
|---|
| 681 | | root.a.b.default(arg='val') -> redirect to /a/b/?arg=val |
|---|
| 682 | | root.a.b(arg='val') |
|---|
| 683 | | root.a.default('b', arg='val') |
|---|
| 684 | | root.default('a', 'b', arg='val') |
|---|
| 685 | | |
|---|
| 686 | | The target method must have an ".exposed = True" attribute. |
|---|
| 687 | | |
|---|
| 688 | | """ |
|---|
| 689 | | |
|---|
| 690 | | # Remove leading and trailing slash |
|---|
| 691 | | tpath = path.strip("/") |
|---|
| 692 | | |
|---|
| 693 | | if not tpath: |
|---|
| 694 | | objectPathList = [] |
|---|
| 695 | | else: |
|---|
| 696 | | objectPathList = tpath.split('/') |
|---|
| 697 | | if objectPathList == ['global']: |
|---|
| 698 | | objectPathList = ['_global'] |
|---|
| 699 | | objectPathList = ['root'] + objectPathList + ['index'] |
|---|
| 700 | | |
|---|
| 701 | | if getattr(cherrypy, "debug", None): |
|---|
| 702 | | cherrypy.log(" Attempting to map path: %s using %s" |
|---|
| 703 | | % (tpath, objectPathList), "DEBUG") |
|---|
| 704 | | |
|---|
| 705 | | # Try successive objects... (and also keep the remaining object list) |
|---|
| 706 | | isFirst = True |
|---|
| 707 | | isSecond = False |
|---|
| 708 | | foundIt = False |
|---|
| 709 | | virtualPathList = [] |
|---|
| 710 | | while objectPathList: |
|---|
| 711 | | if isFirst or isSecond: |
|---|
| 712 | | # Only try this for a.b.index() or a.b() |
|---|
| 713 | | candidate = getObjFromPath(objectPathList) |
|---|
| 714 | | if callable(candidate) and getattr(candidate, 'exposed', False): |
|---|
| 715 | | foundIt = True |
|---|
| 716 | | break |
|---|
| 717 | | # Couldn't find the object: pop one from the list and try "default" |
|---|
| 718 | | lastObj = objectPathList.pop() |
|---|
| 719 | | if (not isFirst) or (not tpath): |
|---|
| 720 | | virtualPathList.insert(0, lastObj) |
|---|
| 721 | | objectPathList.append('default') |
|---|
| 722 | | candidate = getObjFromPath(objectPathList) |
|---|
| 723 | | if callable(candidate) and getattr(candidate, 'exposed', False): |
|---|
| 724 | | foundIt = True |
|---|
| 725 | | break |
|---|
| 726 | | objectPathList.pop() # Remove "default" |
|---|
| 727 | | if isSecond: |
|---|
| 728 | | isSecond = False |
|---|
| 729 | | if isFirst: |
|---|
| 730 | | isFirst = False |
|---|
| 731 | | isSecond = True |
|---|
| 732 | | |
|---|
| 733 | | # Check results of traversal |
|---|
| 734 | | if not foundIt: |
|---|
| 735 | | if tpath.endswith("favicon.ico"): |
|---|
| 736 | | # Use CherryPy's default favicon.ico. If developers really, |
|---|
| 737 | | # really want no favicon, they can make a dummy method |
|---|
| 738 | | # that raises NotFound. |
|---|
| 739 | | icofile = os.path.join(os.path.dirname(__file__), "favicon.ico") |
|---|
| 740 | | cptools.serveFile(icofile) |
|---|
| 741 | | applyFilters('beforeFinalize') |
|---|
| 742 | | finalize() |
|---|
| 743 | | raise cherrypy.RequestHandled() |
|---|
| 744 | | else: |
|---|
| 745 | | # We didn't find anything |
|---|
| 746 | | if getattr(cherrypy, "debug", None): |
|---|
| 747 | | cherrypy.log(" NOT FOUND", "DEBUG") |
|---|
| 748 | | raise cherrypy.NotFound(path) |
|---|
| 749 | | |
|---|
| 750 | | if isFirst: |
|---|
| 751 | | # We found the extra ".index" |
|---|
| 752 | | # Check if the original path had a trailing slash (otherwise, do |
|---|
| 753 | | # a redirect) |
|---|
| 754 | | if path[-1] != '/': |
|---|
| 755 | | atoms = cherrypy.request.browserUrl.split("?", 1) |
|---|
| 756 | | newUrl = atoms.pop(0) + '/' |
|---|
| 757 | | if atoms: |
|---|
| 758 | | newUrl += "?" + atoms[0] |
|---|
| 759 | | if getattr(cherrypy, "debug", None): |
|---|
| 760 | | cherrypy.log(" Found: redirecting to %s" % newUrl, "DEBUG") |
|---|
| 761 | | raise cherrypy.HTTPRedirect(newUrl) |
|---|
| 762 | | |
|---|
| 763 | | if getattr(cherrypy, "debug", None): |
|---|
| 764 | | cherrypy.log(" Found: %s" % candidate, "DEBUG") |
|---|
| 765 | | return candidate, objectPathList, virtualPathList |
|---|
| 766 | | |
|---|