Robert Brewer avatar Robert Brewer committed af448b7

Merged branches/ticket-151 into trunk. See CHANGELOG and the branch logs for details.

Comments (0)

Files changed (35)

             try:
                 value = _cputil.unrepr(value)
             except cperror.WrongUnreprValue, s:
-                raise cperror.WrongConfigValue, "section: %s, option: %s, value: %s" % (
-                    repr(section), repr(option), repr(value))
+                msg = ("section: %s, option: %s, value: %s" %
+                       (repr(section), repr(option), repr(value)))
+                raise cperror.WrongConfigValue, msg
             configMap[section][option] = value
 
 def outputConfigMap():
     _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage')
     _cpLogMessage("Server parameters:", 'CONFIG')
-    _cpLogMessage("  server.environment: %s" % cpg.config.get('server.environment'), 'CONFIG')
+    _cpLogMessage("  server.environment: %s" % cpg.config.get('server.env'), 'CONFIG')
     _cpLogMessage("  server.logToScreen: %s" % cpg.config.get('server.logToScreen'), 'CONFIG')
     _cpLogMessage("  server.logFile: %s" % cpg.config.get('server.logFile'), 'CONFIG')
     _cpLogMessage("  server.protocolVersion: %s" % cpg.config.get('server.protocolVersion'), 'CONFIG')
     elif severity == 2:
         level = "ERROR"
     else:
-        lebel = "UNKNOWN"
+        level = "UNKNOWN"
     try:
-        logToScreen = int(cpg.config.get('server.logToScreen'))
+        logToScreen = cpg.config.get('server.logToScreen')
     except:
         logToScreen = True
     s = nowStr + ' ' + context + ' ' + level + ' ' + msg
 # Filters that are always included
 from cherrypy.lib.filter import baseurlfilter, cachefilter, \
     decodingfilter, encodingfilter, gzipfilter, logdebuginfofilter, \
-    staticfilter, tidyfilter, virtualhostfilter, xmlrpcfilter
+    sessionfilter, staticfilter, tidyfilter, virtualhostfilter, \
+    xmlrpcfilter
+
+# These are in order for a reason!
 _cpDefaultInputFilterList = [
-    cachefilter.CacheInputFilter(),
-    logdebuginfofilter.LogDebugInfoInputFilter(),
+    cachefilter.CacheFilter(),
+    logdebuginfofilter.LogDebugInfoFilter(),
     virtualhostfilter.VirtualHostFilter(),
     baseurlfilter.BaseUrlFilter(),
     decodingfilter.DecodingFilter(),
+    sessionfilter.SessionFilter(),
     staticfilter.StaticFilter(),
-    xmlrpcfilter.XmlRpcInputFilter(),
+    tidyfilter.TidyFilter(),
+    xmlrpcfilter.XmlRpcFilter(),
 ]
 _cpDefaultOutputFilterList = [
-    xmlrpcfilter.XmlRpcOutputFilter(),
+    xmlrpcfilter.XmlRpcFilter(),
     encodingfilter.EncodingFilter(),
     tidyfilter.TidyFilter(),
-    logdebuginfofilter.LogDebugInfoOutputFilter(),
+    logdebuginfofilter.LogDebugInfoFilter(),
     gzipfilter.GzipFilter(),
-    cachefilter.CacheOutputFilter(),
+    sessionfilter.SessionFilter(),
+    cachefilter.CacheFilter(),
 ]
-
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 """
 
-import cpg, sys, threading, SocketServer, _cphttptools
-import BaseHTTPServer, socket, Queue, _cputil
-from lib import autoreload
+import threading, SocketServer, BaseHTTPServer, socket, Queue
+import cpg, _cpserver, _cputil, _cphttptools
 
-def stop():
-    cpg._server.shutdown()
-
-def start():
-    if cpg.config.get("server.environment") == "development":
-        autoreload.main(_start)
-    else:
-        _start()
+try:
+    import cStringIO as StringIO
+except ImportError:
+    import StringIO
 
-def _start():
-    """ Prepare the HTTP server and then run it """
-
-    # If sessions are stored in files and we
-    # use threading, we need a lock on the file
-    if (cpg.config.get('server.threadPool') > 1) and \
-            cpg.config.get('session.storageType') == 'file':
-        cpg._sessionFileLock = threading.RLock()
-
-
-    if cpg.config.get('server.socketFile'):
-        # AF_UNIX socket
-        # TODO: Handle threading here
-        class MyCherryHTTPServer(CherryHTTPServer): address_family = socket.AF_UNIX
-    else:
-        # AF_INET socket
-        if cpg.config.get('server.threadPool') > 1:
-            MyCherryHTTPServer = PooledThreadServer
-        else:
-            MyCherryHTTPServer = CherryHTTPServer
-
-    MyCherryHTTPServer.request_queue_size = cpg.config.get('server.socketQueueSize')
-
-    # Set protocol_version
-    CherryHTTPRequestHandler.protocol_version = cpg.config.get('server.protocolVersion')
-
-    run_server(CherryHTTPRequestHandler, MyCherryHTTPServer, \
-        (cpg.config.get('server.socketHost'), cpg.config.get('server.socketPort')), \
-        cpg.config.get('server.socketFile'))
-
-def run_server(HandlerClass, ServerClass, server_address, socketFile):
-    """Run the HTTP request handler class."""
-
-    if cpg.config.get('server.socketFile'):
-        try: os.unlink(cpg.config.get('server.socketFile')) # So we can reuse the socket
-        except: pass
-        server_address = cpg.config.get('server.socketFile')
-    if cpg.config.get('server.threadPool') > 1:
-        myCherryHTTPServer = ServerClass(server_address, cpg.config.get('server.threadPool'), HandlerClass)
-    else:
-        myCherryHTTPServer = ServerClass(server_address, HandlerClass)
-    cpg._server = myCherryHTTPServer
-    if cpg.config.get('server.socketFile'):
-        try: os.chmod(socketFile, 0777) # So everyone can access the socket
-        except: pass
-    global _cpLogMessage
-    _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage')
-
-    servingWhat = "HTTP"
-    if cpg.config.get('server.socketPort'): 
-        onWhat = ("socket: ('%s', %s)" % 
-                 (cpg.config.get('server.socketHost'), cpg.config.get('server.socketPort')))
-    else: onWhat = "socket file: %s" % cpg.config.get('server.socketFile')
-    _cpLogMessage("Serving %s on %s" % (servingWhat, onWhat), 'HTTP')
-
-    try:
-        # Call the functions from cpg.server.onStartServerList
-        for func in cpg.server.onStartServerList:
-            func()
-        myCherryHTTPServer.serve_forever()
-    except KeyboardInterrupt:
-        _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP")
-        myCherryHTTPServer.server_close()
-    # Call the functions from cpg.server.onStartServerList
-    for func in cpg.server.onStopServerList:
-        func()
 
 class CherryHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
-
+    
     """CherryPy HTTP request handler with the following commands:
-
+        
         o  GET
         o  HEAD
         o  POST
         o  HOTRELOAD
