Robert Brewer avatar Robert Brewer committed a716407

CP 3 initial checkin. Global filters have been replaced by request.hooks. Lots of renaming and reorg of modules.

Comments (0)

Files changed (45)

 """Global module that all modules developing with CherryPy should import."""
 
-__version__ = '2.2.0'
+__version__ = '3.0.0alpha'
 
 import datetime
 import sys
 
 from _cperror import *
 import config
+import tools
 
 import _cptree
 tree = _cptree.Tree()
 # Legacy code may clobber this.
 root = None
 
-lowercase_api = False
-
 import _cpserver
 server = _cpserver.Server()
 
 
 # Create thread_data object as a thread-specific all-purpose storage
 thread_data = local()
-threadData = thread_data # Backward compatibility
 
-# Create variables needed for session (see lib/sessionfilter.py for more info)
-from filters import sessionfilter
-session = sessionfilter.SessionWrapper()
-_session_data_holder = {} # Needed for RAM sessions only
-_session_lock_dict = {} # Needed for RAM sessions only
-_session_last_clean_up_time = datetime.datetime.now()
+### Create variables needed for session (see lib/sessionfilter.py for more info)
+##from filters import sessionfilter
+##session = sessionfilter.SessionWrapper()
+##_session_data_holder = {} # Needed for RAM sessions only
+##_session_lock_dict = {} # Needed for RAM sessions only
+##_session_last_clean_up_time = datetime.datetime.now()
 
 def expose(func=None, alias=None):
     """Expose the function, optionally providing an alias or set of aliases."""
         msg += _cputil.formatExc()
     
     logfunc(msg, context, severity)
+
 import warnings
 
 import cherrypy
-from cherrypy import _cphttptools, filters
+from cherrypy import _cprequest
 from cherrypy.lib import autoreload, profiler, cptools
 
 # Use a flag to indicate the state of the application server.
 class Engine(object):
     """The application server engine, connecting HTTP servers to Requests."""
     
-    request_class = _cphttptools.Request
-    response_class = _cphttptools.Response
+    request_class = _cprequest.Request
+    response_class = _cprequest.Response
     
     def __init__(self):
         self.state = STOPPED
             cherrypy.profiler = None
         
         # Initialize the built in filters
-        filters.init()
+##        filters.init()
     
     def start(self):
         """Start the application server engine."""
     ready = property(_is_ready, doc="Return True if the server is ready to"
                                     " receive requests, False otherwise.")
     
-    def request(self, clientAddress, remoteHost, scheme="http"):
+    def request(self, clientAddress, remote_host, scheme="http"):
         """Obtain an HTTP Request object.
         
         clientAddress: the (IP address, port) of the client
-        remoteHost: the IP address of the client
+        remote_host: the IP address of the client
         scheme: either "http" or "https"; defaults to "http"
         """
         if self.state == STOPPED:
                 func(i)
         
         r = self.request_class(clientAddress[0], clientAddress[1],
-                               remoteHost, scheme)
+                               remote_host, scheme)
         cherrypy.serving.request = r
         cherrypy.serving.response = self.response_class()
         return r
         if params is not None:
             if isinstance(params, basestring):
                 request.query_string = params
-                request.queryString = request.query_string # Backward compatibility
                 pm = cgi.parse_qs(params, keep_blank_values=True)
                 for key, val in pm.items():
                     if len(val) == 1:
+try:
+    set
+except NameError:
+    from sets import Set as set
+import cherrypy
+
+
+class Hook(object):
+    """A point at which CherryPy will call registered callbacks."""
+    
+    def __init__(self, name, failsafe = False):
+        self.name = name
+        self.failsafe = failsafe
+        self.callbacks = []
+
+
+class HookDispatcher(object):
+    
+    _input_hooks = [Hook('on_start_resource', failsafe=True),
+                    Hook('before_request_body'),
+                    Hook('before_main')]
+    _output_hooks = [Hook('before_finalize'),
+                     Hook('on_end_resource', failsafe=True),
+                     Hook('on_end_request', failsafe=True),
+                     Hook('before_error_response'),
+                     Hook('after_error_response'),
+                     ]
+    
+    def __init__(self):
+        self.hooks = dict([(k.name, k) for k
+                           in self._input_hooks + self._output_hooks])
+    
+    def run(self, hook_name):
+        """Execute all registered hooks for the given name."""
+        hook = self.hooks[hook_name]
+        for c in hook.callbacks:
+            if cherrypy.config.enabled(c.name):
+                kwargs = dict([(k, v) for k, v in conf if k.startswith(c.name + ".")])
+                # The on_start_resource, on_end_resource, and on_end_request methods
+                # are guaranteed to run even if other methods of the same name fail.
+                # We will still log the failure, but proceed on to the next method.
+                # The only way to stop all processing from one of these methods is
+                # to raise SystemExit and stop the whole server. So, trap your own
+                # errors in these methods!
+                if hook.failsafe:
+                    try:
+                        c(**kwargs)
+                    except (KeyboardInterrupt, SystemExit):
+                        raise
+                    except:
+                        cherrypy.log(traceback=True)
+                else:
+                    c(**kwargs)
+    
+    def register(self, hook_name, callback):
+        self.hooks[hook_name].callbacks.append(callback)
+    
+    def register_builtin(self, name):
+        from cherrypy.lib import cptools
+        if name == "base_url":
+            self.register('before_request_body', cptools.base_url)
+        elif name == "virtual_host":
+            self.register('before_request_body', cptools.virtual_host)
+        elif name == "static.file":
+            from cherrypy.lib import static
+            self.register(static.File(filename))
+
+
+dispatcher = HookDispatcher()
+
+

_cphttptools.py

