Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Changeset 758

Show
Ignore:
Timestamp:
10/29/05 02:44:09
Author:
fumanchu
Message:

Merged /requestobj branch to trunk. cherrypy.serving.request and .response are now instances of _cphttptools.Request and .Response. ALL HTTP SERVER AUTHORS NEED TO UPGRADE THEIR INTERFACES to _cpserver.request as the signature has changed.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/cherrypy/__init__.py

    r744 r758  
    5454serving = local() 
    5555 
    56 class _AttributeDump: 
    57     pass 
    58  
    5956class _ThreadLocalProxy: 
    6057     
    6158    def __init__(self, attrname): 
    6259        self.__dict__["__attrname__"] = attrname 
    63      
    64     def purge__(self): 
    65         """Make a new, emtpy proxied object in cherrypy.serving.""" 
    66         setattr(serving, self.__attrname__, _AttributeDump()) 
    6760     
    6861    def __getattr__(self, name): 
  • trunk/cherrypy/_cphttpserver.py

    r698 r758  
    3030import SocketServer, BaseHTTPServer, Queue 
    3131import cherrypy 
    32 from cherrypy import _cputil, _cphttptools 
     32from cherrypy import _cputil 
    3333 
    3434 
     
    7777            return 
    7878         
    79         cherrypy.request.purge__() 
    80         cherrypy.response.purge__() 
    81          
    82         tp = cherrypy.config.get("server.threadPool") 
    83         cherrypy.request.multithread = (tp > 1) 
    84         cherrypy.request.multiprocess = False 
    85         cherrypy.server.request(self.client_address, 
    86                                 self.address_string(), 
    87                                 self.raw_requestline, 
    88                                 self._headerlist(), 
    89                                 self.rfile, "http") 
     79        request = cherrypy.server.request(self.client_address, 
     80                                          self.address_string(), "http") 
     81        request.multithread = cherrypy.config.get("server.threadPool") > 1 
     82        request.multiprocess = False 
     83        response = request.run(self.raw_requestline, self._headerlist(), 
     84                               self.rfile) 
     85         
    9086        wfile = self.wfile 
    91         wfile.write("%s %s\r\n" % 
    92                     (self.protocol_version, cherrypy.response.status)) 
     87        wfile.write("%s %s\r\n" % (self.protocol_version, response.status)) 
    9388         
    9489        has_close_conn = False 
    95         for name, value in cherrypy.response.headers: 
     90        for name, value in response.headers: 
    9691            wfile.write("%s: %s\r\n" % (name, value)) 
    9792            if name.lower == 'connection' and value.lower == 'close': 
     
    119114        wfile.write("\r\n") 
    120115        try: 
    121             for chunk in cherrypy.response.body: 
     116            for chunk in response.body: 
    122117                wfile.write(chunk) 
    123118        except: 
    124             s, h, b = _cphttptools.bareError() 
     119            s, h, b = _cputil.bareError() 
    125120            for chunk in b: 
    126121                wfile.write(chunk) 
  • trunk/cherrypy/_cphttptools.py

    r755 r758  
    4242 
    4343import cherrypy 
    44 from cherrypy import _cputil, _cpcgifs, _cpwsgiserver, _cperror 
     44from cherrypy import _cputil, _cpcgifs, _cpwsgiserver 
    4545from cherrypy.lib import cptools 
    4646 
     
    151151 
    152152class Request(object): 
    153      
    154     """Process an HTTP request and set cherrypy.response attributes.""" 
    155      
    156     def __init__(self, clientAddress, remoteHost, requestLine, headers, 
    157                  rfile, scheme="http"): 
     153    """An HTTP request.""" 
     154     
     155    def __init__(self, remoteAddr, remotePort, remoteHost, scheme="http"): 
    158156        """Populate a new Request object. 
    159157         
    160         clientAddress should be a tuple of client IP address, client Port 
     158        remoteAddr should be the client IP address 
     159        remotePort should be the client Port 
    161160        remoteHost should be string of the client's IP address. 
     161        scheme should be a string, either "http" or "https". 
     162        """ 
     163        self.remoteAddr = remoteAddr 
     164        self.remotePort = remotePort 
     165        self.remoteHost = remoteHost 
     166        self.scheme = scheme 
     167     
     168    def run(self, requestLine, headers, rfile): 
     169        """Process the Request. 
     170         
    162171        requestLine should be of the form "GET /path HTTP/1.0". 
    163172        headers should be a list of (name, value) tuples. 
    164173        rfile should be a file-like object containing the HTTP request 
    165174            entity. 
    166         scheme should be a string, either "http" or "https". 
    167          
    168         When __init__ is done, cherrypy.response should have 3 attributes: 
     175         
     176        When run() is done, cherrypy.response should have 3 attributes: 
    169177          status, e.g. "200 OK" 
    170178          headers, a list of (name, value) tuples 
     
    175183         
    176184        """ 
    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": 
    212238            # HEAD requests MUST NOT return a message-body in the response. 
    213239            cherrypy.response.body = [] 
     
    215241        _cputil.getSpecialAttribute("_cpLogAccess")() 
    216242     
    217     def parseFirstLine(self): 
    218         # This has to be done very early in the request process, 
    219         # because request.path is used for config lookups right away. 
    220         request = cherrypy.request 
    221          
    222         # Parse first line 
    223         request.method, path, request.protocol = request.requestLine.split() 
    224         request.processRequestBody = request.method in ("POST", "PUT") 
     243    def processRequestLine(self, requestLine): 
     244        self.requestLine = requestLine.strip() 
     245        self.method, path, self.protocol = self.requestLine.split() 
     246        self.processRequestBody = self.method in ("POST", "PUT") 
    225247         
    226248        # separate the queryString, or set it to "" if not found 
    227249        if "?" in path: 
    228             path, request.queryString = path.split("?", 1) 
    229         else: 
    230             path, request.queryString = path, "" 
     250            path, self.queryString = path.split("?", 1) 
     251        else: 
     252            path, self.queryString = path, "" 
    231253         
    232254        # Unquote the path (e.g. "/this%20path" -> "this path"). 
     
    248270         
    249271        # Save original value (in case it gets modified by filters) 
    250         request.path = request.originalPath = path 
     272        self.path = self.originalPath = path 
    251273         
    252274        # Change objectPath in filters to change 
    253275        # the object that will get rendered 
    254         request.objectPath = None 
    255      
    256     def run(self): 
    257         """Process the Request.""" 
    258         try: 
    259             try: 
    260                 applyFilters('onStartResource') 
    261                  
    262                 try: 
    263                     self.processRequestHeaders() 
    264                      
    265                     applyFilters('beforeRequestBody') 
    266                     if cherrypy.request.processRequestBody: 
    267                         self.processRequestBody() 
    268                      
    269                     applyFilters('beforeMain') 
    270                     if cherrypy.response.body is None: 
    271                         main() 
    272                      
    273                     applyFilters('beforeFinalize') 
    274                     finalize() 
    275                 except cherrypy.RequestHandled: 
    276                     pass 
    277                 except (cherrypy.HTTPRedirect, cherrypy.HTTPError), inst: 
    278                     # For an HTTPRedirect or HTTPError (including NotFound), 
    279                     # we don't go through the regular mechanism: 
    280                     # we return the redirect or error page immediately 
    281                     inst.set_response() 
    282                     applyFilters('beforeFinalize') 
    283                     finalize() 
    284             finally: 
    285                 applyFilters('onEndResource') 
    286         except (KeyboardInterrupt, SystemExit): 
    287             raise 
    288         except: 
    289             handleError(sys.exc_info()) 
    290      
    291     def processRequestHeaders(self): 
    292         request = cherrypy.request 
    293          
     276        self.objectPath = None 
     277     
     278    def processHeaders(self): 
    294279        # Compare request and server HTTP versions, in case our server does 
    295280        # not support the requested version. We can't tell the server what 
     
    305290        # the client only understands 1.0. RFC 2616 10.5.6 says we should 
    306291        # only return 505 if the _major_ version is different. 
    307         request_v = Version.from_http(request.protocol) 
     292         
     293        # cherrypy.request.version == request.protocol in a Version instance. 
     294        self.version = Version.from_http(self.protocol) 
    308295        server_v = cherrypy.config.get("server.protocolVersion", "HTTP/1.0") 
    309296        server_v = Version.from_http(server_v) 
     297         
    310298        # cherrypy.response.version should be used to determine whether or 
    311299        # not to include a given HTTP/1.1 feature in the response content. 
    312         cherrypy.response.version = min(request_v, server_v) 
    313         # cherrypy.request.version == request.protocol in a Version instance. 
    314         cherrypy.request.version = request_v 
     300        cherrypy.response.version = min(self.version, server_v) 
    315301         
    316302        # build a paramMap dictionary from queryString 
    317         if re.match(r"[0-9]+,[0-9]+", request.queryString): 
     303        if re.match(r"[0-9]+,[0-9]+", self.queryString): 
    318304            # Server-side image map. Map the coords to 'x' and 'y' 
    319305            # (like CGI::Request does). 
    320             pm = request.queryString.split(",") 
     306            pm = self.queryString.split(",") 
    321307            pm = {'x': int(pm[0]), 'y': int(pm[1])} 
    322308        else: 
    323             pm = cgi.parse_qs(request.queryString, keep_blank_values=True) 
     309            pm = cgi.parse_qs(self.queryString, keep_blank_values=True) 
    324310            for key, val in pm.items(): 
    325311                if len(val) == 1: 
    326312                    pm[key] = val[0] 
    327         request.paramMap = pm 
    328          
    329         # Process the headers into request.headerMap 
    330         for name, value in request.headers: 
     313        self.paramMap = pm 
     314         
     315        # Process the headers into self.headerMap 
     316        for name, value in self.headers: 
    331317            value = value.strip() 
    332318            # Warning: if there is more than one header entry for cookies (AFAIK, 
    333319            # only Konqueror does that), only the last one will remain in headerMap 
    334320            # (but they will be correctly stored in request.simpleCookie). 
    335             request.headerMap[name] = value 
     321            self.headerMap[name] = value 
    336322             
    337323            # Handle cookies differently because on Konqueror, multiple 
    338324            # cookies come on different lines with the same key 
    339325            if name.title() == 'Cookie': 
    340                 request.simpleCookie.load(value) 
     326                self.simpleCookie.load(value) 
    341327         
    342328        # Write a message to the error.log only if there is no access.log. 
     
    345331        fname = cherrypy.config.get('server.logAccessFile', '') 
    346332        if not fname: 
    347             msg = "%s - %s" % (request.remoteAddr, request.requestLine) 
     333            msg = "%s - %s" % (self.remoteAddr, self.requestLine) 
    348334            cherrypy.log(msg, "HTTP") 
    349335         
    350336        # Save original values (in case they get modified by filters) 
    351         request.originalParamMap = request.paramMap 
    352         request.originalParamList = request.paramList 
    353          
    354         if cherrypy.response.version >= "1.1": 
     337        self.originalParamMap = self.paramMap 
     338        self.originalParamList = self.paramList 
     339         
     340        if self.version >= "1.1": 
    355341            # All Internet-based HTTP/1.1 servers MUST respond with a 400 
    356342            # (Bad Request) status code to any HTTP/1.1 request message 
    357343            # which lacks a Host header field. 
    358             if not request.headerMap.has_key("Host"): 
     344            if not self.headerMap.has_key("Host"): 
    359345                msg = "HTTP/1.1 requires a 'Host' request header." 
    360346                raise cherrypy.HTTPError(400, msg) 
    361         request.base = "%s://%s" % (request.scheme, request.headerMap.get('Host', '')) 
    362         request.browserUrl = request.base + request.path 
    363         if request.queryString: 
    364             request.browserUrl += '?' + request.queryString 
    365      
    366     def processRequestBody(self): 
    367         request = cherrypy.request 
    368          
     347        self.base = "%s://%s" % (self.scheme, self.headerMap.get('Host', '')) 
     348        self.browserUrl = self.base + self.path 
     349        if self.queryString:   
     350            self.browserUrl += '?' + self.queryString 
     351     
     352    def processBody(self): 
    369353        # Create a copy of headerMap with lowercase keys because 
    370354        # FieldStorage doesn't work otherwise 
    371355        lowerHeaderMap = {} 
    372         for key, value in request.headerMap.items(): 
     356        for key, value in self.headerMap.items(): 
    373357            lowerHeaderMap[key.lower()] = value 
    374358         
     
    376360        methenv = {'REQUEST_METHOD': "POST"} 
    377361        try: 
    378             forms = _cpcgifs.FieldStorage(fp=request.rfile, 
    379                                       headers=lowerHeaderMap, 
    380                                       environ=methenv, 
    381                                       keep_blank_values=1) 
     362            forms = _cpcgifs.FieldStorage(fp=self.rfile, 
     363                                          headers=lowerHeaderMap, 
     364                                          environ=methenv, 
     365                                          keep_blank_values=1) 
    382366        except _cpwsgiserver.MaxSizeExceeded: 
    383367            # Post data is too big 
    384             raise _cperror.HTTPError(413) 
     368            raise cherrypy.HTTPError(413) 
    385369         
    386370        if forms.file: 
    387371            # request body was a content-type other than form params. 
    388             cherrypy.request.body = forms.file 
     372            self.body = forms.file 
    389373        else: 
    390374            for key in forms.keys(): 
    391375                valueList = forms[key] 
    392376                if isinstance(valueList, list): 
    393                     request.paramMap[key] = [] 
     377                    self.paramMap[key] = [] 
    394378                    for item in valueList: 
    395379                        if item.filename is not None: 
     
    397381                        else: 
    398382                            value = item.value # It's a regular field 
    399                         request.paramMap[key].append(value) 
     383                        self.paramMap[key].append(value) 
    400384                else: 
    401385                    if valueList.filename is not None: 
     
    403387                    else: 
    404388                        value = valueList.value # It's a regular field 
    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 
     523general_header_fields = ["Cache-Control", "Connection", "Date", "Pragma", 
     524                         "Trailer", "Transfer-Encoding", "Upgrade", "Via", 
     525                         "Warning"] 
     526response_header_fields = ["Accept-Ranges", "Age", "ETag", "Location", 
     527                          "Proxy-Authenticate", "Retry-After", "Server", 
     528                          "Vary", "WWW-Authenticate"] 
     529entity_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 = {} 
     535for _ in general_header_fields: 
     536    _header_order_map[_] = 0 
     537for _ in response_header_fields: 
     538    _header_order_map[_] = 1 
     539for _ 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 
     548class 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.""" 
    435638        try: 
    436             inst.set_response() 
    437             finalize() 
     639            applyFilters('beforeErrorResponse') 
     640            
     641            # _cpOnError will probably change self.body. 
     642            # It may also change the headerMap, etc. 
     643            _cputil.getSpecialAttribute('_cpOnError')() 
     644             
     645            self.finalize() 
     646             
     647            applyFilters('afterErrorResponse') 
    438648            return 
     649        except cherrypy.HTTPRedirect, inst: 
     650            try: 
     651                inst.set_response() 
     652                self.finalize() 
     653                return 
     654            except (KeyboardInterrupt, SystemExit): 
     655                raise 
     656            except: 
     657                # Fall through to the second error handler 
     658                pass 
    439659        except (KeyboardInterrupt, SystemExit): 
    440660            raise 
     
    442662            # Fall through to the second error handler 
    443663            pass 
    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 
    508678 
    509679def iterable(body): 
     
    522692    return body 
    523693 
    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  
     694def 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  
    620702 
    621703def applyFilters(methodName): 
     
    628710        filterList = (_cputil.getSpecialAttribute('_cpFilterList') + 
    629711                      _cputil._cpDefaultOutputFilterList) 
    630     #else: 
    631     #    # '',  
    632     #    # 'beforeErrorResponse', 'afterErrorResponse' 
    633     #    filterList = (_cputil._cpDefaultInputFilterList + 
    634     #                  _cputil.getSpecialAttribute('_cpFilterList') + 
    635     #                  _cputil._cpDefaultOutputFilterList) 
    636712    else: 
    637713        assert False # Wrong methodName for the filter 
     
    640716        if method: 
    641717            method() 
    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