-
+        
     """
-
+    
     def address_string(self):
         """ Try to do a reverse DNS based on [server]reverseDNS in the config file """
         if cpg.config.get('server.reverseDNS'):
             return BaseHTTPServer.BaseHTTPRequestHandler.address_string(self)
         else:
             return self.client_address[0]
-
+    
+    def _headerlist(self):
+        list = []
+        hit = 0
+        for line in self.headers.headers:
+            if line:
+                if line[0] in ' \t':
+                    # Continuation line. Add to previous entry.
+                    if list:
+                        list[-1][1] += " " + line.lstrip()
+                else:
+                    # New header. Add a new entry. We don't catch
+                    # ValueError here because we trust rfc822.py.
+                    name, value = line.split(":", 1)
+                    list.append((name.strip(), value.strip()))
+        return list
+    
+    def doMethod(self):
+        cpg.request.multithread = cpg.config.get("server.threadPool") > 1
+        cpg.request.multiprocess = False
+        r = _cpserver.request(self.client_address[0],
+                              self.address_string(),
+                              self.raw_requestline,
+                              self._headerlist(),
+                              self.rfile)
+        wfile = self.wfile
+        wfile.write("%s %s\r\n" % (self.protocol_version, cpg.response.status))
+        
+        for name, value in cpg.response.headers:
+            wfile.write("%s: %s\r\n" % (name, value))
+        
+        wfile.write("\r\n")
+        try:
+            for chunk in cpg.response.body:
+                wfile.write(chunk)
+        except:
+            wfile.write(">> Error in HTTP server")
+    
     def do_GET(self):
         """Serve a GET request."""
         cpg.request.method = 'GET'
-        _cphttptools.doRequest(
-            self.client_address[0],
-            self.address_string(),
-            self.raw_requestline,
-            self.headers,
-            self.rfile,
-            self.wfile
-        )
-
+        self.doMethod()
+    
     def do_HEAD(self): # Head is not implemented
         """Serve a HEAD request."""
         cpg.request.method = 'HEAD'
-        _cphttptools.doRequest(
-            self.client_address[0],
-            self.address_string(),
-            self.raw_requestline,
-            self.headers,
-            self.rfile,
-            self.wfile
-        )
-
+        self.doMethod()
+    
     def do_POST(self):
         """Serve a POST request."""
         cpg.request.method = 'POST'
-        _cphttptools.doRequest(
-            self.client_address[0],
-            self.address_string(),
-            self.raw_requestline,
-            self.headers,
-            self.rfile,
-            self.wfile
-        )
-
+        self.doMethod()
+        # What does this line do?
         self.connection = self.request
-
+    
     def log_message(self, format, *args):
         """ We have to override this to use our own logging mechanism """
         _cputil.getSpecialFunction('_cpLogMessage')(format % args, "HTTP")
 
 
 class CherryHTTPServer(BaseHTTPServer.HTTPServer):
+    
     def server_activate(self):
         """Override server_activate to set timeout on our listener socket"""
         self.socket.settimeout(1)
         BaseHTTPServer.HTTPServer.server_activate(self)
-
+    
     def server_bind(self):
         # Removed getfqdn call because it was timing out on localhost when calling gethostbyaddr
         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         self.socket.bind(self.server_address)
-
+    
     def get_request(self):
         # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode
         #  results in request sockets that are also set in nonblocking mode. Since that doesn't play
         #  well with makefile() (where wfile and rfile are set in SocketServer.py) we explicitly set
         #  the request socket to blocking
-
+        
         request, client_address = self.socket.accept()
         request.setblocking(1)
         return request, client_address
-
+    
     def handle_request(self):
         """Override handle_request to trap timeout exception."""
         try:
             # The only reason for the timeout is so we can notice keyboard
             # interrupts on Win32, which don't interrupt accept() by default
             return 1
-        except KeyboardInterrupt:
-            _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP")
+        except (KeyboardInterrupt, SystemExit):
+            _cputil.getSpecialFunction('_cpLogMessage')("<Ctrl-C> hit: shutting down", "HTTP")
             self.shutdown()
-
+    
     def serve_forever(self):
         """Override serve_forever to handle shutdown."""
         self.__running = 1
         while self.__running:
             self.handle_request()
-
+    start = serve_forever
+    
     def shutdown(self):
         self.__running = 0
+    stop = shutdown
+
 
 _SHUTDOWNREQUEST = (0,0)
 
 class ServerThread(threading.Thread):
-    def __init__(self, RequestHandlerClass, requestQueue, threadIndex):
+    
+    def __init__(self, RequestHandlerClass, requestQueue):
         threading.Thread.__init__(self)
         self._RequestHandlerClass = RequestHandlerClass
         self._requestQueue = requestQueue
-        self._threadIndex = threadIndex
-        
+    
     def run(self):
-        # Call the functions from cpg.server.onStartThreadList
-        for func in cpg.server.onStartThreadList:
-            func(self._threadIndex)
         while 1:
             request, client_address = self._requestQueue.get()
             if (request, client_address) == _SHUTDOWNREQUEST:
-                # Call the functions from cpg.server.onStopThreadList
-                for func in cpg.server.onStopThreadList:
-                    func()
                 return
-            if self.verify_request(request, client_address):            
+            if self.verify_request(request, client_address):
                 try:
                     self.process_request(request, client_address)
                 except:
                     self.close_request(request)
             else:
                 self.close_request(request)
-
+    
     def verify_request(self, request, client_address):
         """ Verify the request.  May be overridden.
             Return 1 if we should proceed with this request. """
         return 1
-
+    
     def process_request(self, request, client_address):
-        self._RequestHandlerClass(request, client_address, self)        
+        self._RequestHandlerClass(request, client_address, self)
         self.close_request(request)
-
+    
     def close_request(self, request):
         """ Called to clean up an individual request. """
         request.close()
+    
+    def handle_error(self, request, client_address):
+        """Handle an error gracefully.  May be overridden."""
+        errorBody = _cphttptools.formatExc()
+        _cputil.getSpecialFunction('_cpLogMessage')(errorBody)
 
-    def handle_error(self, request, client_address):
-        """ Handle an error gracefully.  May be overridden.
-            The default is to print a traceback and continue.
-        """
-        import traceback, StringIO
-        bodyFile=StringIO.StringIO()
-        traceback.print_exc(file=bodyFile)
-        errorBody=bodyFile.getvalue()
-        bodyFile.close()
-        _cputil.getSpecialFunction('_cpLogMessage')(errorBody)
-        
 
 class PooledThreadServer(SocketServer.TCPServer):
-
-    allow_reuse_address = 1
-
     """A TCP Server using a pool of worker threads. This is superior to the
        alternatives provided by the Python standard library, which only offer
        (1) handling a single request at a time, (2) handling each request in
        a separate process (via ForkingMixIn). It's also superior in some ways
        to the pure async approach used by Twisted because it allows a more
        straightforward and simple programming model in the face of blocking