-"""CherryPy core request/response handling."""
-
-import Cookie
-import os
-import sys
-import types
-
-import cherrypy
-from cherrypy import _cputil, _cpcgifs
-from cherrypy.filters import applyFilters
-from cherrypy.lib import cptools, httptools
-
-
-class Request(object):
-    """An HTTP request."""
-    
-    def __init__(self, remoteAddr, remotePort, remoteHost, scheme="http"):
-        """Populate a new Request object.
-        
-        remoteAddr should be the client IP address
-        remotePort should be the client Port
-        remoteHost should be string of the client's IP address.
-        scheme should be a string, either "http" or "https".
-        """
-        self.remote_addr  = remoteAddr
-        self.remote_port  = remotePort
-        self.remote_host  = remoteHost
-        # backward compatibility
-        self.remoteAddr = remoteAddr
-        self.remotePort = remotePort
-        self.remoteHost = remoteHost
-        
-        self.scheme = scheme
-        self.execute_main = True
-        self.closed = False
-    
-    def close(self):
-        if not self.closed:
-            self.closed = True
-            applyFilters('on_end_request', failsafe=True)
-            cherrypy.serving.__dict__.clear()
-    
-    def run(self, requestLine, headers, rfile):
-        """Process the Request.
-        
-        requestLine should be of the form "GET /path HTTP/1.0".
-        headers should be a list of (name, value) tuples.
-        rfile should be a file-like object containing the HTTP request
-            entity.
-        
-        When run() is done, the returned object should have 3 attributes:
-          status, e.g. "200 OK"
-          headers, a list of (name, value) tuples
-          body, an iterable yielding strings
-        
-        Consumer code (HTTP servers) should then access these response
-        attributes to build the outbound stream.
-        
-        """
-        self.requestLine = requestLine.strip()
-        self.header_list = list(headers)
-        self.rfile = rfile
-        
-        self.headers = httptools.HeaderMap()
-        self.headerMap = self.headers # Backward compatibility
-        self.simple_cookie = Cookie.SimpleCookie()
-        self.simpleCookie = self.simple_cookie # Backward compatibility
-        
-        if cherrypy.profiler:
-            cherrypy.profiler.run(self._run)
-        else:
-            self._run()
-        
-        if self.method == "HEAD":
-            # HEAD requests MUST NOT return a message-body in the response.
-            cherrypy.response.body = []
-        
-        _cputil.get_special_attribute("_cp_log_access", "_cpLogAccess")()
-        
-        return cherrypy.response
-    
-    def _run(self):
-        
-        try:
-            # This has to be done very early in the request process,
-            # because request.object_path is used for config lookups
-            # right away.
-            self.processRequestLine()
-            
-            try:
-                applyFilters('on_start_resource', failsafe=True)
-                
-                try:
-                    self.processHeaders()
-                    
-                    applyFilters('before_request_body')
-                    if self.processRequestBody:
-                        self.processBody()
-                    
-                    # Loop to allow for InternalRedirect.
-                    while True:
-                        try:
-                            applyFilters('before_main')
-                            if self.execute_main:
-                                self.main()
-                            break
-                        except cherrypy.InternalRedirect, ir:
-                            self.object_path = ir.path
-                    
-                    applyFilters('before_finalize')
-                    cherrypy.response.finalize()
-                except cherrypy.RequestHandled:
-                    pass
-                except (cherrypy.HTTPRedirect, cherrypy.HTTPError), inst:
-                    # For an HTTPRedirect or HTTPError (including NotFound),
-                    # we don't go through the regular mechanism:
-                    # we return the redirect or error page immediately
-                    inst.set_response()
-                    applyFilters('before_finalize')
-                    cherrypy.response.finalize()
-            finally:
-                applyFilters('on_end_resource', failsafe=True)
-        except (KeyboardInterrupt, SystemExit):
-            raise
-        except:
-            if cherrypy.config.get("server.throw_errors", False):
-                raise
-            cherrypy.response.handleError(sys.exc_info())
-    
-    def processRequestLine(self):
-        rl = self.requestLine
-        method, path, qs, proto = httptools.parseRequestLine(rl)
-        if path == "*":
-            path = "global"
-        
-        self.method = method
-        self.processRequestBody = method in ("POST", "PUT")
-        
-        self.path = path
-        self.query_string = qs
-        self.queryString = qs # Backward compatibility
-        self.protocol = proto
-        
-        # Change object_path in filters to change
-        # the object that will get rendered
-        self.object_path = path
-        
-        # Compare request and server HTTP versions, in case our server does
-        # not support the requested version. We can't tell the server what
-        # version number to write in the response, so we limit our output
-        # to min(req, server). We want the following output:
-        #     request    server     actual written   supported response
-        #     version    version   response version  feature set (resp.v)
-        # a     1.0        1.0           1.0                1.0
-        # b     1.0        1.1           1.1                1.0
-        # c     1.1        1.0           1.0                1.0
-        # d     1.1        1.1           1.1                1.1
-        # Notice that, in (b), the response will be "HTTP/1.1" even though
-        # the client only understands 1.0. RFC 2616 10.5.6 says we should
-        # only return 505 if the _major_ version is different.
-        
-        # cherrypy.request.version == request.protocol in a Version instance.
-        self.version = httptools.Version.from_http(self.protocol)
-        server_v = cherrypy.config.get("server.protocol_version", "HTTP/1.0")
-        server_v = httptools.Version.from_http(server_v)
-        
-        # cherrypy.response.version should be used to determine whether or
-        # not to include a given HTTP/1.1 feature in the response content.
-        cherrypy.response.version = min(self.version, server_v)
-    
-    def processHeaders(self):
-        
-        self.params = httptools.parseQueryString(self.query_string)
-        self.paramMap = self.params # Backward compatibility
-        
-        # Process the headers into self.headers
-        for name, value in self.header_list:
-            value = value.strip()
-            # Warning: if there is more than one header entry for cookies (AFAIK,
-            # only Konqueror does that), only the last one will remain in headers
-            # (but they will be correctly stored in request.simple_cookie).
-            self.headers[name] = value
-            
-            # Handle cookies differently because on Konqueror, multiple
-            # cookies come on different lines with the same key
-            if name.title() == 'Cookie':
-                self.simple_cookie.load(value)
-        
-        # Save original values (in case they get modified by filters)
-        # This feature is deprecated in 2.2 and will be removed in 2.3.
-        self._original_params = self.params.copy()
-        
-        if self.version >= "1.1":
-            # All Internet-based HTTP/1.1 servers MUST respond with a 400
-            # (Bad Request) status code to any HTTP/1.1 request message
-            # which lacks a Host header field.
-            if not self.headers.has_key("Host"):
-                msg = "HTTP/1.1 requires a 'Host' request header."
-                raise cherrypy.HTTPError(400, msg)
-        self.base = "%s://%s" % (self.scheme, self.headers.get('Host', ''))
-    
-    def _get_original_params(self):
-        # This feature is deprecated in 2.2 and will be removed in 2.3.
-        return self._original_params
-    original_params = property(_get_original_params,
-                        doc="Deprecated. A copy of the original params.")
-    
-    def _get_browser_url(self):
-        url = self.base + self.path
-        if self.query_string:
-            url += '?' + self.query_string
-        return url
-    browser_url = property(_get_browser_url,
-                          doc="The URL as entered in a browser (read-only).")
-    browserUrl = browser_url # Backward compatibility
-    
-    def processBody(self):
-        # Create a copy of headers with lowercase keys because
-        # FieldStorage doesn't work otherwise
-        lowerHeaderMap = {}
-        for key, value in self.headers.items():
-            lowerHeaderMap[key.lower()] = value
-        
-        # FieldStorage only recognizes POST, so fake it.
-        methenv = {'REQUEST_METHOD': "POST"}
-        try:
-            forms = _cpcgifs.FieldStorage(fp=self.rfile,
-                                          headers=lowerHeaderMap,
-                                          environ=methenv,
-                                          keep_blank_values=1)
-        except httptools.MaxSizeExceeded:
-            # Post data is too big
-            raise cherrypy.HTTPError(413)
-        
-        if forms.file:
-            # request body was a content-type other than form params.
-            self.body = forms.file
-        else:
-            self.params.update(httptools.paramsFromCGIForm(forms))
-    
-    def main(self, path=None):
-        """Obtain and set cherrypy.response.body from a page handler."""
-        if path is None:
-            path = self.object_path
-        
-        page_handler, object_path, virtual_path = self.mapPathToObject(path)
-        
-        # Decode any leftover %2F in the virtual_path atoms.
-        virtual_path = [x.replace("%2F", "/") for x in virtual_path]
-        
-        # Remove "root" from object_path and join it to get object_path
-        self.object_path = '/' + '/'.join(object_path[1:])
-        try:
-            body = page_handler(*virtual_path, **self.params)
-        except Exception, x:
-            if hasattr(x, "args"):
-                x.args = x.args + (page_handler,)
-            raise
-        cherrypy.response.body = body
-    
-    def mapPathToObject(self, objectpath):
-        """For path, return the corresponding exposed callable (or raise NotFound).
-        
-        path should be a "relative" URL path, like "/app/a/b/c". Leading and
-        trailing slashes are ignored.
-        
-        Traverse path:
-        for /a/b?arg=val, we'll try:
-          root.a.b.index -> redirect to /a/b/?arg=val
-          root.a.b.default(arg='val') -> redirect to /a/b/?arg=val
-          root.a.b(arg='val')
-          root.a.default('b', arg='val')
-          root.default('a', 'b', arg='val')
-        
-        The target method must have an ".exposed = True" attribute.
-        """
-        
-        objectTrail = _cputil.get_object_trail(objectpath)
-        names = [name for name, candidate in objectTrail]
-        
-        # Try successive objects (reverse order)
-        mounted_app_roots = cherrypy.tree.mount_points.values()
-        for i in xrange(len(objectTrail) - 1, -1, -1):
-            
-            name, candidate = objectTrail[i]
-            
-            # Try a "default" method on the current leaf.
-            defhandler = getattr(candidate, "default", None)
-            if callable(defhandler) and getattr(defhandler, 'exposed', False):
-                return defhandler, names[:i+1] + ["default"], names[i+1:-1]
-            
-            # Uncomment the next line to restrict positional params to "default".
-            # if i < len(objectTrail) - 2: continue
-            
-            # Try the current leaf.
-            if callable(candidate) and getattr(candidate, 'exposed', False):
-                if i == len(objectTrail) - 1:
-                    # We found the extra ".index". Check if the original path
-                    # had a trailing slash (otherwise, do a redirect).
-                    if not objectpath.endswith('/'):
-                        atoms = self.browser_url.split("?", 1)
-                        newUrl = atoms.pop(0) + '/'
-                        if atoms:
-                            newUrl += "?" + atoms[0]
-                        raise cherrypy.HTTPRedirect(newUrl)
-                return candidate, names[:i+1], names[i+1:-1]
-            
-            if candidate in mounted_app_roots:
-                break
-        
-        # We didn't find anything
-        raise cherrypy.NotFound(objectpath)
-
-
-class Body(object):
-    """The body of the HTTP response (the response entity)."""
-    
-    def __get__(self, obj, objclass=None):
-        if obj is None:
-            # When calling on the class instead of an instance...
-            return self
-        else:
-            return obj._body
-    
-    def __set__(self, obj, value):
-        # Convert the given value to an iterable object.
-        if isinstance(value, types.FileType):
-            value = cptools.fileGenerator(value)
-        elif isinstance(value, types.GeneratorType):
-            value = flattener(value)
-        elif isinstance(value, basestring):
-            # strings get wrapped in a list because iterating over a single
-            # item list is much faster than iterating over every character
-            # in a long string.
-            value = [value]
-        elif value is None:
-            value = []
-        obj._body = value
-
-
-def flattener(input):
-    """Yield the given input, recursively iterating over each result (if needed)."""
-    for x in input:
-        if not isinstance(x, types.GeneratorType):
-            yield x
-        else:
-            for y in flattener(x):
-                yield y 
-
-
-class Response(object):
-    """An HTTP Response."""
-    
-    body = Body()
-    
-    def __init__(self):
-        self.status = None
-        self.header_list = None
-        self.body = None
-        
-        self.headers = httptools.HeaderMap()
-        self.headerMap = self.headers # Backward compatibility
-        content_type = cherrypy.config.get('server.default_content_type', 'text/html')
-        self.headers.update({
-            "Content-Type": content_type,
-            "Server": "CherryPy/" + cherrypy.__version__,
-            "Date": httptools.HTTPDate(),
-            "Set-Cookie": [],
-            "Content-Length": None
-        })
-        self.simple_cookie = Cookie.SimpleCookie()
-        self.simpleCookie = self.simple_cookie # Backward compatibility
-    
-    def collapse_body(self):
-        newbody = ''.join([chunk for chunk in self.body])
-        self.body = newbody
-        return newbody
-    
-    def finalize(self):
-        """Transform headers (and cookies) into cherrypy.response.header_list."""
-        
-        try:
-            code, reason, _ = httptools.validStatus(self.status)
-        except ValueError, x:
-            raise cherrypy.HTTPError(500, x.args[0])
-        
-        self.status = "%s %s" % (code, reason)
-        
-        stream = cherrypy.config.get("stream_response", False)
-        # OPTIONS requests MUST include a Content-Length of 0 if no body.
-        # Just punt and figure Content-Length for all OPTIONS requests.
-        if cherrypy.request.method == "OPTIONS":
-            stream = False
-        
-        if stream:
-            try:
-                del self.headers['Content-Length']
-            except KeyError:
-                pass
-        else:
-            # Responses which are not streamed should have a Content-Length,
-            # but allow user code to set Content-Length if desired.
-            if self.headers.get('Content-Length') is None:
-                content = self.collapse_body()
-                self.headers['Content-Length'] = len(content)
-        
-        # Transform our header dict into a sorted list of tuples.
-        self.header_list = self.headers.sorted_list()
-        
-        cookie = self.simple_cookie.output()
-        if cookie:
-            for line in cookie.split("\n"):
-                name, value = line.split(": ", 1)
-                self.header_list.append((name, value))
-    
-    dbltrace = "\n===First Error===\n\n%s\n\n===Second Error===\n\n%s\n\n"
-    
-    def handleError(self, exc):
-        """Set status, headers, and body when an unanticipated error occurs."""
-        try:
-            applyFilters('before_error_response')
-           
-            # _cp_on_error will probably change self.body.
-            # It may also change the headers, etc.
-            _cputil.get_special_attribute('_cp_on_error', '_cpOnError')()
-            
-            self.finalize()
-            
-            applyFilters('after_error_response')
-            return
-        except cherrypy.HTTPRedirect, inst:
-            try:
-                inst.set_response()
-                self.finalize()
-                return
-            except (KeyboardInterrupt, SystemExit):
-                raise
-            except:
-                # Fall through to the second error handler
-                pass
-        except (KeyboardInterrupt, SystemExit):
-            raise
-        except:
-            # Fall through to the second error handler
-            pass
-        
-        # Failure in _cp_on_error, error filter, or finalize.
-        # Bypass them all.
-        if cherrypy.config.get('server.show_tracebacks', False):
-            body = self.dbltrace % (_cputil.formatExc(exc),
-                                    _cputil.formatExc())
-        else:
-            body = ""
-        self.setBareError(body)
-    
-    def setBareError(self, body=None):
-        self.status, self.header_list, self.body = _cputil.bareError(body)
-
+"""CherryPy core request/response handling."""
+
+import Cookie
+import os
+import sys
+import types
+
+import cherrypy
+from cherrypy import _cputil, _cpcgifs, tools
+from cherrypy.lib import cptools, httptools
+
+
+class HookMap(object):
+    
+    def __init__(self, points=None, failsafe=None):
+        points = points or []
+        self.callbacks = dict([(point, []) for point in points])
+        self.failsafe = failsafe or []
+    
+    def attach(self, point, callback, conf=None):
+        if conf is None:
+            self.callbacks[point].append(callback)
+        else:
+            def wrapper():
+                callback(**conf)
+            self.callbacks[point].append(wrapper)
+    
+    def populate_from_config(self):
+        configs = cherrypy.config.configs
+        collapsed_map = {}
+        
+        def collect_tools(section):
+            local_conf = configs.get(section, {})
+            for k, v in local_conf.iteritems():
+                atoms = k.split(".")
+                namespace = atoms.pop(0)
+                if namespace == "tools":
+                    toolname = atoms.pop(0)
+                    bucket = collapsed_map.setdefault(toolname, {})
+                    bucket[".".join(atoms)] = v
+        
+        collect_tools("global")
+        path = ""
+        for b in cherrypy.request.object_path.split('/'):
+            path = "/".join((path, b))
+            collect_tools(path)
+        
+        for toolname, conf in collapsed_map.iteritems():
+            if conf.get("on", False):
+                del conf["on"]
+                getattr(tools, toolname).setup(conf)
+    
+    def run(self, point):
+        """Execute all registered callbacks for the given point."""
+        failsafe = point in self.failsafe
+        for callback in self.callbacks[point]:
+            # Some hookpoints guarantee all callbacks are run even if
+            # others at the same hookpoint fail. We will still log the
+            # failure, but proceed on to the next callback. The only way
+            # to stop all processing from one of these callbacks is
+            # to raise SystemExit and stop the whole server. So, trap
+            # your own errors in these callbacks!
+            if failsafe:
+                try:
+                    callback()
+                except (KeyboardInterrupt, SystemExit):
+                    raise
+                except:
+                    cherrypy.log(traceback=True)
+            else:
+                callback()
+
+
+
+class Request(object):
+    """An HTTP request."""
+    
+    def __init__(self, remote_addr, remote_port, remote_host, scheme="http"):
+        """Populate a new Request object.
+        
+        remote_addr should be the client IP address
+        remote_port should be the client Port
+        remote_host should be string of the client's IP address.
+        scheme should be a string, either "http" or "https".
+        """
+        self.remote_addr  = remote_addr
+        self.remote_port  = remote_port
+        self.remote_host  = remote_host
+        
+        self.scheme = scheme
+        self.execute_main = True
+        self.closed = False
+        
+        self.hooks = HookMap(['on_start_resource', 'before_request_body',
+                              'before_main', 'before_finalize',
+                              'on_end_resource', 'on_end_request',
+                              'before_error_response', 'after_error_response'])
+        self.hooks.failsafe = ['on_start_resource', 'on_end_resource',
+                               'on_end_request']
+    
+    def close(self):
+        if not self.closed:
+            self.closed = True
+            self.hooks.run('on_end_request')
+            cherrypy.serving.__dict__.clear()
+    
+    def run(self, requestLine, headers, rfile):
+        """Process the Request.
+        
+        requestLine should be of the form "GET /path HTTP/1.0".
+        headers should be a list of (name, value) tuples.
+        rfile should be a file-like object containing the HTTP request
+            entity.
+        
+        When run() is done, the returned object should have 3 attributes:
+          status, e.g. "200 OK"
+          headers, a list of (name, value) tuples
+          body, an iterable yielding strings
+        
+        Consumer code (HTTP servers) should then access these response
+        attributes to build the outbound stream.
+        
+        """
+        self.requestLine = requestLine.strip()
+        self.header_list = list(headers)
+        self.rfile = rfile
+        
+        self.headers = httptools.HeaderMap()
+        self.simple_cookie = Cookie.SimpleCookie()
+        
+        if cherrypy.profiler:
+            cherrypy.profiler.run(self._run)
+        else:
+            self._run()
+        
+        if self.method == "HEAD":
+            # HEAD requests MUST NOT return a message-body in the response.
+            cherrypy.response.body = []
+        
+        _cputil.get_special_attribute("_cp_log_access", "_cpLogAccess")()
+        
+        return cherrypy.response
+    
+    def _run(self):
+        
+        try:
+            # This has to be done very early in the request process,
+            # because request.object_path is used for config lookups
+            # right away.
+            self.processRequestLine()
+            self.hooks.populate_from_config()
+            
+            try:
+                self.hooks.run('on_start_resource')
+                
+                try:
+                    self.processHeaders()
+                    
+                    self.hooks.run('before_request_body')
+                    if self.processRequestBody:
+                        self.processBody()
+                    
+                    # Loop to allow for InternalRedirect.
+                    while True:
+                        try:
+                            self.hooks.run('before_main')
+                            if self.execute_main:
+                                self.main()
+                            break
+                        except cherrypy.InternalRedirect, ir:
+                            self.object_path = ir.path
+                    
+                    self.hooks.run('before_finalize')
+                    cherrypy.response.finalize()
+                except cherrypy.RequestHandled:
+                    pass
+                except (cherrypy.HTTPRedirect, cherrypy.HTTPError), inst:
+                    # For an HTTPRedirect or HTTPError (including NotFound),
+                    # we don't go through the regular mechanism:
+                    # we return the redirect or error page immediately
+                    inst.set_response()
+                    self.hooks.run('before_finalize')
+                    cherrypy.response.finalize()
+            finally:
+                self.hooks.run('on_end_resource')
+        except (KeyboardInterrupt, SystemExit):
+            raise
+        except:
+            if cherrypy.config.get("server.throw_errors", False):
+                raise
+            cherrypy.response.handleError(sys.exc_info())
+    
+    def processRequestLine(self):
+        rl = self.requestLine
+        method, path, qs, proto = httptools.parseRequestLine(rl)
+        if path == "*":
+            path = "global"
+        
+        self.method = method
+        self.processRequestBody = method in ("POST", "PUT")
+        
+        self.path = path
+        self.query_string = qs
+        self.protocol = proto
+        
+        # Change object_path to change the object that will get rendered
+        self.object_path = path
+        
+        # Compare request and server HTTP versions, in case our server does
+        # not support the requested version. We can't tell the server what
+        # version number to write in the response, so we limit our output
+        # to min(req, server). We want the following output:
+        #     request    server     actual written   supported response
+        #     version    version   response version  feature set (resp.v)
+        # a     1.0        1.0           1.0                1.0
+        # b     1.0        1.1           1.1                1.0
+        # c     1.1        1.0           1.0                1.0
+        # d     1.1        1.1           1.1                1.1
+        # Notice that, in (b), the response will be "HTTP/1.1" even though
+        # the client only understands 1.0. RFC 2616 10.5.6 says we should
+        # only return 505 if the _major_ version is different.
+        
+        # cherrypy.request.version == request.protocol in a Version instance.
+        self.version = httptools.Version.from_http(self.protocol)
+        
+        # cherrypy.response.version should be used to determine whether or
+        # not to include a given HTTP/1.1 feature in the response content.
+        server_v = cherrypy.config.get("server.protocol_version", "HTTP/1.0")
+        server_v = httptools.Version.from_http(server_v)
+        cherrypy.response.version = min(self.version, server_v)
+    
+    def main(self, path=None):
+        """Obtain and set cherrypy.response.body from a page handler."""
+        dispatch = cherrypy.config.get("dispatcher") or Dispatcher()
+        handler = dispatch(path)
+        cherrypy.response.body = handler(*self.virtual_path, **self.params)
+    
+    def processHeaders(self):
+        self.params = httptools.parseQueryString(self.query_string)
+        
+        # Process the headers into self.headers
+        for name, value in self.header_list:
+            value = value.strip()
+            # Warning: if there is more than one header entry for cookies (AFAIK,
+            # only Konqueror does that), only the last one will remain in headers
+            # (but they will be correctly stored in request.simple_cookie).
+            self.headers[name] = value
+            
+            # Handle cookies differently because on Konqueror, multiple
+            # cookies come on different lines with the same key
+            if name.title() == 'Cookie':
+                self.simple_cookie.load(value)
+        
+        if self.version >= "1.1":
+            # All Internet-based HTTP/1.1 servers MUST respond with a 400
+            # (Bad Request) status code to any HTTP/1.1 request message
+            # which lacks a Host header field.
+            if not self.headers.has_key("Host"):
+                msg = "HTTP/1.1 requires a 'Host' request header."
+                raise cherrypy.HTTPError(400, msg)
+        self.base = "%s://%s" % (self.scheme, self.headers.get('Host', ''))
+    
+    def _get_browser_url(self):
+        url = self.base + self.path
+        if self.query_string:
+            url += '?' + self.query_string
+        return url
+    browser_url = property(_get_browser_url,
+                          doc="The URL as entered in a browser (read-only).")
+    
+    def processBody(self):
+        # Create a copy of headers with lowercase keys because
+        # FieldStorage doesn't work otherwise
+        lowerHeaderMap = {}
+        for key, value in self.headers.items():
+            lowerHeaderMap[key.lower()] = value
+        
+        # FieldStorage only recognizes POST, so fake it.
+        methenv = {'REQUEST_METHOD': "POST"}
+        try:
+            forms = _cpcgifs.FieldStorage(fp=self.rfile,
+                                          headers=lowerHeaderMap,
+                                          environ=methenv,
+                                          keep_blank_values=1)
+        except httptools.MaxSizeExceeded:
+            # Post data is too big
+            raise cherrypy.HTTPError(413)
+        
+        if forms.file:
+            # request body was a content-type other than form params.
+            self.body = forms.file
+        else:
+            self.params.update(httptools.paramsFromCGIForm(forms))
+
+
+class Dispatcher(object):
+    
+    def __call__(self, path=None):
+        """Find the appropriate page handler."""
+        request = cherrypy.request
+        if path is None:
+            path = request.object_path
+        
+        handler, opath, vpath = self.find(request.browser_url, path)
+        
+        # Remove "root" from opath and join it to get object_path
+        request.object_path = '/' + '/'.join(opath[1:])
+        
+        # Decode any leftover %2F in the virtual_path atoms.
+        request.virtual_path = [x.replace("%2F", "/") for x in vpath]
+        
+        return handler
+    
+    def find(self, browser_url, objectpath):
+        objectTrail = _cputil.get_object_trail(objectpath)
+        names = [name for name, candidate in objectTrail]
+        
+        # Try successive objects (reverse order)
+        mounted_app_roots = cherrypy.tree.mount_points.values()
+        for i in xrange(len(objectTrail) - 1, -1, -1):
+            
+            name, candidate = objectTrail[i]
+            
+            # Try a "default" method on the current leaf.
+            defhandler = getattr(candidate, "default", None)
+            if callable(defhandler) and getattr(defhandler, 'exposed', False):
+                return defhandler, names[:i+1] + ["default"], names[i+1:-1]
+            
+            # Uncomment the next line to restrict positional params to "default".
+            # if i < len(objectTrail) - 2: continue
+            
+            # Try the current leaf.
+            if callable(candidate) and getattr(candidate, 'exposed', False):
+                if i == len(objectTrail) - 1:
+                    # We found the extra ".index". Check if the original path
+                    # had a trailing slash (otherwise, do a redirect).
+                    if not objectpath.endswith('/'):
+                        atoms = browser_url.split("?", 1)
+                        newUrl = atoms.pop(0) + '/'
+                        if atoms:
+                            newUrl += "?" + atoms[0]
+                        raise cherrypy.HTTPRedirect(newUrl)
+                return candidate, names[:i+1], names[i+1:-1]
+            
+            if candidate in mounted_app_roots:
+                break
+        
+        # We didn't find anything
+        raise cherrypy.NotFound(objectpath)
+
+
+class Body(object):
+    """The body of the HTTP response (the response entity)."""
+    
+    def __get__(self, obj, objclass=None):
+        if obj is None:
+            # When calling on the class instead of an instance...
+            return self
+        else:
+            return obj._body
+    
+    def __set__(self, obj, value):
+        # Convert the given value to an iterable object.
+        if isinstance(value, types.FileType):
+            value = cptools.fileGenerator(value)
+        elif isinstance(value, types.GeneratorType):
+            value = flattener(value)
+        elif isinstance(value, basestring):
+            # strings get wrapped in a list because iterating over a single
+            # item list is much faster than iterating over every character
+            # in a long string.
+            value = [value]
+        elif value is None:
+            value = []
+        obj._body = value
+
+
+def flattener(input):
+    """Yield the given input, recursively iterating over each result (if needed)."""
+    for x in input:
+        if not isinstance(x, types.GeneratorType):
+            yield x
+        else:
+            for y in flattener(x):
+                yield y 
+
+
+class Response(object):
+    """An HTTP Response."""
+    
+    body = Body()
+    
+    def __init__(self):
+        self.status = None
+        self.header_list = None
+        self.body = None
+        
+        self.headers = httptools.HeaderMap()
+        content_type = cherrypy.config.get('server.default_content_type', 'text/html')
+        self.headers.update({
+            "Content-Type": content_type,
+            "Server": "CherryPy/" + cherrypy.__version__,
+            "Date": httptools.HTTPDate(),
+            "Set-Cookie": [],
+            "Content-Length": None
+        })
+        self.simple_cookie = Cookie.SimpleCookie()
+    
+    def collapse_body(self):
+        newbody = ''.join([chunk for chunk in self.body])
+        self.body = newbody
+        return newbody
+    
+    def finalize(self):
+        """Transform headers (and cookies) into cherrypy.response.header_list."""
+        
+        try:
+            code, reason, _ = httptools.validStatus(self.status)
+        except ValueError, x:
+            raise cherrypy.HTTPError(500, x.args[0])
+        
+        self.status = "%s %s" % (code, reason)
+        
+        stream = cherrypy.config.get("stream_response", False)
+        # OPTIONS requests MUST include a Content-Length of 0 if no body.
+        # Just punt and figure Content-Length for all OPTIONS requests.
+        if cherrypy.request.method == "OPTIONS":
+            stream = False
+        
+        if stream:
+            try:
+                del self.headers['Content-Length']
+            except KeyError:
+                pass
+        else:
+            # Responses which are not streamed should have a Content-Length,
+            # but allow user code to set Content-Length if desired.
+            if self.headers.get('Content-Length') is None:
+                content = self.collapse_body()
+                self.headers['Content-Length'] = len(content)
+        
+        # Transform our header dict into a sorted list of tuples.
+        self.header_list = self.headers.sorted_list()
+        
+        cookie = self.simple_cookie.output()
+        if cookie:
+            for line in cookie.split("\n"):
+                name, value = line.split(": ", 1)
+                self.header_list.append((name, value))
+    
+    dbltrace = "\n===First Error===\n\n%s\n\n===Second Error===\n\n%s\n\n"
+    
+    def handleError(self, exc):
+        """Set status, headers, and body when an unanticipated error occurs."""
+        try:
+            cherrypy.request.hooks.run('before_error_response')
+            
+            self.error_response()
+            self.finalize()
+            
+            cherrypy.request.hooks.run('after_error_response')
+            return
+        except cherrypy.HTTPRedirect, inst:
+            try:
+                inst.set_response()
+                self.finalize()
+                return
+            except (KeyboardInterrupt, SystemExit):
+                raise
+            except:
+                # Fall through to the second error handler
+                pass
+        except (KeyboardInterrupt, SystemExit):
+            raise
+        except:
+            # Fall through to the second error handler
+            pass
+        
+        # Failure in self.error_response, error hooks, or finalize.
+        # Bypass them all.
+        if cherrypy.config.get('server.show_tracebacks', False):
+            body = self.dbltrace % (_cputil.formatExc(exc),
+                                    _cputil.formatExc())
+        else:
+            body = ""
+        self.setBareError(body)
+    
+    def error_response(self):
+        # Allow logging of only *unexpected* HTTPError's.
+        if (not cherrypy.config.get('server.log_tracebacks', True)
+            and cherrypy.config.get('server.log_unhandled_tracebacks', True)):
+            cherrypy.log(traceback=True)
+        cherrypy.HTTPError(500).set_response()
+    
+    def setBareError(self, body=None):
+        self.status, self.header_list, self.body = _cputil.bareError(body)
+
 import time
 
 import cherrypy