-       requests (i.e. you don't have to bother with Deferreds).""" 
+       requests (i.e. you don't have to bother with Deferreds)."""
+    
+    allow_reuse_address = 1
+    
     def __init__(self, serverAddress, numThreads, RequestHandlerClass, ThreadClass=ServerThread):
         assert(numThreads > 0)
+        
         # I know it says "do not override", but I have to in order to implement SSL support !
         SocketServer.BaseServer.__init__(self, serverAddress, RequestHandlerClass)
         self.socket=socket.socket(self.address_family, self.socket_type)
         self.server_bind()
         self.server_activate()
-
-        self._numThreads = numThreads        
+        
+        self._numThreads = numThreads
         self._RequestHandlerClass = RequestHandlerClass
         self._ThreadClass = ThreadClass
         self._requestQueue = Queue.Queue()
         self._workerThreads = []
-
-    def createThread(self, threadIndex):
-        return self._ThreadClass(self._RequestHandlerClass, self._requestQueue, threadIndex)
-            
-    def start(self):
-        if self._workerThreads != []:
-            return
-        for i in xrange(self._numThreads):
-            self._workerThreads.append(self.createThread(i))        
-        for worker in self._workerThreads:
-            worker.start()
-            
-    def server_close(self):
-        """Override server_close to shutdown thread pool"""
-        SocketServer.TCPServer.server_close(self)
+    
+    def createThread(self):
+        return self._ThreadClass(self._RequestHandlerClass, self._requestQueue)
+    
+    def server_activate(self):
+        """Override server_activate to set timeout on our listener socket"""
+        self.socket.settimeout(1)
+        SocketServer.TCPServer.server_activate(self)
+    
+    def server_bind(self):
+        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        self.socket.bind(self.server_address)
+    
+    def shutdown(self):
+        """Gracefully shutdown a server that is serve_forever()ing."""
+        self.__running = 0
+        
+        # Must shut down threads here so the code that calls
+        # this method can know when all threads are stopped.
         for worker in self._workerThreads:
             self._requestQueue.put(_SHUTDOWNREQUEST)
         for worker in self._workerThreads:
             worker.join()
         self._workerThreads = []
-
-    def server_activate(self):
-        """Override server_activate to set timeout on our listener socket"""
-        self.socket.settimeout(1)
-        SocketServer.TCPServer.server_activate(self)
-
-    def server_bind(self):
-        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-        self.socket.bind(self.server_address)
-
-    def shutdown(self):
-        """Gracefully shutdown a server that is serve_forever()ing."""
-        self.__running = 0
-
+    stop = shutdown
+    
     def serve_forever(self):
         """Handle one request at a time until doomsday (or shutdown is called)."""
         if self._workerThreads == []:
-            self.start()
+            for i in xrange(self._numThreads):
+                self._workerThreads.append(self.createThread())
+            for worker in self._workerThreads:
+                worker.start()
         self.__running = 1
         while self.__running:
             if not self.handle_request():
                 break
-        self.server_close()            
-        
+        self.server_close()
+    start = serve_forever
+    
     def handle_request(self):
         """Override handle_request to enqueue requests rather than handle
            them synchronously. Return 1 by default, 0 to shutdown the
            server."""
         try:
             request, client_address = self.get_request()
-        except KeyboardInterrupt:
-            _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP")
+        except (KeyboardInterrupt, SystemExit):
+            _cputil.getSpecialFunction('_cpLogMessage')("<Ctrl-C> hit: shutting down", "HTTP")
             return 0
         except socket.error, e:
             return 1
         self._requestQueue.put((request, client_address))
         return 1
-
+    
     def get_request(self):
         # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode
         #  results in request sockets that are also set in nonblocking mode. Since that doesn't play
             request.setblocking(1)
         return request, client_address
 
+
+def embedded_server(handler=None):
+    """Selects and instantiates the appropriate server."""
+    
+    # Set protocol_version
+    CherryHTTPRequestHandler.protocol_version = cpg.config.get('server.protocolVersion')
+    
+    # Select the appropriate server based on config options
+    sockFile = cpg.config.get('server.socketFile')
+    threadPool = cpg.config.get('server.threadPool')
+    if sockFile:
+        # AF_UNIX socket
+        # TODO: Handle threading here
+        class ServerClass(CherryHTTPServer): address_family = socket.AF_UNIX
+        
+        # So we can reuse the socket
+        try: os.unlink(sockFile)
+        except: pass
+        
+        server_address = sockFile
+    else:
+        # AF_INET socket
+        if threadPool > 1:
+            ServerClass = PooledThreadServer
+        else:
+            ServerClass = CherryHTTPServer
+        server_address = (cpg.config.get('server.socketHost'),
+                          cpg.config.get('server.socketPort'))
+    
+    ServerClass.request_queue_size = cpg.config.get('server.socketQueueSize')
+    
+    if handler is None:
+        handler = CherryHTTPRequestHandler
+    
+    if threadPool > 1:
+        myCherryHTTPServer = ServerClass(server_address, threadPool, handler)
+    else:
+        myCherryHTTPServer = ServerClass(server_address, handler)
+    
+    # So everyone can access the socket
+    if sockFile:
+        try: os.chmod(sockFile, 0777)
+        except: pass
+    
+    return myCherryHTTPServer
+
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 """
 
-import cpg, urllib, sys, time, traceback, types, StringIO, cgi, os
-import mimetypes, sha, random, string, _cputil, cperror, Cookie, urlparse
-from lib.filter import basefilter
-import _cpdefaults
-
 """
 Common Service Code for CherryPy
 """
 
+import urllib, sys, time, traceback, types, StringIO, cgi
+import mimetypes, Cookie, urlparse
+import cpg, _cputil, cperror, _cpdefaults
+from lib.filter import basefilter
+
+from BaseHTTPServer import BaseHTTPRequestHandler
+responseCodes = BaseHTTPRequestHandler.responses
+
 mimetypes.types_map['.dwg']='image/x-dwg'
 mimetypes.types_map['.ico']='image/x-icon'
 
 weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
 monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
 
-class IndexRedirect(Exception): pass
 
-def parseFirstLine(data):
-    cpg.request.path = data.split()[1]
-    cpg.request.queryString = ""
-    cpg.request.browserUrl = cpg.request.path
-    cpg.request.paramMap = {}
-    cpg.request.paramList = [] # Only used for Xml-Rpc
-    cpg.request.filenameMap = {}
-    cpg.request.fileTypeMap = {}
-    # From http://www.cherrypy.org/ticket/141/
-    # find the queryString, or set it to "" if not found
-    if "?" in cpg.request.path:
-        cpg.request.path, cpg.request.queryString = cpg.request.path.split("?",1)
-    else:
-        cpg.request.path, cpg.request.queryString = cpg.request.path, ""
-    
-    # build a paramMap dictionary from queryString
-    pm = cpg.request.paramMap = cgi.parse_qs(cpg.request.queryString, True)
-    for key, val in pm.items():
-        if len(val) == 1:
-            pm[key] = val[0]
+class Request(object):
+    """Process a request and yield a series of response chunks.
+    
+    headers should be a list of (name, value) tuples.
+    """
+    
+    def __init__(self, clientAddress, remoteHost, requestLine, headers, rfile):
+        # When __init__ is finished, cpg.response should have three attributes:
+        #   status, e.g. "200 OK"
+        #   headers, a list of (name, value) tuples
+        #   body, an iterable yielding strings
+        # Consumer code should then access these three attributes
+        # to build the outbound stream.
+        
+        self.requestLine = requestLine
+        self.requestHeaders = headers
+        self.rfile = rfile
+        
+        # Prepare cpg.request variables
+        cpg.request.remoteAddr = clientAddress
+        cpg.request.remoteHost = remoteHost
+        cpg.request.paramList = [] # Only used for Xml-Rpc
+        cpg.request.filenameMap = {}
+        cpg.request.fileTypeMap = {}
+        cpg.request.headerMap = {}
+        cpg.request.requestLine = requestLine
+        cpg.request.simpleCookie = Cookie.SimpleCookie()
+        cpg.request.isStatic = False
+        cpg.request.processRequestBody = cpg.request.method in ("POST",)
+        cpg.request.rfile = self.rfile
+        
+        # Prepare cpg.response variables
+        cpg.response.status = None
+        cpg.response.headers = None
+        cpg.response.body = None
+        
+        year, month, day, hh, mm, ss, wd, y, z = time.gmtime(time.time())
+        date = ("%s, %02d %3s %4d %02d:%02d:%02d GMT" %
+                (weekdayname[wd], day, monthname[month], year, hh, mm, ss))
+        cpg.response.headerMap = {
+            "Content-Type": "text/html",
+            "Server": "CherryPy/" + cpg.__version__,
+            "Date": date,
+            "Set-Cookie": [],
+            "Content-Length": 0
+        }
+        cpg.response.simpleCookie = Cookie.SimpleCookie()
+        
+        self.run()
+    
+    def run(self):
+        try:
+            self.processRequestHeaders()
+            
+            try:
+                # onStart has to be after processRequestHeaders
+                # so that "path", for example, gets set.
+                applyFilters('onStartResource')
+                
+                applyFilters('beforeRequestBody')
+                if cpg.request.processRequestBody:
+                    self.processRequestBody()
+                
+                applyFilters('beforeMain')
+                if cpg.response.body is None:
+                    main()
+                
+                applyFilters('beforeFinalize')
+                finalize()
+            finally:
+                applyFilters('onEndResource')
+        except basefilter.RequestHandled:
+            pass
+        except:
+            handleError(sys.exc_info())
+    
+    def processRequestHeaders(self):
+        # Parse first line
+        path = self.requestLine.split()[1]
+        
+        # find the queryString, or set it to "" if not found
+        if "?" in path:
+            cpg.request.path, cpg.request.queryString = path.split("?", 1)
+        else:
+            cpg.request.path, cpg.request.queryString = path, ""
+        
+        # build a paramMap dictionary from queryString
+        pm = cgi.parse_qs(cpg.request.queryString, keep_blank_values=True)
+        for key, val in pm.items():
+            if len(val) == 1:
+                pm[key] = val[0]
+        cpg.request.paramMap = pm
+        
+        # Process the headers into request.headerMap
+        for name, value in self.requestHeaders:
+            name = name.title()
+            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 headerMap
+            # (but they will be correctly stored in request.simpleCookie).
+            cpg.request.headerMap[name] = value
+            
+            # Handle cookies differently because on Konqueror, multiple cookies
+            # come on different lines with the same key
+            if name == 'Cookie':
+                cpg.request.simpleCookie.load(value)
+        
+        # Set peer_certificate (in SSL mode) so the
+        # web app can examine the client certificate
+        try:
+            cpg.request.peerCertificate = self.request.get_peer_certificate()
+        except:
+            pass
+        msg = "%s - %s" % (cpg.request.remoteAddr, self.requestLine[:-2])
+        _cputil.getSpecialFunction('_cpLogMessage')(msg, "HTTP")
+        
+        cpg.request.base = "http://" + cpg.request.headerMap.get('Host', '')
+        cpg.request.browserUrl = cpg.request.base + path
+        
+        # Change objectPath in filters to change
+        # the object that will get rendered
+        cpg.request.objectPath = None
+        
+        # Save original values (in case they get modified by filters)
+        cpg.request.originalPath = cpg.request.path
+        cpg.request.originalParamMap = cpg.request.paramMap
+        cpg.request.originalParamList = cpg.request.paramList
+    
+    def processRequestBody(self):
+        # Read request body and put it in data
+        data = ""
+        length = int(cpg.request.headerMap.get("Content-Length", "0"))
+        if length:
+            data = self.rfile.read(length)
+        
+        # Put data in a StringIO so FieldStorage can read it
+        newRfile = StringIO.StringIO(data)
+        # Create a copy of headerMap with lowercase keys because
+        # FieldStorage doesn't work otherwise
+        lowerHeaderMap = {}
+        for key, value in cpg.request.headerMap.items():
+            lowerHeaderMap[key.lower()] = value
+        forms = cgi.FieldStorage(fp = newRfile, headers = lowerHeaderMap,
+                                 environ = {'REQUEST_METHOD': 'POST'},
+                                 keep_blank_values = 1)
+        
+        # In case of files being uploaded, save filenames/types in maps.
+        for key in forms.keys():
+            valueList = forms[key]
+            if isinstance(valueList, list):
+                cpg.request.paramMap[key] = []
+                cpg.request.filenameMap[key] = []
+                cpg.request.fileTypeMap[key] = []
+                for item in valueList:
+                    cpg.request.paramMap[key].append(item.value)
+                    cpg.request.filenameMap[key].append(item.filename)
+                    cpg.request.fileTypeMap[key].append(item.type)
+            else:
+                cpg.request.paramMap[key] = valueList.value
+                cpg.request.filenameMap[key] = valueList.filename
+                cpg.request.fileTypeMap[key] = valueList.type
 
-def cookHeaders(clientAddress, remoteHost, headers, requestLine):
-    """Process the headers into the request.headerMap"""
-    cpg.request.headerMap = {}
-    cpg.request.requestLine = requestLine
-    cpg.request.simpleCookie = Cookie.SimpleCookie()
 
-    # Build headerMap
-    for item in headers.items():
-        # Warning: if there is more than one header entry for cookies (AFAIK, only Konqueror does that)
-        # only the last one will remain in headerMap (but they will be correctly stored in request.simpleCookie)
-        insertIntoHeaderMap(item[0],item[1])
+# Error handling
 
-    # Handle cookies differently because on Konqueror, multiple cookies come on different lines with the same key
-    cookieList = headers.getallmatchingheaders('cookie')
-    for cookie in cookieList:
-        cpg.request.simpleCookie.load(cookie)
+dbltrace = """
+=====First Error=====
 
-    cpg.request.remoteAddr = clientAddress
-    cpg.request.remoteHost = remoteHost
+%s
 
-    # Set peer_certificate (in SSL mode) so the web app can examinate the client certificate
-    try: cpg.request.peerCertificate = self.request.get_peer_certificate()
-    except: pass
+=====Second Error=====
 
-    _cputil.getSpecialFunction('_cpLogMessage')("%s - %s" % (cpg.request.remoteAddr, requestLine[:-2]), "HTTP")
+%s
 
+"""
 
-def parsePostData(rfile):
-    # Read request body and put it in data
-    len = int(cpg.request.headerMap.get("Content-Length","0"))
-    if len: data = rfile.read(len)
-    else: data=""
+def handleError(exc):
+    """Set status, headers, and body when an error occurs."""
+    try:
+        applyFilters('beforeErrorResponse')
+        
+        # _cpOnError will probably change cpg.response.body.
+        # It may also change the headerMap, etc.
+        _cputil.getSpecialFunction('_cpOnError')()
+        
+        finalize()
+        
+        applyFilters('afterErrorResponse')
+    except:
+        # Failure in _cpOnError, error filter, or finalize.
+        # Bypass them all.
+        body = dbltrace % (formatExc(exc), formatExc())
+        cpg.response.status, cpg.response.headers, body = bareError(body)
+        cpg.response.body = [body]
 
-    # Put data in a StringIO so FieldStorage can read it
-    newRfile = StringIO.StringIO(data)
-    # Create a copy of headerMap with lowercase keys because
-    #   FieldStorage doesn't work otherwise
-    lowerHeaderMap = {}
-    for key, value in cpg.request.headerMap.items():
-        lowerHeaderMap[key.lower()] = value
-    forms = cgi.FieldStorage(fp = newRfile, headers = lowerHeaderMap, environ = {'REQUEST_METHOD':'POST'}, keep_blank_values = 1)
-    for key in forms.keys():
-        # Check if it's a list or not
-        valueList = forms[key]
-        if type(valueList) == type([]):
-            # It's a list of values
-            cpg.request.paramMap[key] = []
-            cpg.request.filenameMap[key] = []
-            cpg.request.fileTypeMap[key] = []
-            for item in valueList:
-                cpg.request.paramMap[key].append(item.value)
-                cpg.request.filenameMap[key].append(item.filename)
-                cpg.request.fileTypeMap[key].append(item.type)
+def formatExc(exc=None):
+    """formatExc(exc=None) -> exc (or sys.exc_info), formatted."""
+    if exc is None:
+        exc = sys.exc_info()
+    return "".join(traceback.format_exception(*exc))
+
+def bareError(extrabody=None):
+    """bareError(extrabody=None) -> status, headers, body.
+    
+    Returns a triple without calling any other questionable functions,
+    so it should be as error-free as possible. Call it from an HTTP server
+    if you get errors after Request() is done.
+    
+    If extrabody is None, a friendly but rather unhelpful error message
+    is set in the body. If extrabody is a string, it will be appended
+    as-is to the body.
+    """
+    
+    body = "Unrecoverable error in the server."
+    if extrabody is not None:
+        body += "\n" + extrabody
+    return ("500 Internal Server Error",
+            [('Content-Type', 'text/plain'),
+             ('Content-Length', str(len(body)))],
+            body)
+
+
+
+# Response functions
+
+def main():
+    """Obtain and set cpg.response.body."""
+    path = cpg.request.path
+    # Remove leading and trailing slash
+    path = path.strip("/")
+    # Replace quoted chars (eg %20) from url
+    path = urllib.unquote(path)
+    
+    try:
+        func, objectPathList, virtualPathList = mapPathToObject()
+    except IndexRedirect, inst:
+        # For an IndexRedirect, we don't go through the regular
+        # mechanism: we return the redirect immediately
+        newUrl = urlparse.urljoin(cpg.request.base, inst.args[0])
+        cpg.response.status = '302 Found'
+        cpg.response.headerMap['Location'] = newUrl
+        cpg.response.body = []
+        return
+     
+    # Remove "root" from objectPathList and join it to get objectPath
+    cpg.request.objectPath = '/' + '/'.join(objectPathList[1:])
+    body = func(*(virtualPathList + cpg.request.paramList),
+                **(cpg.request.paramMap))
+    
+    # build a uniform return type (iterable)
+    if isinstance(body, types.FileType):
+        body = fileGenerator(body)
+    elif isinstance(body, types.GeneratorType):
+        body = flattener(body)
+    elif isinstance(body, basestring):
+        body = [body]
+    elif body is None:
+        body = [""]
+    cpg.response.body = body
+    return body
+
+def checkStatus():
+    """Test/set cpg.response.status. Provide Reason-phrase if missing."""
+    if not cpg.response.status:
+        cpg.response.status = "200 OK"
+    else:
+        status = str(cpg.response.status)
+        parts = status.split(" ", 1)
+        if len(parts) == 1:
+            # No reason supplied.
+            code, = parts
+            reason = None
         else:
-            # It's a single value
-            # In case it's a file being uploaded, we save the filename in a map (user might need it)
-            cpg.request.paramMap[key] = valueList.value
-            cpg.request.filenameMap[key] = valueList.filename
-            cpg.request.fileTypeMap[key] = valueList.type
+            code, reason = parts
+            reason = reason.strip()
+        
+        try:
+            code = int(code)
+            assert code >= 100 and code < 600
+        except (ValueError, AssertionError):
+            code = 500
+            reason = None
+        
+        if reason is None:
+            try:
+                reason = responseCodes[code][0]
+            except (KeyError, IndexError):
+                reason = ""
+        
+        cpg.response.status = "%s %s" % (code, reason)
+    return cpg.response.status
 
-def applyFilterList(methodName):
-    filterList = _cpdefaults._cpDefaultInputFilterList + \
-        _cputil.getSpecialFunction('_cpFilterList') + \
-        _cpdefaults._cpDefaultOutputFilterList
+def finalize():
+    """Transform headerMap + cookies into cpg.response.headers."""
+    
+    checkStatus()
+    
+    if (cpg.config.get("server.protocolVersion") != "HTTP/1.1"
+        and cpg.response.headerMap.get('Content-Length') == 0):
+        buf = StringIO.StringIO()
+        for chunk in cpg.response.body:
+            buf.write(chunk)
+        buf.seek(0)
+        content = buf.read()
+        cpg.response.body = [content]
+        cpg.response.headerMap['Content-Length'] = len(content)
+    
+    # Headers
+    cpg.response.headers = []
+    for key, valueList in cpg.response.headerMap.items():
+        if not isinstance(valueList, list):
+            valueList = [valueList]
+        for value in valueList:
+            cpg.response.headers.append((key, str(value)))
+    
+    cookie = cpg.response.simpleCookie.output()
+    if cookie:
+        name, value = cookie.split(": ", 1)
+        cpg.response.headers.append((name, value))
+    return cpg.response.headers
+
+def applyFilters(methodName):
+    if methodName in ('beforeRequestBody', 'beforeMain'):
+        filterList = (_cpdefaults._cpDefaultInputFilterList +
+                      _cputil.getSpecialFunction('_cpFilterList'))
+    elif methodName in ('beforeFinalize',):
+        filterList = (_cputil.getSpecialFunction('_cpFilterList') +
+                      _cpdefaults._cpDefaultOutputFilterList)
+    else:
+        # 'onStartResource', 'onEndResource'
+        # 'beforeErrorResponse', 'afterErrorResponse'
+        filterList = (_cpdefaults._cpDefaultInputFilterList +
+                      _cputil.getSpecialFunction('_cpFilterList') +
+                      _cpdefaults._cpDefaultOutputFilterList)
     for filter in filterList:
         method = getattr(filter, methodName, None)
         if method:
             method()
 
-def insertIntoHeaderMap(key,value):
-    normalizedKey = '-'.join([s.capitalize() for s in key.split('-')])
-    cpg.request.headerMap[normalizedKey] = value
-
-def initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile):
-    parseFirstLine(requestLine)
-    cookHeaders(clientAddress, remoteHost, headers, requestLine)
-
-    cpg.request.base = "http://" + cpg.request.headerMap.get('Host', '')
-    cpg.request.browserUrl = cpg.request.base + cpg.request.browserUrl
-    cpg.request.isStatic = False
-    cpg.request.parsePostData = True
-    cpg.request.rfile = rfile
-
-    # Change objectPath in filters to change the object that will get rendered
-    cpg.request.objectPath = None 
-
-    applyFilterList('setConfig')
-    applyFilterList('afterRequestHeader')
-
-    if cpg.request.method == 'POST' and cpg.request.parsePostData:
-        parsePostData(rfile)
-
-    applyFilterList('afterRequestBody')
-
-def doRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile):
-    # creates some attributes on cpg.response so filters can use them
-    cpg.response.wfile = wfile
-    cpg.response.sendResponse = True
-    cpg.response.body = None
-
-    # Prepare response variables
-    now = time.time()
-    year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now)
-    date = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (weekdayname[wd], day, monthname[month], year, hh, mm, ss)
-    cpg.response.headerMap = {
-        "protocolVersion": cpg.config.get('server.protocolVersion'),
-        "Status": "200 OK",
-        "Content-Type": "text/html",
-        "Server": "CherryPy/" + cpg.__version__,
-        "Date": date,
-        "Set-Cookie": [],
-        "Content-Length": 0
-    }
-    cpg.response.simpleCookie = Cookie.SimpleCookie()
-
-    try:
-        try:
-            initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile)
-        except basefilter.RequestHandled:
-            # request was already fully handled; it may be a cache hit
-            return
-        except:
-            # exception raised in a filter.
-            # we don't have a response body yet, so create an empty one
-            cpg.response.body = []
-            raise
-        handleRequest(cpg.response.wfile)
-    except:
-        # TODO: in some cases exceptions and filters are conflicting; 
-        # error reporting seems to be broken in some cases. This code is 
-        # a helper to check it
-        err = ""
-        exc_info_1 = sys.exc_info()[1]
-        if hasattr(exc_info_1, 'args') and len(exc_info_1.args) >= 1:
-            err = exc_info_1.args[0]
-
-        try:
-            _cputil.getSpecialFunction('_cpOnError')()
-
-            # Still save session data
-            if cpg.config.get('session.storageType') and not cpg.request.isStatic:
-                sessionId = cpg.response.simpleCookie[cpg.config.get('session.cookieName')].value
-                expirationTime = time.time() + cpg.config.get('session.timeout') * 60
-                _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime)
-
-            applyFilterList('beforeErrorResponse')
-            wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status']))
-
-            if (cpg.response.headerMap.has_key('Content-Length') and
-                    cpg.response.headerMap['Content-Length']==0):
-                buf = StringIO.StringIO()
-                [buf.write(x) for x in cpg.response.body]
-                buf.seek(0)
-                cpg.response.body = [buf.read()]
-                cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0])
-
-            for key, valueList in cpg.response.headerMap.items():
-                if key not in ('Status', 'protocolVersion'):
-                    if type(valueList) != type([]): valueList = [valueList]
-                    for value in valueList:
-                        wfile.write('%s: %s\r\n'%(key, value))
-            wfile.write('\r\n')
-            for line in cpg.response.body:
-                wfile.write(line)
-            applyFilterList('afterErrorResponse')
-        except:
-            bodyFile = StringIO.StringIO()
-            traceback.print_exc(file = bodyFile)
-            body = bodyFile.getvalue()
-            wfile.write('%s 200 OK\r\n' % cpg.config.get('server.protocolVersion'))
-            wfile.write('Content-Type: text/plain\r\n')
-            wfile.write('Content-Length: %s\r\n' % len(body))
-            wfile.write('\r\n')
-            wfile.write(body)
-
-def sendResponse(wfile):
-    applyFilterList('beforeResponse')
-
-    # Set the content-length
-    if (cpg.response.headerMap.has_key('Content-Length') and
-            cpg.response.headerMap['Content-Length']==0):
-        buf = StringIO.StringIO()
-        [buf.write(x) for x in cpg.response.body]
-        buf.seek(0)
-        cpg.response.body = [buf.read()]
-        cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0])
-
-    # Save session data
-    if cpg.config.get('session.storageType') and not cpg.request.isStatic:
-        sessionId = cpg.response.simpleCookie[cpg.config.get('session.cookieName')].value
-        expirationTime = time.time() + cpg.config.get('session.timeout') * 60
-        _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime)
-
-    wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status']))
-    for key, valueList in cpg.response.headerMap.items():
-        if key not in ('Status', 'protocolVersion'):
-            if type(valueList) != type([]): valueList = [valueList]
-            for value in valueList:
-                wfile.write('%s: %s\r\n' % (key, value))
-
-    # Send response cookies
-    cookie = cpg.response.simpleCookie.output()
-    if cookie:
-        wfile.write(cookie+'\r\n')
-    wfile.write('\r\n')
-
-    for line in cpg.response.body:
-        wfile.write(line)
-    
-    # finalization hook for filter cleanup & logging purposes
-    applyFilterList('afterResponse')
-
-def handleRequest(wfile):
-    # Clean up expired sessions if needed:
-    now = time.time()
-    if (cpg.config.get('session.storageType') and 
-        cpg.config.get('session.cleanUpDelay') and 
-        (cpg._lastSessionCleanUpTime + cpg.config.get('session.cleanUpDelay') * 60) <= now):
-        cpg._lastSessionCleanUpTime = now
-        _cputil.getSpecialFunction('_cpCleanUpOldSessions')()
-
-    # Save original values (in case they get modified by filters)
-    cpg.request.originalPath = cpg.request.path
-    cpg.request.originalParamMap = cpg.request.paramMap
-    cpg.request.originalParamList = cpg.request.paramList
-
-    path = cpg.request.path
-    if path.startswith('/'):
-        # Remove leading slash
-        path = path[1:]
-    if path.endswith('/'):
-        # Remove trailing slash
-        path = path[:-1]
-    path = urllib.unquote(path) # Replace quoted chars (eg %20) from url
-
-    # Get session data
-    if cpg.config.get('session.storageType') and not cpg.request.isStatic:
-        now = time.time()
-        # First, get sessionId from cookie
-        try: sessionId = cpg.request.simpleCookie[cpg.config.get('session.cookieName')].value
-        except: sessionId=None
-        if sessionId:
-            # Load session data from wherever it was stored
-            sessionData = _cputil.getSpecialFunction('_cpLoadSessionData')(sessionId)
-            if sessionData == None:
-                sessionId = None
-            else:
-                cpg.request.sessionMap, expirationTime = sessionData
-                # Check that is hasn't expired
-                if now > expirationTime:
-                    # Session expired
-                    sessionId = None
-
-        # Create a new sessionId if needed
-        if not sessionId:
-            cpg.request.sessionMap = {}
-            sessionId = generateSessionId()
-            cpg.request.sessionMap['_sessionId'] = sessionId
-
-        cpg.response.simpleCookie[cpg.config.get('session.cookieName')] = sessionId
-        cpg.response.simpleCookie[cpg.config.get('session.cookieName')]['path'] = '/'
-        cpg.response.simpleCookie[cpg.config.get('session.cookieName')]['version'] = 1
-
-    if cpg.response.body is None:
-        try:
-            func, objectPathList, virtualPathList = mapPathToObject()
-        except IndexRedirect, inst:
-            # For an IndexRedirect, we don't go through the regular
-            #   mechanism: we return the redirect immediately
-            newUrl = urlparse.urljoin(cpg.request.base, inst.args[0])
-            wfile.write('%s 302\r\n' % (cpg.response.headerMap['protocolVersion']))
-            cpg.response.headerMap['Location'] = newUrl
-            for key, valueList in cpg.response.headerMap.items():
-                if key not in ('Status', 'protocolVersion'):
-                    if type(valueList) != type([]): valueList = [valueList]
-                    for value in valueList:
-                        wfile.write('%s: %s\r\n'%(key, value))
-            wfile.write('\r\n')
-            return
-             
-        # Remove "root" from objectPathList and join it to get objectPath
-        cpg.request.objectPath = '/' + '/'.join(objectPathList[1:])
-        body = func(*(virtualPathList + cpg.request.paramList), **(cpg.request.paramMap))
-    else:
-        body = cpg.response.body
-
-    # builds a uniform return type
-    if isinstance(body, types.FileType):
-        cpg.response.body = fileGenerator(body)
-    elif not isinstance(body, types.GeneratorType):
-        cpg.response.body = [body]
-    else:
-        cpg.response.body = flattener(body)
-
-    if cpg.response.sendResponse:
-        sendResponse(wfile)
-
-def fileGenerator(file, chunkSize = 65536): # 65536 = 64kb
-   """ make file objects iterable generators """
-   input = file.read(chunkSize)
-   while input:
-      yield input
-      input = file.read(chunkSize) 
-
+def fileGenerator(input, chunkSize=65536):
+    # Iterate over the file in 64k chunks
+    chunk = input.read(chunkSize)
+    while chunk:
+        yield chunk
+        chunk = input.read(chunkSize)
 
 def flattener(input):
     for x in input:
                 yield y 
 
 
-def generateSessionId():
-    s = ''
-    for i in range(50):
-        s += random.choice(string.letters+string.digits)
-    s += '%s'%time.time()
-    return sha.sha(s).hexdigest()
+# Object lookup
+
+class IndexRedirect(Exception): pass
+
 
 def getObjFromPath(objPathList):
     """ For a given objectPathList (like ['root', 'a', 'b', 'index']),
     for objname in objPathList:
         # maps virtual filenames to Python identifiers (substitutes '.' for '_')
         objname = objname.replace('.', '_')
+        if getattr(cpg, "debug", None):
+            print "Attempting to call method: %s.%s" % (root, objname)
         root = getattr(root, objname, None)
         if root is None:
             return None
-
     return root
 
-
-def mapPathToObject(path = None):
+def mapPathToObject(path=None):
     # Traverse path:
     # for /a/b?arg=val, we'll try:
     #   root.a.b.index -> redirect to /a/b/?arg=val
     #   root.a.b(arg='val')
     #   root.a.default('b', arg='val')
     #   root.default('a', 'b', arg='val')
-
+    
     # Also, we ignore trailing slashes
     # Also, a method has to have ".exposed = True" in order to be exposed
-
+    
     if path is None:
         path = cpg.request.objectPath or cpg.request.path
-    if path.startswith('/'):
-        path = path[1:] # Remove leading slash
-    if path.endswith('/'):
-        path = path[:-1] # Remove trailing slash
-
+    # Remove leading and trailing slash
+    path = path.strip("/")
+    # Replace quoted chars (eg %20) from url
+    path = urllib.unquote(path)
+    
     if not path:
         objectPathList = []
     else:
         objectPathList = path.split('/')
     objectPathList = ['root'] + objectPathList + ['index']
-
+    
+    if getattr(cpg, "debug", None):
+        print "Attempting to map path: %s" % path
+        print "    objectPathList: %s" % objectPathList
+    
     # Try successive objects... (and also keep the remaining object list)
     objCache = {}
     isFirst = True
         if isFirst:
             isFirst = False
             isSecond = True
-
+    
     # Check results of traversal
     if not foundIt:
-        raise cperror.NotFound (path)# We didn't find anything
-
+        # We didn't find anything
+        raise cperror.NotFound(path)
+    
     if isFirst:
         # We found the extra ".index"
         # Check if the original path had a trailing slash (otherwise, do
         #   a redirect)
         if cpg.request.path[-1] != '/':
             newUrl = cpg.request.path + '/'
-            if cpg.request.queryString: newUrl += cpg.request.queryString
+            if cpg.request.queryString:
+                newUrl += cpg.request.queryString
             raise IndexRedirect(newUrl)
+    
+    return candidate, objectPathList, virtualPathList
 
-    return candidate, objectPathList, virtualPathList
 
 """
 Main CherryPy module:
-    - Parses config file
-    - Creates the HTTP server
+    - Creates a server
 """
 
-import cpg, thread, _cputil, _cphttpserver, time, _cpthreadinglocal
+import threading
+import time
+import sys
+import cpg, _cputil, _cphttptools, _cpthreadinglocal
+from lib import autoreload
 
-def start(initOnly = False):
-    """
-        Main function. All it does is this:
-            - output config options
-            - create response and request objects
-            - creates HTTP server based on configFile and configMap
-            - start HTTP server
-    """
-
-    # Output config options
-    cpg.config.outputConfigMap()
-
-    # Check the config options
-    # TODO
-    # _cpconfig.checkConfigOptions()
-
-    # Create request and response object (the same objects will be used
-    #   throughout the entire life of the webserver)
-    cpg.request = _cpthreadinglocal.local()
-    cpg.response = _cpthreadinglocal.local()
-    # Create threadData object as a thread-specific all-purpose storage
-    cpg.threadData = _cpthreadinglocal.local()
-
-    # Initialize a few global variables
-    cpg._lastCacheFlushTime = time.time()
-    cpg._lastSessionCleanUpTime = time.time()
-    cpg._sessionMap = {} # Map of "cookie" -> ("session object", "expiration time")
-
-    if not initOnly:
-        _cphttpserver.start()
-
-def stop():
-    _cphttpserver.stop()
 
 # Set some special attributes for adding hooks
 onStartServerList = []
 onStartThreadList = []
 onStopServerList = []
 onStopThreadList = []
+
+def start(initOnly=False, serverClass=None):
+    if cpg.config.get("server.environment") == "development":
+        # Check initOnly. If True, we're probably not starting
+        # our own webserver, and therefore could do Very Bad Things
+        # when autoreload calls sys.exit.
+        if not initOnly:
+            autoreload.main(_start, (initOnly, serverClass))
+            return
+    
+    _start(initOnly, serverClass)
+
+def start(initOnly=False, serverClass=None):
+    """
+        Main function. All it does is this:
+            - output config options
+            - create response and request objects
+            - starts a server
+    """
+    
+    # Create request and response object (the same objects will be used
+    #   throughout the entire life of the webserver)
+    cpg.request = _cpthreadinglocal.local()
+    cpg.response = _cpthreadinglocal.local()
+    
+    # Create threadData object as a thread-specific all-purpose storage
+    cpg.threadData = _cpthreadinglocal.local()
+    
+    # Output config options (if cpg.config.get('server.logToScreen'))
+    cpg.config.outputConfigMap()
+    
+    # Check the config options
+    # TODO
+    # _cpconfig.checkConfigOptions()
+    
+    # Initialize a few global variables
+    cpg._lastCacheFlushTime = time.time()
+    cpg._lastSessionCleanUpTime = time.time()
+    cpg._sessionMap = {} # Map of "cookie" -> ("session object", "expiration time")
+    
+    # If sessions are stored in files and we
+    # use threading, we need a lock on the file
+    if (cpg.config.get('server.threadPool') > 1
+        and cpg.config.get('session.storageType') == 'file'):
+        cpg._sessionFileLock = threading.RLock()
+    
+    # Call the functions from cpg.server.onStartServerList
+    for func in cpg.server.onStartServerList:
+        func()
+    
+    if not initOnly:
+        run_server(serverClass)
+
+def run_server(serverClass=None):
+    """Prepare the requested server and then run it."""
+    
+    # Instantiate the server.
+    if serverClass is None:
+        confclass = cpg.config.get("server.class")
+        if confclass:
+            serverClass = attributes(confclass)
+        else:
+            import _cpwsgi
+            serverClass = _cpwsgi.WSGIServer
+    
+    cpg._httpserver = serverClass()
+    
+    _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage')
+    
+    servingWhat = "HTTP"
+    if cpg.config.get('server', 'socketPort'):
+        onWhat = "socket: ('%s', %s)" % (cpg.config.get('server.socketHost'),
+                                         cpg.config.get('server.socketPort'))
+    else:
+        onWhat = "socket file: %s" % cpg.config.get('server.socketFile')
+    _cpLogMessage("Serving %s on %s" % (servingWhat, onWhat), 'HTTP')
+    
+    # Start the http server.
+    try:
+        cpg._httpserver.start()
+    except (KeyboardInterrupt, SystemExit):
+        _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP")
+        stop()
+
+def modules(modulePath):
+    """Load a module and retrieve a reference to that module."""
+    try:
+        aMod = sys.modules[modulePath]
+        if aMod is None:
+            raise KeyError
+    except KeyError:
+        # The last [''] is important.
+        aMod = __import__(modulePath, globals(), locals(), [''])
+    return aMod
+
+def attributes(fullAttributeName):
+    """Load a module and retrieve an attribute of that module."""
+    
+    # Parse out the path, module, and attribute
+    lastDot = fullAttributeName.rfind(u".")
+    attrName = fullAttributeName[lastDot + 1:]
+    modPath = fullAttributeName[:lastDot]
+    
+    aMod = modules(modPath)
+    # Let an AttributeError propagate outward.
+    try:
+        anAttr = getattr(aMod, attrName)
+    except AttributeError:
+        raise AttributeError("'%s' object has no attribute '%s'"
+                             % (modPath, attrName))
+    
+    # Return a reference to the attribute.
+    return anAttr
+
+
+seen_threads = {}
+
+def request(clientAddress, remoteHost, requestLine, headers, rfile):
+    threadID = threading._get_ident()
+    if threadID not in seen_threads:
+        i = len(seen_threads) + 1
+        seen_threads[threadID] = i
+        # Call the functions from cpg.server.onStartThreadList
+        for func in cpg.server.onStartThreadList:
+            func(i)
+    return _cphttptools.Request(clientAddress, remoteHost,
+                                requestLine, headers, rfile)
+
+def stop():
+    """Shutdown CherryPy (and any HTTP servers it started)."""
+    try:
+        httpstop = cpg._httpserver.stop
+    except AttributeError:
+        pass
+    else:
+        httpstop()
+    
+    # Call the functions from cpg.server.onStopThreadList
+    for thread_ident, i in seen_threads.iteritems():
+        for func in cpg.server.onStopThreadList:
+            func(i)
+    seen_threads.clear()
+    
+    # Call the functions from cpg.server.onStopServerList
+    for func in cpg.server.onStopServerList:
+        func()

_cpthreadinglocal.py

             raise TypeError("Initialization arguments are not supported")
 
         # We need to create the thread dict in anticipation of
-        # __init__ being called, to make sire we don't cal it
+        # __init__ being called, to make sure we don't call it
         # again ourselves.
         dict = object.__getattribute__(self, '__dict__')
         currentThread().__dict__[key] = dict
 import compiler
 
 def getObj(s):
-    s="a="+s
-    return compiler.parse(s).getChildren()[1].getChildren()[0].getChildren()[1]
+    s = "a=" + s
+    p = compiler.parse(s)
+    return p.getChildren()[1].getChildren()[0].getChildren()[1]
 
 class UnknownType(Exception):
     pass
         return Builder().build(getObj(s))
     except:
         raise cperror.WrongUnreprValue, repr(s)
-
+"""
+Copyright (c) 2005, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, 
+      this list of conditions and the following disclaimer in the documentation 
+      and/or other materials provided with the distribution.
+    * Neither the name of the CherryPy Team nor the names of its contributors 
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+"""
+A WSGI application and server (see PEP 333).
+"""
+
+import threading
+import os, socket, sys, traceback, urllib
+import SocketServer, BaseHTTPServer
+import cpg, _cpserver, _cputil, _cphttptools
+
+
+def requestLine(environ):
+    # Rebuild first line of the request
+    resource = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '')
+    qString = environ.get('QUERY_STRING')
+    if qString:
+        resource += '?' + qString
+    return ('%s %s %s' % (environ['REQUEST_METHOD'],
+                          resource or '/',
+                          environ['SERVER_PROTOCOL']
+                          )
+            )
+
+headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization',
+               'CONTENT_LENGTH': 'Content-Length',
+               'CONTENT_TYPE': 'Content-Type',
+               'REMOTE_HOST': 'Remote-Host',
+               'REMOTE_ADDR': 'Remote-Addr',
+               }
+
+def translate_headers(environ):
+    for cgiName in environ:
+        translatedHeader = headerNames.get(cgiName.upper())
+        if translatedHeader:
+            yield translatedHeader, environ[cgiName]
+        elif cgiName.upper().startswith("HTTP_"):
+            # Hackish attempt at recovering original header names.
+            translatedHeader = cgiName[5:].replace("_", "-")
+            yield translatedHeader, environ[cgiName]
+
+class NullWriter(object):
+    
+    def write(self, data):
+        pass
+
+
+def wsgiApp(environ, start_response):
+    
+    # Trap screen output from BaseHTTPRequestHandler.log_message()
+    if not cpg.config.get('server.logToScreen'):
+        sys.stderr = NullWriter()
+    
+    try:
+        cpg.request.method = environ['REQUEST_METHOD']
+        cpg.request.multithread = environ['wsgi.multithread']
+        cpg.request.multiprocess = environ['wsgi.multiprocess']
+        r = _cpserver.request(environ['REMOTE_ADDR'],
+                              environ['REMOTE_ADDR'],
+                              requestLine(environ),
+                              translate_headers(environ),
+                              environ['wsgi.input'],
+                              )
+        start_response(cpg.response.status, cpg.response.headers)
+        for chunk in cpg.response.body:
+            # WSGI requires all data to be of type "str". This coercion should
+            # not take any time at all if chunk is already of type "str".
+            # If it's unicode, it could be a big performance hit (x ~500).
+            yield str(chunk)
+    except:
+        tb = _cphttptools.formatExc()
+        _cputil.getSpecialFunction('_cpLogMessage')(tb)
+        s, h, b = _cphttptools.bareError(tb)
+        start_response(s, h, sys.exc_info())
+        for chunk in b:
+            yield str(chunk)
+
+
+
+# Server components
+
+class WSGIRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+    
+    server_version = "CherryPyWSGI/2.1"
+    os_environ = dict(os.environ.items())
+    
+    def handle_one_request(self):
+        """Handle a single HTTP request. Overridden to handle all verbs."""
+        response = None
+        self.headers_sent = False
+        self.responseHeaders = []
+        try:
+            try:
+                self.raw_requestline = self.rfile.readline()
+                if not self.raw_requestline:
+                    self.close_connection = 1
+                    return
+                
+                if not self.parse_request():
+                    return
+                
+                response = wsgiApp(self.environ(), self.start_response)
+                for chunk in response:
+                    self.write(chunk)
+            except:
+                self.handleError(sys.exc_info())
+        finally:
+            if hasattr(response, 'close'):
+                response.close()
+    
+    def environ(self):
+        env = {'wsgi.version': (1, 0),
+               'wsgi.input': self.rfile,
+               'wsgi.errors': sys.stderr,
+               'wsgi.multithread': (cpg.config.get("server.threadPool") > 1),
+               'wsgi.multiprocess': False,
+               'wsgi.run_once': False,
+               'SERVER_PROTOCOL': self.request_version,
+               'GATEWAY_INTERFACE': 'CGI/1.1',
+               'CONTENT_TYPE': self.headers.get('Content-Type', ''),
+               'CONTENT_LENGTH': self.headers.get('Content-Length', ''),
+               # SCRIPT_NAME doesn't really apply to CherryPy
+               'SCRIPT_NAME': '',
+               }
+        
+        env['SERVER_NAME'] = cpg.config.get('server.socketHost')
+        env['SERVER_PORT'] = cpg.config.get('server.socketPort')
+        
+        if self.os_environ.get("HTTPS") in ('yes', 'on', '1'):
+            env['wsgi.url_scheme'] = 'https'
+        else:
+            env['wsgi.url_scheme'] = 'http'
+        
+        host, port = self.client_address[:2]
+        env['REMOTE_ADDR'] = host
+        
+        fullhost = socket.getfqdn(host)
+        if fullhost == host:
+            env['REMOTE_HOST'] = ''
+        else:
+            env['REMOTE_HOST'] = fullhost
+        
+        # Update env with results of parse_request
+        env['REQUEST_METHOD'] = self.command
+        env['SERVER_PROTOCOL'] = self.request_version
+        
+        if '?' in self.path:
+            path, query = self.path.split('?', 1)
+        else:
+            path, query = self.path, ''
+        env['PATH_INFO'] = urllib.unquote(path)
+        env['QUERY_STRING'] = query
+        
+        # Update env with additional request headers
+        for name, value in self.headers.items():
+            env['HTTP_%s' % name.replace ('-', '_').upper()] = value
+        return env
+    
+    def start_response(self, status, headers, exc_info=None):
+        if exc_info:
+            try:
+                if self.headers_sent:
+                    raise exc_info[0], exc_info[1], exc_info[2]
+            finally:
+                # avoid dangling circular ref
+                exc_info = None
+        elif self.responseHeaders:
+            raise AssertionError("Headers already set!")
+        self.status = status
+        self.responseHeaders = headers[:]
+        return self.write
+    
+    def write(self, data):
+        if not self.headers_sent:
+            code, reason = self.status.split(" ", 1)
+            self.send_response(int(code), reason)
+            for name, value in self.responseHeaders:
+                self.send_header(name, value)
+            self.end_headers()
+            self.headers_sent = True
+        self.wfile.write(data)
+    
+    def handleError(self, exc):
+        self.close_connection = 1
+        msg = _cphttptools.formatExc(exc)
+        _cputil.getSpecialFunction('_cpLogMessage')(msg, "HTTP")
+        self.status, self.headers, body = _cphttptools.bareError()
+        self.write(body)
+
+
+def WSGIServer():
+    import _cphttpserver
+    return _cphttpserver.embedded_server(WSGIRequestHandler)

lib/autoreload.py

                                args, new_environ)
         if exit_code != 3:
             return exit_code
-        
-def main(main_func):
+
+def main(main_func, args=None, kwargs=None):
     if os.environ.get("RUN_MAIN") == "true":
         
-        thread.start_new_thread(main_func, ())
-
+        if args is None:
+            args = ()
+        if kwargs is None:
+            kwargs = {}
+        thread.start_new_thread(main_func, args, kwargs)
+        
         try:
             reloader_thread()
         except KeyboardInterrupt:
             sys.exit(restart_with_reloader())
         except KeyboardInterrupt:
             _cputil.getSpecialFunction('_cpLogMessage')("<Ctrl-C> hit: shutting down", "HTTP")
-            

lib/csauthenticate.py

         cpg.response.simpleCookie[self.sessionIdCookieName]['path'] = '/'
         cpg.response.simpleCookie[self.sessionIdCookieName]['max-age'] = 31536000
         cpg.response.simpleCookie[self.sessionIdCookieName]['version'] = 1
-        cpg.response.headerMap['Status'] = 302
+        cpg.response.status = "302 Found"
         cpg.response.headerMap['Location'] = fromPage
         return ""
     doLogin.exposed = True
         cpg.response.simpleCookie[self.sessionIdCookieName]['max-age'] = 0
         cpg.response.simpleCookie[self.sessionIdCookieName]['version'] = 1
         cpg.request.login = ''
-        cpg.response.headerMap['Status'] = 302
+        cpg.response.status = "302 Found"
         cpg.response.headerMap['Location'] = 'logoutScreen'
         return ""
     doLogout.exposed = True

lib/defaultformmask.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, 
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the CherryPy Team nor the names of its contributors 
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-
-"""
-Default mask for the form.py module
-"""
-
-def defaultMask(field):
-    res="<tr><td valign=top>%s</td>"%field.label
-    if field.typ=='text':
-        res+='<td><input name="%s" type=text value="%s" size=%s></td>'%(field.name, field.currentValue, field.size)
-    elif field.typ=='forced':
-        res+='<td><input name="%s" type=hidden value="%s">%s</td>'%(field.name, field.currentValue, field.currentValue)
-    elif field.typ=='password':
-        res+='<td><input name="%s" type=password value="%s"></td>'%(field.name, field.currentValue)
-    elif field.typ=='select':
-        res+='<td><select name="%s">'%field.name
-        for option in field.optionList:
-            if type(option)==type(()):
-                optionId, optionLabel=option
-                if optionId==field.currentValue or str(optionId)==field.currentValue: res+="<option selected value=%s>%s</option>"%(optionId, optionLabel)
-                else: res+="<option value=%s>%s</option>"%(optionId, optionLabel)
-            else:
-                if option==field.currentValue: res+="<option selected>%s</option>"%option
-                else: res+="<option>%s</option>"%option
-        res+='</select></td>'
-    elif field.typ=='textarea':
-        # Size is colsxrows
-        if field.size==15: size="15x15"
-        else: size=field.size
-        cols, rows=size.split('x')
-        res+='<td><textarea name="%s" rows="%s" cols="%s">%s</textarea></td>'%(field.name, rows, cols, field.currentValue)
-    elif field.typ=='submit':
-        res+='<td><input type=submit value="%s"></td>'%field.name
-    elif field.typ=='hidden':
-        if type(field.currentValue)==type([]): currentValue=field.currentValue
-        else: currentValue=[field.currentValue]
-        res=""
-        for value in currentValue:
-            res+='<input name="%s" type=hidden value="%s">'%(field.name, value)
-        return res
-    elif field.typ=='checkbox' or field.typ=='radio':
-        res+='<td>'
-        # print "##### currentValue:", field.currentValue # TBC
-        for option in field.optionList:
-            if type(option)==type(()): optionValue, optionLabel=option
-            else: optionValue, optionLabel=option, option
-            res+='<input type="%s" name="%s" value="%s"'%(field.typ, field.name, optionValue)
-            if type(field.currentValue)==type([]):
-                if optionValue in field.currentValue: res+=' checked'
-            else:
-                if optionValue==field.currentValue: res+=' checked'
-            res+='>&nbsp;&nbsp;%s<br>'%optionLabel
-        res+='</td>'
-    if field.errorMessage:
-        res+="<td><font color=red>%s</font></td>"%field.errorMessage
-    else:
-        res+="<td>&nbsp;</td>"
-    return res+"</tr>"
-def hiddenMask(field):
-        if type(field.currentValue)==type([]): currentValue=field.currentValue
-        else: currentValue=[field.currentValue]
-        res=""
-        for value in currentValue:
-            res+='<input name="%s" type=hidden value="%s">'%(field.name, value)
-        return res
-def defaultHeader(label):
-    return "<table>"
-def defaultFooter(label):
-    return "</table>"
-def echoMask(label):
-    return label
+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, 
+      this list of conditions and the following disclaimer in the documentation 
+      and/or other materials provided with the distribution.
+    * Neither the name of the CherryPy Team nor the names of its contributors 
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+"""
+Default mask for the form.py module
+"""
+
+def defaultMask(field):
+    res="<tr><td valign=top>%s</td>"%field.label
+    if field.typ=='text':
+        res+='<td><input name="%s" type=text value="%s" size=%s></td>'%(field.name, field.currentValue, field.size)
+    elif field.typ=='forced':
+        res+='<td><input name="%s" type=hidden value="%s">%s</td>'%(field.name, field.currentValue, field.currentValue)
+    elif field.typ=='password':
+        res+='<td><input name="%s" type=password value="%s"></td>'%(field.name, field.currentValue)
+    elif field.typ=='select':
+        res+='<td><select name="%s">'%field.name
+        for option in field.optionList:
+            if type(option)==type(()):
+                optionId, optionLabel=option
+                if optionId==field.currentValue or str(optionId)==field.currentValue: res+="<option selected value=%s>%s</option>"%(optionId, optionLabel)
+                else: res+="<option value=%s>%s</option>"%(optionId, optionLabel)
+            else:
+                if option==field.currentValue: res+="<option selected>%s</option>"%option
+                else: res+="<option>%s</option>"%option
+        res+='</select></td>'
+    elif field.typ=='textarea':
+        # Size is colsxrows
+        if field.size==15: size="15x15"
+        else: size=field.size
+        cols, rows=size.split('x')
+        res+='<td><textarea name="%s" rows="%s" cols="%s">%s</textarea></td>'%(field.name, rows, cols, field.currentValue)
+    elif field.typ=='submit':
+        res+='<td><input type=submit value="%s"></td>'%field.name
+    elif field.typ=='hidden':
+        if type(field.currentValue)==type([]): currentValue=field.currentValue
+        else: currentValue=[field.currentValue]
+        res=""
+        for value in currentValue:
+            res+='<input name="%s" type=hidden value="%s">'%(field.name, value)
+        return res
+    elif field.typ=='checkbox' or field.typ=='radio':
+        res+='<td>'
+        # print "##### currentValue:", field.currentValue # TBC
+        for option in field.optionList:
+            if type(option)==type(()): optionValue, optionLabel=option
+            else: optionValue, optionLabel=option, option
+            res+='<input type="%s" name="%s" value="%s"'%(field.typ, field.name, optionValue)
+            if type(field.currentValue)==type([]):
+                if optionValue in field.currentValue: res+=' checked'
+            else:
+                if optionValue==field.currentValue: res+=' checked'
+            res+='>&nbsp;&nbsp;%s<br>'%optionLabel
+        res+='</td>'
+    if field.errorMessage:
+        res+="<td><font color=red>%s</font></td>"%field.errorMessage
+    else:
+        res+="<td>&nbsp;</td>"
+    return res+"</tr>"
+def hiddenMask(field):
+        if type(field.currentValue)==type([]): currentValue=field.currentValue
+        else: currentValue=[field.currentValue]
+        res=""
+        for value in currentValue:
+            res+='<input name="%s" type=hidden value="%s">'%(field.name, value)
+        return res
+def defaultHeader(label):
+    return "<table>"
+def defaultFooter(label):
+    return "</table>"
+def echoMask(label):
+    return label

lib/filter/basefilter.py

 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 """
 
-class RequestHandled(Exception): pass
+class RequestHandled(Exception):
+    """Exception raised when no further request handling should occur."""
+    pass
 
-class BaseInputFilter(object):
+
+class BaseFilter(object):
     """
-    Base class for input filters. Derive new filter classes from this, then
+    Base class for filters. Derive new filter classes from this, then
     override some of the methods to add some side-effects.
     """
-    def afterRequestHeader(self):
-        """ Called after the request header has been read/parsed"""
+    
+    def onStartResource(self):
+        """Called before any request processing has been done"""
+        pass
+    
+    def beforeRequestBody(self):
+        """Called after the request header has been read/parsed"""
+        pass
+    
+    def beforeMain(self):
+        """ Called after the request body has been read/parsed"""
+        pass
+    
+    def beforeFinalize(self):
+        """Called before final output processing"""
+        pass
+    
+    def beforeErrorResponse(self):
+        """Called before _cpOnError and/or finalizing output"""
+        pass
+    
+    def afterErrorResponse(self):
+        """Called after _cpOnError and finalize"""
+        pass
+    
+    def onEndResource(self):
+        """Called after finalizing the output (status, header, and body)"""
         pass
 
-    def afterRequestBody(self):
-        """ Called after the request body has been read/parsed"""
-        pass
-
-class BaseOutputFilter(object):
-    """
-    Base class for output filters. Derive new filter classes from this, then
-    override some of the methods to add some side-effects.
-    """
-    def beforeResponse(self):
-        """ Called before starting to write response """
-        pass
-
-    def afterResponse(self):
-        """ Called after writing the response (header & body included) """
-        pass
-
-    def beforeErrorResponse(self):
-        """ Called before starting to write response, after _cpOnError has
-            been called
-        """
-        pass
-
-    def afterErrorResponse(self):
-        """ Called after writing the response, after _cpOnError has
-            been called
-        """
-        pass
-

lib/filter/baseurlfilter.py

 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 """
 
-from basefilter import BaseInputFilter
+from basefilter import BaseFilter
 
-class BaseUrlFilter(BaseInputFilter):
-    """
-    Filter that changes the base URL.
+class BaseUrlFilter(BaseFilter):
+    """Filter that changes the base URL.
+    
     Useful when running a CP server behind Apache.
     """
-
-    # def __init__(self, baseUrl = 'http://localhost', useXForwardedHost = True):
-    #     # New baseUrl
-    #     self.baseUrl = baseUrl
-    #     self.useXForwardedHost = useXForwardedHost
-
-    def setConfig(self):
+    
+    def onStartResource(self):
         # We have to dynamically import cpg because Python can't handle
         #   circular module imports :-(
         global cpg
         cpg.threadData.baseUrlFilterOn = cpg.config.get('baseUrlFilter.on', False)
         cpg.threadData.baseUrlFilterBaseUrl = cpg.config.get('baseUrlFilter.baseUrl', 'http://localhost')
         cpg.threadData.baseUrlFilterUseXForwardedHost = cpg.config.get('baseUrlFilter.useXForwardedHost', True)
-
-    def afterRequestHeader(self):
+    
+    def beforeRequestBody(self):
         if not cpg.threadData.baseUrlFilterOn:
             return
+        
+        newBaseUrl = cpg.threadData.baseUrlFilterBaseUrl
         if cpg.threadData.baseUrlFilterUseXForwardedHost:
-            newBaseUrl = cpg.request.headerMap.get("X-Forwarded-Host", cpg.threadData.baseUrlFilterBaseUrl)
-        else:
-            newBaseUrl = cpg.threadData.baseUrlFilterBaseUrl
+            newBaseUrl = cpg.request.headerMap.get("X-Forwarded-Host", newBaseUrl)
+        
         if newBaseUrl.find("://") == -1:
             # add http:// or https:// if needed	
             newBaseUrl = cpg.request.base[:cpg.request.base.find("://") + 3] + newBaseUrl
-
+        
         cpg.request.browserUrl = cpg.request.browserUrl.replace(
             cpg.request.base, newBaseUrl)
         cpg.request.base = newBaseUrl

lib/filter/cachefilter.py

 import threading
 import Queue
 import time
-import cStringIO
 
-from basefilter import BaseInputFilter, BaseOutputFilter, RequestHandled
+import basefilter
 
 def defaultCacheKey():
     return cpg.request.browserUrl
-    
-class Tee:
-    """
-    Wraps a stream object; chains the content that is written and keep a 
-    copy in a StringIO for caching purposes.
-    """
-    
-    def __init__(self, wfile, maxobjsize):
-        self.wfile = wfile
-        self.cache = cStringIO.StringIO()
-        self.maxobjsize = maxobjsize
-        self.caching = True
-        self.size = 0
-        
-    def write(self, s):
-        self.wfile.write(s)
-        if self.caching:
-            self.size += len(s)
-            if self.size < self.maxobjsize:
-                self.cache.write(s)
-            else:
-                # exceeded the limit, aborts caching
-                self.stopCaching()
-        
-    def flush(self):
-        self.wfile.flush()
-            
-    def close(self):
-        self.wfile.close()
-        if self.caching:
-            self.stopCaching()
 
-    def stopCaching(self):
-        self.caching = False
-        self.cache.close()