-from cherrypy import _cphttptools
 from cherrypy.lib import cptools
-
 from cherrypy._cpengine import Engine, STOPPED, STARTING, STARTED
 
 _missing = object()
         self.blocking = True
         
         self.httpserver = None
-        # Starting in 2.2, the "httpserverclass" attr is essentially dead;
-        # no CP code uses it. Inspect "httpserver" instead.
-        self.httpserverclass = None
-        
-        # Backward compatibility:
-        self.onStopServerList = self.on_stop_server_list
-        self.onStartThreadList = self.on_start_thread_list
-        self.onStartServerList = self.on_start_server_list
-        self.onStopThreadList = self.on_stop_thread_list
     
     def start(self, init_only=False, server_class=_missing, server=None, **kwargs):
         """Main function. MUST be called from the main thread.
         Set serverClass and server to None to skip starting any HTTP server.
         """
         
-        # Read old variable names for backward compatibility
-        if 'initOnly' in kwargs:
-            init_only = kwargs['initOnly']
-        if 'serverClass' in kwargs:
-            server_class = kwargs['serverClass']
-        
         conf = cherrypy.config.get
         
         if not init_only:
             elif server_class and isinstance(server_class, basestring):
                 # Dynamically load the class from the given string
                 server_class = cptools.attributes(server_class)
-            self.httpserverclass = server_class
             if server_class is not None:
                 self.httpserver = server_class()
         else:
             if isinstance(server, basestring):
                 server = cptools.attributes(server)
-            self.httpserverclass = server.__class__
             self.httpserver = server
         
         self.blocking = not init_only
                             server_class = _missing, serverClass = None):
         """Start, then callback the given func in a new thread."""
         
-        # Read old name for backward compatibility
-        if serverClass is not None:
-            server_class = None
-        
         if args is None:
             args = ()
         if kwargs is None:
         now.day, month, now.year, now.hour, now.minute, now.second)
 
 def _cp_log_access():
-    """ Default method for logging access """
+    """Default method for logging access"""
     
     tmpl = '%(h)s %(l)s %(u)s [%(t)s] "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
-    s = tmpl % {'h': cherrypy.request.remoteHost,
+    s = tmpl % {'h': cherrypy.request.remote_host,
                 'l': '-',
                 'u': getattr(cherrypy.request, "login", None) or "-",
                 't': logtime(),
 
 
 def _cp_on_http_error(status, message):
-    """ Default _cp_on_http_error method.
+    """Default _cp_on_http_error method.
     
     status should be an int.
     """
              ('Content-Length', str(len(body)))],
             [body])
 
-def _cp_on_error():
-    """ Default _cp_on_error method """
-    # Allow logging of only *unexpected* HTTPError's.
-    if (not cherrypy.config.get('server.log_tracebacks', True)
-        and cherrypy.config.get('server.log_unhandled_tracebacks', True)):
-        cherrypy.log(traceback=True)
-    
-    cherrypy.HTTPError(500).set_response()
-
 def headers(headers):
     """ Provides a simple way to add specific headers to page handler
     Any previously set headers provided in the list of tuples will be changed
         return inner
     return wrapper
 
-_cp_filters = []
-
 
 class CherryPyWSGIServer(object):
     
-    version = "CherryPy/2.2.0"
+    version = "CherryPy/3.0.0alpha"
     ready = False
     interrupt = None
     RequestHandlerClass = HTTPRequest
 
 import ConfigParser
 import os
+_favicon_path = os.path.join(os.path.dirname(__file__), "favicon.ico")
 
 import cherrypy
 from cherrypy import _cputil
 # This configs dict holds the settings metadata for all cherrypy objects.
 # Keys are URL paths, and values are dicts.
 configs = {}
-configMap = configs # Backward compatibility
 
 default_global = {
     'server.socket_port': 8080,
     'server.thread_pool': 10,
     'server.environment': "development",
     
-    '/favicon.ico': {
-        'static_filter.on': True,
-        'static_filter.file': os.path.join(os.path.dirname(__file__), "favicon.ico"),}
+    '/favicon.ico': {'hooks.static.on': True,
+                     'hooks.static.file': _favicon_path},
     }
 
 environments = {
     "development": {
         'autoreload.on': True,
-        'log_debug_info_filter.on': True,
         'server.log_file_not_found': True,
         'server.show_tracebacks': True,
         'server.log_request_headers': True,
         },
     "staging": {
         'autoreload.on': False,
-        'log_debug_info_filter.on': False,
         'server.log_file_not_found': False,
         'server.show_tracebacks': False,
         'server.log_request_headers': False,
         },
     "production": {
         'autoreload.on': False,
-        'log_debug_info_filter.on': False,
         'server.log_file_not_found': False,
         'server.show_tracebacks': False,
         'server.log_request_headers': False,
         if path == "":
             path = "/"
         
-        if cherrypy.lowercase_api is False:
-            # We don't know for sure if user uses the new lowercase API
-            try:
-                result = configs[path][_cputil.lower_to_camel(key)]
-                break
-            except KeyError:
-                try:
-                    result = configs[path][key]
-                    break
-                except KeyError:
-                    pass
-                pass
-            
-            try:
-                # Check for a server.environment entry at this node.
-                env = configs[path]["server.environment"]
-                # For backward compatibility, check for camelCase key first
-                result = environments[env][_cputil.lower_to_camel(key)]
-                break
-            except KeyError:
-                try:
-                    env = configs[path]["server.environment"]
-                    result = environments[env][key]
-                    break
-                except KeyError:
-                    pass
-                pass
-        else:
-            # We know for sure that user uses the new lowercase api
-            try:
-                result = configs[path][key]
-                break
-            except KeyError:
-                pass
-            
-            try:
-                env = configs[path]["server.environment"]
-                result = environments[env][key]
-                break
-            except KeyError:
-                pass
+        try:
+            result = configs[path][key]
+            break
+        except KeyError:
             pass
+        
+        try:
+            env = configs[path]["server.environment"]
+            result = environments[env][key]
+            break
+        except KeyError:
+            pass
+        pass
 
         if path == "global":
             result = default_value
     else:
         return result
 
-def getAll(key):
-    """Lookup key in the current node and all of its parent nodes
-    as a list of path, value pairs.
-    """
-    # Needed by the session filter
-    
-    try:
-        results = [('global', configs['global'][key])]
-    except KeyError:
-        results = []
-    
-    try:
-        path = cherrypy.request.object_path
-    except AttributeError:
-        return results
-    
-    pathList = path.split('/')
-    
-    for n in xrange(1, len(pathList)):
-        path = '/' + '/'.join(pathList[0:n+1])
-        try:
-            results.append((path, configs[path][key]))
-        except KeyError:
-            pass
-    
-    return results
-
 
 class CaseSensitiveConfigParser(ConfigParser.ConfigParser):
     """Sub-class of ConfigParser that keeps the case of options and that raises

filters/__init__.py

-import cherrypy
-from cherrypy import _cputil
-
-
-# These are in order for a reason!
-# Entries in the input_filters and output_filters lists
-# may be either a class, or the full package name of a class.
-
-input_filters = [
-    "cherrypy.filters.cachefilter.CacheFilter",
-    "cherrypy.filters.logdebuginfofilter.LogDebugInfoFilter",
-    "cherrypy.filters.baseurlfilter.BaseUrlFilter",
-    "cherrypy.filters.virtualhostfilter.VirtualHostFilter",
-    "cherrypy.filters.decodingfilter.DecodingFilter",
-    "cherrypy.filters.sessionfilter.SessionFilter",
-    "cherrypy.filters.sessionauthenticatefilter.SessionAuthenticateFilter",
-    "cherrypy.filters.staticfilter.StaticFilter",
-    "cherrypy.filters.nsgmlsfilter.NsgmlsFilter",
-    "cherrypy.filters.tidyfilter.TidyFilter",
-    "cherrypy.filters.xmlrpcfilter.XmlRpcFilter",
-]
-
-output_filters = [
-    "cherrypy.filters.responseheadersfilter.ResponseHeadersFilter",
-    "cherrypy.filters.xmlrpcfilter.XmlRpcFilter",
-    "cherrypy.filters.encodingfilter.EncodingFilter",
-    "cherrypy.filters.tidyfilter.TidyFilter",
-    "cherrypy.filters.nsgmlsfilter.NsgmlsFilter",
-    "cherrypy.filters.logdebuginfofilter.LogDebugInfoFilter",
-    "cherrypy.filters.gzipfilter.GzipFilter",
-    "cherrypy.filters.sessionfilter.SessionFilter",
-    "cherrypy.filters.cachefilter.CacheFilter",
-]
-
-_input_methods = ['on_start_resource', 'before_request_body', 'before_main']
-_output_methods = ['before_finalize', 'on_end_resource', 'on_end_request',
-        'before_error_response', 'after_error_response']
-
-_old_input_methods = ['onStartResource', 'beforeRequestBody', 'beforeMain']
-_old_output_methods = ['beforeFinalize', 'onEndResource', 'onEndRequest',
-        'beforeErrorResponse', 'afterErrorResponse']
-
-backward_compatibility_dict = {
-    'on_start_resource': 'onStartResource',
-    'before_request_body': 'beforeRequestBody',
-    'before_main': 'beforeMain',
-    'before_finalize': 'beforeFinalize',
-    'on_end_resource': 'onEndResource',
-    'on_end_request': 'onEndRequest',
-    'before_error_response': 'beforeErrorResponse',
-    'after_error_response': 'afterErrorResponse'
-}
-
-
-def init():
-    """Initialize the filters."""
-    from cherrypy.lib import cptools
-    
-    instances = {}
-    inputs, outputs = [], []
-    
-    conf = cherrypy.config.get
-    
-    for filtercls in input_filters + conf('server.input_filters', []):
-        if isinstance(filtercls, basestring):
-            filtercls = cptools.attributes(filtercls)
-        
-        f = instances.get(filtercls)
-        if f is None:
-            f = instances[filtercls] = filtercls()
-        inputs.append(f)
-    
-    for filtercls in conf('server.output_filters', []) + output_filters:
-        if isinstance(filtercls, basestring):
-            filtercls = cptools.attributes(filtercls)
-        
-        f = instances.get(filtercls)
-        if f is None:
-            f = instances[filtercls] = filtercls()
-        outputs.append(f)
-    
-    # Transform the instance lists into a dict of methods
-    # in 2.2 we check the old camelCase filter names first
-    # to provide backward compatibility with 2.1
-    _filterhooks.clear()
-    for old_name, new_name in zip(_old_input_methods, _input_methods):
-        _filterhooks[new_name] = []
-        for f in inputs:
-            method = getattr(f, old_name, None)
-            if method:
-                _filterhooks[new_name].append(method)
-            else:
-                method = getattr(f, new_name, None)
-                if method:
-                    _filterhooks[new_name].append(method)
-    for old_name, new_name in zip(_old_output_methods, _output_methods):
-        _filterhooks[new_name] = []
-        for f in outputs:
-            method = getattr(f, old_name, None)
-            if method:
-                _filterhooks[new_name].append(method)
-            else:
-                method = getattr(f, new_name, None)
-                if method:
-                    _filterhooks[new_name].append(method)
-
-
-_filterhooks = {}
-
-
-def applyFilters(method_name, failsafe=False):
-    """Execute the given method for all registered filters."""
-    special_methods = []
-    for f in _cputil.get_special_attribute("_cp_filters", "_cpFilterList"):
-        if cherrypy.lowercase_api is False:
-            # Try old name first
-            old_method_name = backward_compatibility_dict.get(method_name)
-            method = getattr(f, old_method_name, None)
-            if (method is None):
-                method = getattr(f, method_name, None)
-            if method:
-                special_methods.append(method)
-        else:
-            # We know for sure that user uses the new lowercase API
-            method = getattr(f, method_name, None)
-            if method:
-                special_methods.append(method)
-    
-    if method_name in _input_methods:
-        # Run special filters after defaults.
-        methods = _filterhooks[method_name] + special_methods
-    else:
-        # Run special filters before defaults.
-        methods = special_methods + _filterhooks[method_name]
-
-    for method in methods:
-        # The on_start_resource, on_end_resource, and on_end_request methods
-        # are guaranteed to run even if other methods of the same name fail.
-        # We will still log the failure, but proceed on to the next method.
-        # The only way to stop all processing from one of these methods is
-        # to raise SystemExit and stop the whole server. So, trap your own
-        # errors in these methods!
-        if failsafe:
-            try:
-                method()
-            except (KeyboardInterrupt, SystemExit):
-                raise
-            except:
-                cherrypy.log(traceback=True)
-        else:
-            method()
-

filters/basefilter.py

-"""Base class for CherryPy filters."""
-
-class BaseFilter(object):
-    """
-    Base class for filters. Derive new filter classes from this, then
-    override some of the methods to add some side-effects.
-    """
-    
-    def on_start_resource(self):
-        """Called before any request processing has been done"""
-        pass
-    
-    def before_request_body(self):
-        """Called after the request header has been read/parsed"""
-        pass
-    
-    def before_main(self):
-        """ Called after the request body has been read/parsed"""
-        pass
-    
-    def before_finalize(self):
-        """Called before final output processing"""
-        pass
-    
-    def before_error_response(self):
-        """Called before _cp_on_error and/or finalizing output"""
-        pass
-    
-    def after_error_response(self):
-        """Called after _cp_on_error and finalize"""
-        pass
-    
-    def on_end_resource(self):
-        """Called after finalizing the output (status, header, and body)"""
-        pass
-    
-    def on_end_request(self):
-        """Called when the server closes the request."""
-        pass
-

filters/baseurlfilter.py

-import cherrypy
-from basefilter import BaseFilter
-
-
-class BaseUrlFilter(BaseFilter):
-    """Filter that changes the base URL.
-    
-    Useful when running a CP server behind Apache.
-    """
-    
-    def before_request_body(self):
-        if not cherrypy.config.get('base_url_filter.on', False):
-            return
-        
-        request = cherrypy.request
-        
-        port = str(cherrypy.config.get('server.socket_port', '80'))
-        if port == "80":
-            defaultUrl = 'http://localhost'
-        else:
-            defaultUrl = 'http://localhost:%s' % port
-        newBaseUrl = cherrypy.config.get('base_url_filter.base_url', defaultUrl)
-        
-        if cherrypy.config.get('base_url_filter.use_x_forwarded_host', True):
-            newBaseUrl = request.headers.get("X-Forwarded-Host", newBaseUrl)
-        
-        if newBaseUrl.find("://") == -1:
-            # add http:// or https:// if needed
-            newBaseUrl = request.base[:request.base.find("://") + 3] + newBaseUrl
-        
-        request.base = newBaseUrl

filters/cachefilter.py

-
-import threading
-import Queue
-import time
-
-import cherrypy
-import basefilter
-
-
-class MemoryCache:
-    
-    def __init__(self):
-        self.clear()
-        self.expirationQueue = Queue.Queue()
-        t = self.expirationThread = threading.Thread(target=self.expireCache,
-                                                     name='expireCache')
-        t.setDaemon(True)
-        t.start()
-    
-    def clear(self):
-        """Reset the cache to its initial, empty state."""
-        self.cache = {}
-        self.totPuts = 0
-        self.totGets = 0
-        self.totHits = 0
-        self.totExpires = 0
-        self.totNonModified = 0
-        self.cursize = 0
-    
-    def _key(self):
-        return cherrypy.config.get("cache_filter.key", cherrypy.request.browser_url)
-    key = property(_key)
-    
-    def _maxobjsize(self):
-        return cherrypy.config.get("cache_filter.maxobjsize", 100000)
-    maxobjsize = property(_maxobjsize)
-    
-    def _maxsize(self):
-        return cherrypy.config.get("cache_filter.maxsize", 10000000)
-    maxsize = property(_maxsize)
-    
-    def _maxobjects(self):
-        return cherrypy.config.get("cache_filter.maxobjects", 1000)
-    maxobjects = property(_maxobjects)
-    
-    def expireCache(self):
-        while True:
-            expirationTime, objSize, objKey = self.expirationQueue.get(block=True, timeout=None)
-            # expireCache runs in a separate thread which the servers are
-            # not aware of. It's possible that "time" will be set to None
-            # arbitrarily, so we check "while time" to avoid exceptions.
-            # See tickets #99 and #180 for more information.
-            while time and (time.time() < expirationTime):
-                time.sleep(0.1)
-            try:
-                del self.cache[objKey]
-                self.totExpires += 1
-                self.cursize -= objSize
-            except KeyError:
-                # the key may have been deleted elsewhere
-                pass
-    
-    def get(self):
-        """
-        If the content is in the cache, returns a tuple containing the 
-        expiration time, the lastModified response header and the object 
-        (rendered as a string); returns None if the key is not found.
-        """
-        self.totGets += 1
-        cacheItem = self.cache.get(self.key, None)
-        if cacheItem:
-            self.totHits += 1
-            return cacheItem
-        else:
-            return None
-    
-    def put(self, lastModified, obj):
-        # Size check no longer includes header length
-        objSize = len(obj[2])
-        totalSize = self.cursize + objSize
-        
-        # checks if there's space for the object
-        if ((objSize < self.maxobjsize) and 
-            (totalSize < self.maxsize) and 
-            (len(self.cache) < self.maxobjects)):
-            # add to the expirationQueue & cache
-            try:
-                expirationTime = time.time() + cherrypy.config.get("cache_filter.delay", 600)
-                objKey = self.key
-                self.expirationQueue.put((expirationTime, objSize, objKey))
-                self.cache[objKey] = (expirationTime, lastModified, obj)
-                self.totPuts += 1
-                self.cursize += objSize
-            except Queue.Full:
-                # can't add because the queue is full
-                return
-
-
-class CacheFilter(basefilter.BaseFilter):
-    """If the page is already stored in the cache, serves the contents.
-    If the page is not in the cache, caches the output.
-    """
-    
-    def __init__(self):
-        cache_class = cherrypy.config.get("cache_filter.cacheClass", MemoryCache)
-        cherrypy._cache = cache_class()
-    
-    def on_start_resource(self):
-        cherrypy.request.cacheable = False
-    
-    def before_main(self):
-        if not cherrypy.config.get('cache_filter.on', False):
-            return
-        
-        cacheData = cherrypy._cache.get()
-        if cacheData:
-            # found a hit! check the if-modified-since request header
-            expirationTime, lastModified, obj = cacheData
-            modifiedSince = cherrypy.request.headers.get('If-Modified-Since', None)
-            if modifiedSince is not None and modifiedSince == lastModified:
-                cherrypy._cache.totNonModified += 1
-                cherrypy.response.status = "304 Not Modified"
-                cherrypy.response.body = None
-            else:
-                # serve it & get out from the request
-                cherrypy.response.status, cherrypy.response.header_list, body = obj
-                cherrypy.response.body = body
-            raise cherrypy.RequestHandled()
-        else:
-            cherrypy.request.cacheable = True
-    
-    def before_finalize(self):
-        if not cherrypy.request.cacheable:
-            return
-        
-        cherrypy.response._cachefilter_tee = []
-        def tee(body):
-            """Tee response.body into response._cachefilter_tee (a list)."""
-            for chunk in body:
-                cherrypy.response._cachefilter_tee.append(chunk)
-                yield chunk
-        cherrypy.response.body = tee(cherrypy.response.body)
-    
-    def on_end_request(self):
-        # Close & fix the cache entry after content was fully written
-        if not cherrypy.request.cacheable:
-            return
-        
-        response = cherrypy.response
-        if response.headers.get('Pragma', None) != 'no-cache':
-            lastModified = response.headers.get('Last-Modified', None)
-            # save the cache data
-            body = ''.join([chunk for chunk in response._cachefilter_tee])
-            cherrypy._cache.put(lastModified, (response.status,
-                                               response.header_list,
-                                               body))
-
-
-def percentual(n,d):
-    """calculates the percentual, dealing with div by zeros"""
-    if d == 0:
-        return 0
-    else:
-        return (float(n)/float(d))*100
-
-def formatSize(n):
-    """formats a number as a memory size, in bytes, kbytes, MB, GB)"""
-    if n < 1024:
-        return "%4d bytes" % n
-    elif n < 1024*1024:
-        return "%4d kbytes" % (n / 1024)
-    elif n < 1024*1024*1024:
-        return "%4d MB" % (n / (1024*1024))
-    else:
-        return "%4d GB" % (n / (1024*1024*1024))
-
-
-class CacheStats:
-    
-    def index(self):
-        cherrypy.response.headers['Content-Type'] = 'text/plain'
-        cherrypy.response.headers['Pragma'] = 'no-cache'
-        cache = cherrypy._cache
-        yield "Cache statistics\n"
-        yield "Maximum object size: %s\n" % formatSize(cache.maxobjsize)
-        yield "Maximum cache size: %s\n" % formatSize(cache.maxsize)
-        yield "Maximum number of objects: %d\n" % cache.maxobjects
-        yield "Current cache size: %s\n" % formatSize(cache.cursize)
-        yield "Approximated expiration queue size: %d\n" % cache.expirationQueue.qsize()
-        yield "Number of cache entries: %d\n" % len(cache.cache)
-        yield "Total cache writes: %d\n" % cache.totPuts
-        yield "Total cache read attempts: %d\n" % cache.totGets
-        yield "Total hits: %d (%1.2f%%)\n" % (cache.totHits, percentual(cache.totHits, cache.totGets))
-        yield "Total misses: %d (%1.2f%%)\n" % (cache.totGets-cache.totHits, percentual(cache.totGets-cache.totHits, cache.totGets))
-        yield "Total expires: %d\n" % cache.totExpires
-        yield "Total non-modified content: %d\n" % cache.totNonModified
-    index.exposed = True

filters/decodingfilter.py

-import cherrypy
-from basefilter import BaseFilter
-
-class DecodingFilter(BaseFilter):
-    """Automatically decodes request parameters (except uploads)."""
-    
-    def before_main(self):
-        conf = cherrypy.config.get
-        
-        if not conf('decoding_filter.on', False):
-            return
-        
-        enc = conf('decoding_filter.encoding', None)
-        if not enc:
-            ct = cherrypy.request.headers.elements("Content-Type")
-            if ct:
-                ct = ct[0]
-                enc = ct.params.get("charset", None)
-                if (not enc) and ct.value.lower().startswith("text/"):
-                    # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
-                    # When no explicit charset parameter is provided by the
-                    # sender, media subtypes of the "text" type are defined
-                    # to have a default charset value of "ISO-8859-1" when
-                    # received via HTTP.
-                    enc = "ISO-8859-1"
-            
-            if not enc:
-                enc = conf('decoding_filter.default_encoding', "utf-8")
-        
-        try:
-            self.decode(enc)
-        except UnicodeDecodeError:
-            # IE and Firefox don't supply a charset when submitting form
-            # params with a CT of application/x-www-form-urlencoded.
-            # So after all our guessing, it could *still* be wrong.
-            # Start over with ISO-8859-1, since that seems to be preferred.
-            self.decode("ISO-8859-1")
-    
-    def decode(self, enc):
-        decodedParams = {}
-        for key, value in cherrypy.request.params.items():
-            if hasattr(value, 'file'):
-                # This is a file being uploaded: skip it
-                decodedParams[key] = value
-            elif isinstance(value, list):
-                # value is a list: decode each element
-                decodedParams[key] = [v.decode(enc) for v in value]
-            else:
-                # value is a regular string: decode it
-                decodedParams[key] = value.decode(enc)
-        
-        # Decode all or nothing, so we can try again on error.
-        cherrypy.request.params = decodedParams
-

filters/encodingfilter.py

-import cherrypy
-from basefilter import BaseFilter
-
-
-class EncodingFilter(BaseFilter):
-    """Filter that automatically encodes the response."""
-    
-    def before_finalize(self):
-        conf = cherrypy.config.get
-        if not conf('encoding_filter.on', False):
-            return
-        
-        ct = cherrypy.response.headers.elements("Content-Type")
-        if ct:
-            ct = ct[0]
-            if ct.value.lower().startswith("text/"):
-                # Set "charset=..." param on response Content-Type header
-                ct.params['charset'] = find_acceptable_charset()
-                cherrypy.response.headers["Content-Type"] = str(ct)
-
-
-def encode_stream(encoding):
-    """Encode a streaming response body.
-    
-    Use a generator wrapper, and just pray it works as the stream is
-    being written out.
-    """
-    def encoder(body):
-        for line in body:
-            yield line.encode(encoding)
-    cherrypy.response.body = encoder(cherrypy.response.body)
-    return True
-
-def encode_string(encoding):
-    """Encode a buffered response body."""
-    try:
-        body = []
-        for chunk in cherrypy.response.body:
-            body.append(chunk.encode(encoding))
-        cherrypy.response.body = body
-    except UnicodeError:
-        return False
-    else:
-        return True
-
-def find_acceptable_charset():
-    conf = cherrypy.config.get
-    response = cherrypy.response
-    
-    attempted_charsets = []
-    
-    stream = conf("stream_response", False)
-    if stream:
-        encode = encode_stream
-    else:
-        response.collapse_body()
-        encode = encode_string
-    
-    failmsg = "The response could not be encoded with %s"
-    
-    enc = conf('encoding_filter.encoding', None)
-    if enc is not None:
-        # If specified, force this encoding to be used, or fail.
-        if encode(enc):
-            return enc
-        else:
-            raise cherrypy.HTTPError(500, failmsg % enc)
-    
-    # Parse the Accept_Charset request header, and try to provide one
-    # of the requested charsets (in order of user preference).
-    default_enc = conf('encoding_filter.default_encoding', 'utf-8')
-    
-    encs = cherrypy.request.headerMap.elements('Accept-Charset')
-    if not encs:
-        # Any character-set is acceptable.
-        charsets = []
-        if encode(default_enc):
-            return default_enc
-        else:
-            raise cherrypy.HTTPError(500, failmsg % default_enc)
-    else:
-        charsets = [enc.value.lower() for enc in encs]
-        if "*" not in charsets:
-            # If no "*" is present in an Accept-Charset field, then all
-            # character sets not explicitly mentioned get a quality
-            # value of 0, except for ISO-8859-1, which gets a quality
-            # value of 1 if not explicitly mentioned.
-            iso = 'iso-8859-1'
-            if iso not in charsets:
-                attempted_charsets.append(iso)
-                if encode(iso):
-                    return iso
-        
-        for element in encs:
-            if element.qvalue > 0:
-                if element.value == "*":
-                    # Matches any charset. Try our default.
-                    if default_enc not in attempted_charsets:
-                        attempted_charsets.append(default_enc)
-                        if encode(default_enc):
-                            return default_enc
-                else:
-                    encoding = element.value
-                    if encoding not in attempted_charsets:
-                        attempted_charsets.append(encoding)
-                        if encode(encoding):
-                            return encoding
-    
-    # No suitable encoding found.
-    ac = cherrypy.request.headers.get('Accept-Charset')
-    if ac is None:
-        msg = "Your client did not send an Accept-Charset header."
-    else:
-        msg = "Your client sent this Accept-Charset header: %s." % ac
-    msg += " We tried these charsets: %s." % ", ".join(attempted_charsets)
-    raise cherrypy.HTTPError(406, msg)

filters/gzipfilter.py

-import struct
-import time
-import zlib
-
-import cherrypy
-from basefilter import BaseFilter
-
-class GzipFilter(BaseFilter):
-    """Filter that gzips the response."""
-    
-    def before_finalize(self):
-        if not cherrypy.config.get('gzip_filter.on', False):
-            return
-        
-        response = cherrypy.response
-        if not response.body:
-            # Response body is empty (might be a 304 for instance)
-            return
-        
-        def zipit():
-            # Return a generator that compresses the page
-            varies = response.headers.get("Vary", "")
-            varies = [x.strip() for x in varies.split(",") if x.strip()]
-            if "Accept-Encoding" not in varies:
-                varies.append("Accept-Encoding")
-            response.headers['Vary'] = ", ".join(varies)
-            
-            response.headers['Content-Encoding'] = 'gzip'
-            level = cherrypy.config.get('gzip_filter.compresslevel', 9)
-            response.body = self.zip_body(response.body, level)
-        
-        acceptable = cherrypy.request.headers.elements('Accept-Encoding')
-        if not acceptable:
-            # If no Accept-Encoding field is present in a request,
-            # the server MAY assume that the client will accept any
-            # content coding. In this case, if "identity" is one of
-            # the available content-codings, then the server SHOULD use
-            # the "identity" content-coding, unless it has additional
-            # information that a different content-coding is meaningful
-            # to the client.
-            return
-        
-        ct = response.headers.get('Content-Type').split(';')[0]
-        ct = ct in cherrypy.config.get('gzip_filter.mime_types', ['text/html', 'text/plain'])
-        for coding in acceptable:
-            if coding.value == 'identity' and coding.qvalue != 0:
-                return
-            if coding.value in ('gzip', 'x-gzip'):
-                if coding.qvalue == 0:
-                    return
-                if ct:
-                    zipit()
-                return
-        cherrypy.HTTPError(406, "identity, gzip").set_response()
-    
-    def write_gzip_header(self):
-        """Adapted from the gzip.py standard module code"""
-        
-        header = '\037\213'      # magic header
-        header += '\010'         # compression method
-        header += '\0'
-        header += struct.pack("<L", long(time.time()))
-        header += '\002'
-        header += '\377'
-        return header
-    
-    def write_gzip_trailer(self, crc, size):
-        footer = struct.pack("<l", crc)
-        footer += struct.pack("<L", size & 0xFFFFFFFFL)
-        return footer
-    
-    def zip_body(self, body, compress_level):
-        # Compress page
-        yield self.write_gzip_header()
-        crc = zlib.crc32("")
-        size = 0
-        zobj = zlib.compressobj(compress_level,
-                                zlib.DEFLATED, -zlib.MAX_WBITS,
-                                zlib.DEF_MEM_LEVEL, 0)
-        for line in body:
-            size += len(line)
-            crc = zlib.crc32(line, crc)
-            yield zobj.compress(line)
-        yield zobj.flush()
-        yield self.write_gzip_trailer(crc, size)

filters/logdebuginfofilter.py

-import time
-
-try:
-    import cPickle as pickle
-except ImportError:
-    import pickle
-
-import cherrypy
-from basefilter import BaseFilter
-
-
-class LogDebugInfoFilter(BaseFilter):
-    """Filter that adds debug information to the page"""
-    
-    def on_start_resource(self):
-        cherrypy.request.startBuilTime = time.time()
-    
-    def before_finalize(self):
-        if not cherrypy.config.get('log_debug_info_filter.on', False):
-            return
-        
-        mimelist = cherrypy.config.get('log_debug_info_filter.mime_types', ['text/html'])
-        ct = cherrypy.response.headers.get('Content-Type').split(';')[0]
-        if ct in mimelist:
-            body = cherrypy.response.collapse_body()
-            debuginfo = '\n'
-            
-            logAsComment = cherrypy.config.get('log_debug_info_filter.log_as_comment', False)
-            if logAsComment:
-                debuginfo += '<!-- '
-            else:
-                debuginfo += "<br/><br/>"
-            logList = []
-            
-            if cherrypy.config.get('log_debug_info_filter.log_build_time', True):
-                logList.append("Build time: %.03fs" % (
-                    time.time() - cherrypy.request.startBuilTime))
-            
-            if cherrypy.config.get('log_debug_info_filter.log_page_size', True):
-                logList.append("Page size: %.02fKB" % (
-                    len(body)/float(1024)))
-            ''' 
-            # this is not compatible with the session filter
-            if (cherrypy.config.get('log_debug_info_filter.log_session_size', True)
-                and cherrypy.config.get('session.storageType')):
-                # Pickle session data to get its size
-                try:
-                    dumpStr = pickle.dumps(cherrypy.request.sessionMap, 1)
-                    logList.append("Session data size: %.02fKB" %
-                                   (len(dumpStr) / float(1024)))
-                except:
-                    logList.append("Session data size: Unable to pickle session")
-            '''
-            
-            debuginfo += ', '.join(logList)
-            if logAsComment:
-                debuginfo += '-->'
-            
-            cherrypy.response.body = [body, debuginfo]

filters/nsgmlsfilter.py

-import os, cgi
-
-import cherrypy
-from basefilter import BaseFilter
-
-
-class NsgmlsFilter(BaseFilter):
-    """Filter that runs the response through Nsgmls.
-    """
-    
-    def before_finalize(self):
-        if not cherrypy.config.get('nsgmls_filter.on', False):
-            return
-        
-        # the tidy filter, by its very nature it's not generator friendly, 
-        # so we just collect the body and work with it.
-        original_body = cherrypy.response.collapse_body()
-        
-        fct = cherrypy.response.headers.get('Content-Type', '')
-        ct = fct.split(';')[0]
-        encoding = ''
-        i = fct.find('charset=')
-        if i != -1:
-            encoding = fct[i+8:]
-        if ct == 'text/html':
-            # Remove bits of Javascript (nsgmls doesn't seem to handle
-            #   them correctly (for instance, if <a appears in your
-            #   Javascript code nsgmls complains about it)
-            while True:
-                i = original_body.find('<script')
-                if i == -1:
-                    break
-                j = original_body.find('</script>', i)
-                if j == -1:
-                    break
-                original_body = original_body[:i] + original_body[j+9:]
-
-            tmpdir = cherrypy.config.get('nsgmls_filter.tmp_dir')
-            page_file = os.path.join(tmpdir, 'page.html')
-            err_file = os.path.join(tmpdir, 'nsgmls.err')
-            f = open(page_file, 'wb')
-            f.write(original_body)
-            f.close()
-            nsgmls_path = cherrypy.config.get('nsgmls_filter.nsgmls_path')
-            catalog_path = cherrypy.config.get('nsgmls_filter.catalog_path')
-            command = '%s -c%s -f%s -s -E10 %s' % (
-                nsgmls_path, catalog_path, err_file, page_file)
-            command = command.replace('\\', '/')
-            os.system(command)
-            f = open(err_file, 'rb')
-            err = f.read()
-            f.close()
-            errs = err.splitlines()
-            new_errs = []
-            for err in errs:
-                ignore = False
-                for err_ign in cherrypy.config.get('nsgmls_filter.errors_to_ignore', []):
-                    if err.find(err_ign) != -1:
-                        ignore = True
-                        break
-                if not ignore:
-                    new_errs.append(err)
-            if new_errs:
-                new_body = "Wrong HTML:<br />" + cgi.escape('\n'.join(new_errs)).replace('\n','<br />')