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

Changeset 229

Show
Ignore:
Timestamp:
06/01/05 16:59:18
Author:
fumanchu
Message:

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

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/CHANGELOG.txt

    r198 r229  
     12005-06-01: 
     2    * Core (_cphttptools) was completely rewritten to use iterators throughout. (fumanchu) 
     3    * HTTP server has been decoupled from _cpserver. (fumanchu) 
     4    * New WSGI interface to the builtin http server; WSGI is now the default. (fumanchu) 
     5    * Moved session handling into a filter. (fumanchu) 
     6    * New testCore.py for testing basic request and error handling. (fumanchu) 
     7    * autoreload disabled if cpg.server.start is called with initOnly=True. (fumanchu) 
     8    * BACKWARD INCOMPATIBILITY: filters changed, see http://www.cherrypy.org/wiki/WhatsNewIn21#filter_methods. (fumanchu) 
     9 
     102005-05-20: 
    111    * BACKWARD INCOMPATIBILITY: New config system, see http://www.cherrypy.org/wiki/ConfigSystem21 (Remi) 
    212    * Fixed small bug in httptools.redirect (Remi) 
  • trunk/CHERRYPYTEAM.txt

    r214 r229  
    1 See http://trac.cherrypy.org/wiki/CherryPyTeam 
     1See http://trac.cherrypy.org/cgi-bin/trac.cgi/wiki/CherryPyTeam 
  • trunk/cherrypy/_cpconfig.py

    r218 r229  
    156156                value = _cputil.unrepr(value) 
    157157            except cperror.WrongUnreprValue, s: 
    158                 raise cperror.WrongConfigValue, "section: %s, option: %s, value: %s" % ( 
    159                     repr(section), repr(option), repr(value)) 
     158                msg = ("section: %s, option: %s, value: %s" % 
     159                       (repr(section), repr(option), repr(value))) 
     160                raise cperror.WrongConfigValue, msg 
    160161            configMap[section][option] = value 
    161162 
     
    163164    _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage') 
    164165    _cpLogMessage("Server parameters:", 'CONFIG') 
    165     _cpLogMessage("  server.environment: %s" % cpg.config.get('server.environment'), 'CONFIG') 
     166    _cpLogMessage("  server.environment: %s" % cpg.config.get('server.env'), 'CONFIG') 
    166167    _cpLogMessage("  server.logToScreen: %s" % cpg.config.get('server.logToScreen'), 'CONFIG') 
    167168    _cpLogMessage("  server.logFile: %s" % cpg.config.get('server.logFile'), 'CONFIG') 
  • trunk/cherrypy/_cpdefaults.py

    r213 r229  
    4848        level = "ERROR" 
    4949    else: 
    50         lebel = "UNKNOWN" 
     50        level = "UNKNOWN" 
    5151    try: 
    52         logToScreen = int(cpg.config.get('server.logToScreen')
     52        logToScreen = cpg.config.get('server.logToScreen'
    5353    except: 
    5454        logToScreen = True 
     
    170170from cherrypy.lib.filter import baseurlfilter, cachefilter, \ 
    171171    decodingfilter, encodingfilter, gzipfilter, logdebuginfofilter, \ 
    172     staticfilter, tidyfilter, virtualhostfilter, xmlrpcfilter 
     172    sessionfilter, staticfilter, tidyfilter, virtualhostfilter, \ 
     173    xmlrpcfilter 
     174 
     175# These are in order for a reason! 
    173176_cpDefaultInputFilterList = [ 
    174     cachefilter.CacheInputFilter(), 
    175     logdebuginfofilter.LogDebugInfoInputFilter(), 
     177    cachefilter.CacheFilter(), 
     178    logdebuginfofilter.LogDebugInfoFilter(), 
    176179    virtualhostfilter.VirtualHostFilter(), 
    177180    baseurlfilter.BaseUrlFilter(), 
    178181    decodingfilter.DecodingFilter(), 
     182    sessionfilter.SessionFilter(), 
    179183    staticfilter.StaticFilter(), 
    180     xmlrpcfilter.XmlRpcInputFilter(), 
     184    tidyfilter.TidyFilter(), 
     185    xmlrpcfilter.XmlRpcFilter(), 
    181186] 
    182187_cpDefaultOutputFilterList = [ 
    183     xmlrpcfilter.XmlRpcOutputFilter(), 
     188    xmlrpcfilter.XmlRpcFilter(), 
    184189    encodingfilter.EncodingFilter(), 
    185190    tidyfilter.TidyFilter(), 
    186     logdebuginfofilter.LogDebugInfoOutputFilter(), 
     191    logdebuginfofilter.LogDebugInfoFilter(), 
    187192    gzipfilter.GzipFilter(), 
    188     cachefilter.CacheOutputFilter(), 
     193    sessionfilter.SessionFilter(), 
     194    cachefilter.CacheFilter(), 
    189195] 
    190  
  • trunk/cherrypy/_cphttpserver.py

    r213 r229  
    2727""" 
    2828 
    29 import cpg, sys, threading, SocketServer, _cphttptools 
    30 import BaseHTTPServer, socket, Queue, _cputil 
    31 from lib import autoreload 
    32  
    33 def stop(): 
    34     cpg._server.shutdown() 
    35  
    36 def start(): 
    37     if cpg.config.get("server.environment") == "development": 
    38         autoreload.main(_start) 
    39     else: 
    40         _start() 
    41  
    42 def _start(): 
    43     """ Prepare the HTTP server and then run it """ 
    44  
    45     # If sessions are stored in files and we 
    46     # use threading, we need a lock on the file 
    47     if (cpg.config.get('server.threadPool') > 1) and \ 
    48             cpg.config.get('session.storageType') == 'file': 
    49         cpg._sessionFileLock = threading.RLock() 
    50  
    51  
    52     if cpg.config.get('server.socketFile'): 
    53         # AF_UNIX socket 
    54         # TODO: Handle threading here 
    55         class MyCherryHTTPServer(CherryHTTPServer): address_family = socket.AF_UNIX 
    56     else: 
    57         # AF_INET socket 
    58         if cpg.config.get('server.threadPool') > 1: 
    59             MyCherryHTTPServer = PooledThreadServer 
    60         else: 
    61             MyCherryHTTPServer = CherryHTTPServer 
    62  
    63     MyCherryHTTPServer.request_queue_size = cpg.config.get('server.socketQueueSize') 
    64  
    65     # Set protocol_version 
    66     CherryHTTPRequestHandler.protocol_version = cpg.config.get('server.protocolVersion') 
    67  
    68     run_server(CherryHTTPRequestHandler, MyCherryHTTPServer, \ 
    69         (cpg.config.get('server.socketHost'), cpg.config.get('server.socketPort')), \ 
    70         cpg.config.get('server.socketFile')) 
    71  
    72 def run_server(HandlerClass, ServerClass, server_address, socketFile): 
    73     """Run the HTTP request handler class.""" 
    74  
    75     if cpg.config.get('server.socketFile'): 
    76         try: os.unlink(cpg.config.get('server.socketFile')) # So we can reuse the socket 
    77         except: pass 
    78         server_address = cpg.config.get('server.socketFile') 
    79     if cpg.config.get('server.threadPool') > 1: 
    80         myCherryHTTPServer = ServerClass(server_address, cpg.config.get('server.threadPool'), HandlerClass) 
    81     else: 
    82         myCherryHTTPServer = ServerClass(server_address, HandlerClass) 
    83     cpg._server = myCherryHTTPServer 
    84     if cpg.config.get('server.socketFile'): 
    85         try: os.chmod(socketFile, 0777) # So everyone can access the socket 
    86         except: pass 
    87     global _cpLogMessage 
    88     _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage') 
    89  
    90     servingWhat = "HTTP" 
    91     if cpg.config.get('server.socketPort'):  
    92         onWhat = ("socket: ('%s', %s)" %  
    93                  (cpg.config.get('server.socketHost'), cpg.config.get('server.socketPort'))) 
    94     else: onWhat = "socket file: %s" % cpg.config.get('server.socketFile') 
    95     _cpLogMessage("Serving %s on %s" % (servingWhat, onWhat), 'HTTP') 
    96  
    97     try: 
    98         # Call the functions from cpg.server.onStartServerList 
    99         for func in cpg.server.onStartServerList: 
    100             func() 
    101         myCherryHTTPServer.serve_forever() 
    102     except KeyboardInterrupt: 
    103         _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP") 
    104         myCherryHTTPServer.server_close() 
    105     # Call the functions from cpg.server.onStartServerList 
    106     for func in cpg.server.onStopServerList: 
    107         func() 
     29import threading, SocketServer, BaseHTTPServer, socket, Queue 
     30import cpg, _cpserver, _cputil, _cphttptools 
     31 
     32try: 
     33    import cStringIO as StringIO 
     34except ImportError: 
     35    import StringIO 
     36 
    10837 
    10938class CherryHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 
    110  
     39     
    11140    """CherryPy HTTP request handler with the following commands: 
    112  
     41         
    11342        o  GET 
    11443        o  HEAD 
    11544        o  POST 
    11645        o  HOTRELOAD 
    117  
     46         
    11847    """ 
    119  
     48     
    12049    def address_string(self): 
    12150        """ Try to do a reverse DNS based on [server]reverseDNS in the config file """ 
     
    12453        else: 
    12554            return self.client_address[0] 
    126  
     55     
     56    def _headerlist(self): 
     57        list = [] 
     58        hit = 0 
     59        for line in self.headers.headers: 
     60            if line: 
     61                if line[0] in ' \t': 
     62                    # Continuation line. Add to previous entry. 
     63                    if list: 
     64                        list[-1][1] += " " + line.lstrip() 
     65                else: 
     66                    # New header. Add a new entry. We don't catch 
     67                    # ValueError here because we trust rfc822.py. 
     68                    name, value = line.split(":", 1) 
     69                    list.append((name.strip(), value.strip())) 
     70        return list 
     71     
     72    def doMethod(self): 
     73        cpg.request.multithread = cpg.config.get("server.threadPool") > 1 
     74        cpg.request.multiprocess = False 
     75        r = _cpserver.request(self.client_address[0], 
     76                              self.address_string(), 
     77                              self.raw_requestline, 
     78                              self._headerlist(), 
     79                              self.rfile) 
     80        wfile = self.wfile 
     81        wfile.write("%s %s\r\n" % (self.protocol_version, cpg.response.status)) 
     82         
     83        for name, value in cpg.response.headers: 
     84            wfile.write("%s: %s\r\n" % (name, value)) 
     85         
     86        wfile.write("\r\n") 
     87        try: 
     88            for chunk in cpg.response.body: 
     89                wfile.write(chunk) 
     90        except: 
     91            wfile.write(">> Error in HTTP server") 
     92     
    12793    def do_GET(self): 
    12894        """Serve a GET request.""" 
    12995        cpg.request.method = 'GET' 
    130         _cphttptools.doRequest( 
    131             self.client_address[0], 
    132             self.address_string(), 
    133             self.raw_requestline, 
    134             self.headers, 
    135             self.rfile, 
    136             self.wfile 
    137         ) 
    138  
     96        self.doMethod() 
     97     
    13998    def do_HEAD(self): # Head is not implemented 
    14099        """Serve a HEAD request.""" 
    141100        cpg.request.method = 'HEAD' 
    142         _cphttptools.doRequest( 
    143             self.client_address[0], 
    144             self.address_string(), 
    145             self.raw_requestline, 
    146             self.headers, 
    147             self.rfile, 
    148             self.wfile 
    149         ) 
    150  
     101        self.doMethod() 
     102     
    151103    def do_POST(self): 
    152104        """Serve a POST request.""" 
    153105        cpg.request.method = 'POST' 
    154         _cphttptools.doRequest( 
    155             self.client_address[0], 
    156             self.address_string(), 
    157             self.raw_requestline, 
    158             self.headers, 
    159             self.rfile, 
    160             self.wfile 
    161         ) 
    162  
     106        self.doMethod() 
     107        # What does this line do? 
    163108        self.connection = self.request 
    164  
     109     
    165110    def log_message(self, format, *args): 
    166111        """ We have to override this to use our own logging mechanism """ 
     
    169114 
    170115class CherryHTTPServer(BaseHTTPServer.HTTPServer): 
     116     
    171117    def server_activate(self): 
    172118        """Override server_activate to set timeout on our listener socket""" 
    173119        self.socket.settimeout(1) 
    174120        BaseHTTPServer.HTTPServer.server_activate(self) 
    175  
     121     
    176122    def server_bind(self): 
    177123        # Removed getfqdn call because it was timing out on localhost when calling gethostbyaddr 
    178124        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
    179125        self.socket.bind(self.server_address) 
    180  
     126     
    181127    def get_request(self): 
    182128        # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode 
     
    184130        #  well with makefile() (where wfile and rfile are set in SocketServer.py) we explicitly set 
    185131        #  the request socket to blocking 
    186  
     132         
    187133        request, client_address = self.socket.accept() 
    188134        request.setblocking(1) 
    189135        return request, client_address 
    190  
     136     
    191137    def handle_request(self): 
    192138        """Override handle_request to trap timeout exception.""" 
     
    197143            # interrupts on Win32, which don't interrupt accept() by default 
    198144            return 1 
    199         except KeyboardInterrupt
    200             _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP") 
     145        except (KeyboardInterrupt, SystemExit)
     146            _cputil.getSpecialFunction('_cpLogMessage')("<Ctrl-C> hit: shutting down", "HTTP") 
    201147            self.shutdown() 
    202  
     148     
    203149    def serve_forever(self): 
    204150        """Override serve_forever to handle shutdown.""" 
     
    206152        while self.__running: 
    207153            self.handle_request() 
    208  
     154    start = serve_forever 
     155     
    209156    def shutdown(self): 
    210157        self.__running = 0 
     158    stop = shutdown 
     159 
    211160 
    212161_SHUTDOWNREQUEST = (0,0) 
    213162 
    214163class ServerThread(threading.Thread): 
    215     def __init__(self, RequestHandlerClass, requestQueue, threadIndex): 
     164     
     165    def __init__(self, RequestHandlerClass, requestQueue): 
    216166        threading.Thread.__init__(self) 
    217167        self._RequestHandlerClass = RequestHandlerClass 
    218168        self._requestQueue = requestQueue 
    219         self._threadIndex = threadIndex 
    220          
     169     
    221170    def run(self): 
    222         # Call the functions from cpg.server.onStartThreadList 
    223         for func in cpg.server.onStartThreadList: 
    224             func(self._threadIndex) 
    225171        while 1: 
    226172            request, client_address = self._requestQueue.get() 
    227173            if (request, client_address) == _SHUTDOWNREQUEST: 
    228                 # Call the functions from cpg.server.onStopThreadList 
    229                 for func in cpg.server.onStopThreadList: 
    230                     func() 
    231174                return 
    232             if self.verify_request(request, client_address):             
     175            if self.verify_request(request, client_address): 
    233176                try: 
    234177                    self.process_request(request, client_address) 
     
    238181            else: 
    239182                self.close_request(request) 
    240  
     183     
    241184    def verify_request(self, request, client_address): 
    242185        """ Verify the request.  May be overridden. 
    243186            Return 1 if we should proceed with this request. """ 
    244187        return 1 
    245  
     188     
    246189    def process_request(self, request, client_address): 
    247         self._RequestHandlerClass(request, client_address, self)         
     190        self._RequestHandlerClass(request, client_address, self) 
    248191        self.close_request(request) 
    249  
     192     
    250193    def close_request(self, request): 
    251194        """ Called to clean up an individual request. """ 
    252195        request.close() 
    253  
     196     
    254197    def handle_error(self, request, client_address): 
    255         """ Handle an error gracefully.  May be overridden. 
    256             The default is to print a traceback and continue. 
    257         """ 
    258         import traceback, StringIO 
    259         bodyFile=StringIO.StringIO() 
    260         traceback.print_exc(file=bodyFile) 
    261         errorBody=bodyFile.getvalue() 
    262         bodyFile.close() 
     198        """Handle an error gracefully.  May be overridden.""" 
     199        errorBody = _cphttptools.formatExc() 
    263200        _cputil.getSpecialFunction('_cpLogMessage')(errorBody) 
    264          
     201 
    265202 
    266203class PooledThreadServer(SocketServer.TCPServer): 
    267  
    268     allow_reuse_address = 1 
    269  
    270204    """A TCP Server using a pool of worker threads. This is superior to the 
    271205       alternatives provided by the Python standard library, which only offer 
     
    275209       to the pure async approach used by Twisted because it allows a more 
    276210       straightforward and simple programming model in the face of blocking 
    277        requests (i.e. you don't have to bother with Deferreds)."""  
     211       requests (i.e. you don't have to bother with Deferreds).""" 
     212     
     213    allow_reuse_address = 1 
     214     
    278215    def __init__(self, serverAddress, numThreads, RequestHandlerClass, ThreadClass=ServerThread): 
    279216        assert(numThreads > 0) 
     217         
    280218        # I know it says "do not override", but I have to in order to implement SSL support ! 
    281219        SocketServer.BaseServer.__init__(self, serverAddress, RequestHandlerClass) 
     
    283221        self.server_bind() 
    284222        self.server_activate() 
    285  
    286         self._numThreads = numThreads         
     223         
     224        self._numThreads = numThreads 
    287225        self._RequestHandlerClass = RequestHandlerClass 
    288226        self._ThreadClass = ThreadClass 
    289227        self._requestQueue = Queue.Queue() 
    290228        self._workerThreads = [] 
    291  
    292     def createThread(self, threadIndex): 
    293         return self._ThreadClass(self._RequestHandlerClass, self._requestQueue, threadIndex) 
    294              
    295     def start(self): 
    296         if self._workerThreads != []: 
    297             return 
    298         for i in xrange(self._numThreads): 
    299             self._workerThreads.append(self.createThread(i))         
    300         for worker in self._workerThreads: 
    301             worker.start() 
    302              
    303     def server_close(self): 
    304         """Override server_close to shutdown thread pool""" 
    305         SocketServer.TCPServer.server_close(self) 
     229     
     230    def createThread(self): 
     231        return self._ThreadClass(self._RequestHandlerClass, self._requestQueue) 
     232     
     233    def server_activate(self): 
     234        """Override server_activate to set timeout on our listener socket""" 
     235        self.socket.settimeout(1) 
     236        SocketServer.TCPServer.server_activate(self) 
     237     
     238    def server_bind(self): 
     239        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
     240        self.socket.bind(self.server_address) 
     241     
     242    def shutdown(self): 
     243        """Gracefully shutdown a server that is serve_forever()ing.""" 
     244        self.__running = 0 
     245         
     246        # Must shut down threads here so the code that calls 
     247        # this method can know when all threads are stopped. 
    306248        for worker in self._workerThreads: 
    307249            self._requestQueue.put(_SHUTDOWNREQUEST) 
     
    309251            worker.join() 
    310252        self._workerThreads = [] 
    311  
    312     def server_activate(self): 
    313         """Override server_activate to set timeout on our listener socket""" 
    314         self.socket.settimeout(1) 
    315         SocketServer.TCPServer.server_activate(self) 
    316  
    317     def server_bind(self): 
    318         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
    319         self.socket.bind(self.server_address) 
    320  
    321     def shutdown(self): 
    322         """Gracefully shutdown a server that is serve_forever()ing.""" 
    323         self.__running = 0 
    324  
     253    stop = shutdown 
     254     
    325255    def serve_forever(self): 
    326256        """Handle one request at a time until doomsday (or shutdown is called).""" 
    327257        if self._workerThreads == []: 
    328             self.start() 
     258            for i in xrange(self._numThreads): 
     259                self._workerThreads.append(self.createThread()) 
     260            for worker in self._workerThreads: 
     261                worker.start() 
    329262        self.__running = 1 
    330263        while self.__running: 
    331264            if not self.handle_request(): 
    332265                break 
    333         self.server_close()             
    334          
     266        self.server_close() 
     267    start = serve_forever 
     268     
    335269    def handle_request(self): 
    336270        """Override handle_request to enqueue requests rather than handle 
     
    339273        try: 
    340274            request, client_address = self.get_request() 
    341         except KeyboardInterrupt
    342             _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP") 
     275        except (KeyboardInterrupt, SystemExit)
     276            _cputil.getSpecialFunction('_cpLogMessage')("<Ctrl-C> hit: shutting down", "HTTP") 
    343277            return 0 
    344278        except socket.error, e: 
     
    346280        self._requestQueue.put((request, client_address)) 
    347281        return 1 
    348  
     282     
    349283    def get_request(self): 
    350284        # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode 
     
    358292        return request, client_address 
    359293 
     294 
     295def embedded_server(handler=None): 
     296    """Selects and instantiates the appropriate server.""" 
     297     
     298    # Set protocol_version 
     299    CherryHTTPRequestHandler.protocol_version = cpg.config.get('server.protocolVersion') 
     300     
     301    # Select the appropriate server based on config options 
     302    sockFile = cpg.config.get('server.socketFile') 
     303    threadPool = cpg.config.get('server.threadPool') 
     304    if sockFile: 
     305        # AF_UNIX socket 
     306        # TODO: Handle threading here 
     307        class ServerClass(CherryHTTPServer): address_family = socket.AF_UNIX 
     308         
     309        # So we can reuse the socket 
     310        try: os.unlink(sockFile) 
     311        except: pass 
     312         
     313        server_address = sockFile 
     314    else: 
     315        # AF_INET socket 
     316        if threadPool > 1: 
     317            ServerClass = PooledThreadServer 
     318        else: 
     319            ServerClass = CherryHTTPServer 
     320        server_address = (cpg.config.get('server.socketHost'), 
     321                          cpg.config.get('server.socketPort')) 
     322     
     323    ServerClass.request_queue_size = cpg.config.get('server.socketQueueSize') 
     324     
     325    if handler is None: 
     326        handler = CherryHTTPRequestHandler 
     327     
     328    if threadPool > 1: 
     329        myCherryHTTPServer = ServerClass(server_address, threadPool, handler) 
     330    else: 
     331        myCherryHTTPServer = ServerClass(server_address, handler) 
     332     
     333    # So everyone can access the socket 
     334    if sockFile: 
     335        try: os.chmod(sockFile, 0777) 
     336        except: pass 
     337     
     338    return myCherryHTTPServer 
     339 
  • trunk/cherrypy/_cphttptools.py

    r213 r229  
    2727""" 
    2828 
    29 import cpg, urllib, sys, time, traceback, types, StringIO, cgi, os 
    30 import mimetypes, sha, random, string, _cputil, cperror, Cookie, urlparse 
    31 from lib.filter import basefilter 
    32 import _cpdefaults 
    33  
    3429""" 
    3530Common Service Code for CherryPy 
    3631""" 
    3732 
     33import urllib, sys, time, traceback, types, StringIO, cgi 
     34import mimetypes, Cookie, urlparse 
     35import cpg, _cputil, cperror, _cpdefaults 
     36from lib.filter import basefilter 
     37 
     38from BaseHTTPServer import BaseHTTPRequestHandler 
     39responseCodes = BaseHTTPRequestHandler.responses 
     40 
    3841mimetypes.types_map['.dwg']='image/x-dwg' 
    3942mimetypes.types_map['.ico']='image/x-icon' 
     
    4245monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 
    4346 
    44 class IndexRedirect(Exception): pass 
    45  
    46 def parseFirstLine(data): 
    47     cpg.request.path = data.split()[1] 
    48     cpg.request.queryString = "" 
    49     cpg.request.browserUrl = cpg.request.path 
    50     cpg.request.paramMap = {} 
    51     cpg.request.paramList = [] # Only used for Xml-Rpc 
    52     cpg.request.filenameMap = {} 
    53     cpg.request.fileTypeMap = {} 
    54     # From http://www.cherrypy.org/ticket/141/ 
    55     # find the queryString, or set it to "" if not found 
    56     if "?" in cpg.request.path: 
    57         cpg.request.path, cpg.request.queryString = cpg.request.path.split("?",1) 
     47 
     48class Request(object): 
     49    """Process a request and yield a series of response chunks. 
     50     
     51    headers should be a list of (name, value) tuples. 
     52    """ 
     53     
     54    def __init__(self, clientAddress, remoteHost, requestLine, headers, rfile): 
     55        # When __init__ is finished, cpg.response should have three attributes: 
     56        #   status, e.g. "200 OK" 
     57        #   headers, a list of (name, value) tuples 
     58        #   body, an iterable yielding strings 
     59        # Consumer code should then access these three attributes 
     60        # to build the outbound stream. 
     61         
     62        self.requestLine = requestLine 
     63        self.requestHeaders = headers 
     64        self.rfile = rfile 
     65         
     66        # Prepare cpg.request variables 
     67        cpg.request.remoteAddr = clientAddress 
     68        cpg.request.remoteHost = remoteHost 
     69        cpg.request.paramList = [] # Only used for Xml-Rpc 
     70        cpg.request.filenameMap = {} 
     71        cpg.request.fileTypeMap = {} 
     72        cpg.request.headerMap = {} 
     73        cpg.request.requestLine = requestLine 
     74        cpg.request.simpleCookie = Cookie.SimpleCookie() 
     75        cpg.request.isStatic = False 
     76        cpg.request.processRequestBody = cpg.request.method in ("POST",) 
     77        cpg.request.rfile = self.rfile 
     78         
     79        # Prepare cpg.response variables 
     80        cpg.response.status = None 
     81        cpg.response.headers = None 
     82        cpg.response.body = None 
     83         
     84        year, month, day, hh, mm, ss, wd, y, z = time.gmtime(time.time()) 
     85        date = ("%s, %02d %3s %4d %02d:%02d:%02d GMT" % 
     86                (weekdayname[wd], day, monthname[month], year, hh, mm, ss)) 
     87        cpg.response.headerMap = { 
     88            "Content-Type": "text/html", 
     89            "Server": "CherryPy/" + cpg.__version__, 
     90            "Date": date, 
     91            "Set-Cookie": [], 
     92            "Content-Length": 0 
     93        } 
     94        cpg.response.simpleCookie = Cookie.SimpleCookie() 
     95         
     96        self.run() 
     97     
     98    def run(self): 
     99        try: 
     100            self.processRequestHeaders() 
     101             
     102            try: 
     103                # onStart has to be after processRequestHeaders 
     104                # so that "path", for example, gets set. 
     105                applyFilters('onStartResource') 
     106                 
     107                applyFilters('beforeRequestBody') 
     108                if cpg.request.processRequestBody: 
     109                    self.processRequestBody() 
     110                 
     111                applyFilters('beforeMain') 
     112                if cpg.response.body is None: 
     113                    main() 
     114                 
     115                applyFilters('beforeFinalize') 
     116                finalize() 
     117            finally: 
     118                applyFilters('onEndResource') 
     119        except basefilter.RequestHandled: 
     120            pass 
     121        except: 
     122            handleError(sys.exc_info()) 
     123     
     124    def processRequestHeaders(self): 
     125        # Parse first line 
     126        path = self.requestLine.split()[1] 
     127         
     128        # find the queryString, or set it to "" if not found 
     129        if "?" in path: 
     130            cpg.request.path, cpg.request.queryString = path.split("?", 1) 
     131        else: 
     132            cpg.request.path, cpg.request.queryString = path, "" 
     133         
     134        # build a paramMap dictionary from queryString 
     135        pm = cgi.parse_qs(cpg.request.queryString, keep_blank_values=True) 
     136        for key, val in pm.items(): 
     137            if len(val) == 1: 
     138                pm[key] = val[0] 
     139        cpg.request.paramMap = pm 
     140         
     141        # Process the headers into request.headerMap 
     142        for name, value in self.requestHeaders: 
     143            name = name.title() 
     144            value = value.strip() 
     145            # Warning: if there is more than one header entry for cookies (AFAIK, 
     146            # only Konqueror does that), only the last one will remain in headerMap 
     147            # (but they will be correctly stored in request.simpleCookie). 
     148            cpg.request.headerMap[name] = value 
     149             
     150            # Handle cookies differently because on Konqueror, multiple cookies 
     151            # come on different lines with the same key 
     152            if name == 'Cookie': 
     153                cpg.request.simpleCookie.load(value) 
     154         
     155        # Set peer_certificate (in SSL mode) so the 
     156        # web app can examine the client certificate 
     157        try: 
     158            cpg.request.peerCertificate = self.request.get_peer_certificate() 
     159        except: 
     160            pass 
     161        msg = "%s - %s" % (cpg.request.remoteAddr, self.requestLine[:-2]) 
     162        _cputil.getSpecialFunction('_cpLogMessage')(msg, "HTTP") 
     163         
     164        cpg.request.base = "http://" + cpg.request.headerMap.get('Host', '') 
     165        cpg.request.browserUrl = cpg.request.base + path 
     166         
     167        # Change objectPath in filters to change 
     168        # the object that will get rendered 
     169        cpg.request.objectPath = None 
     170         
     171        # Save original values (in case they get modified by filters) 
     172        cpg.request.originalPath = cpg.request.path 
     173        cpg.request.originalParamMap = cpg.request.paramMap 
     174        cpg.request.originalParamList = cpg.request.paramList 
     175     
     176    def processRequestBody(self): 
     177        # Read request body and put it in data 
     178        data = "" 
     179        length = int(cpg.request.headerMap.get("Content-Length", "0")) 
     180        if length: 
     181            data = self.rfile.read(length) 
     182         
     183        # Put data in a StringIO so FieldStorage can read it 
     184        newRfile = StringIO.StringIO(data) 
     185        # Create a copy of headerMap with lowercase keys because 
     186        # FieldStorage doesn't work otherwise 
     187        lowerHeaderMap = {} 
     188        for key, value in cpg.request.headerMap.items(): 
     189            lowerHeaderMap[key.lower()] = value 
     190        forms = cgi.FieldStorage(fp = newRfile, headers = lowerHeaderMap, 
     191                                 environ = {'REQUEST_METHOD': 'POST'}, 
     192                                 keep_blank_values = 1) 
     193         
     194        # In case of files being uploaded, save filenames/types in maps. 
     195        for key in forms.keys(): 
     196            valueList = forms[key] 
     197            if isinstance(valueList, list): 
     198                cpg.request.paramMap[key] = [] 
     199                cpg.request.filenameMap[key] = [] 
     200                cpg.request.fileTypeMap[key] = [] 
     201                for item in valueList: 
     202                    cpg.request.paramMap[key].append(item.value) 
     203                    cpg.request.filenameMap[key].append(item.filename) 
     204                    cpg.request.fileTypeMap[key].append(item.type) 
     205            else: 
     206                cpg.request.paramMap[key] = valueList.value 
     207                cpg.request.filenameMap[key] = valueList.filename 
     208                cpg.request.fileTypeMap[key] = valueList.type 
     209 
     210 
     211# Error handling 
     212 
     213dbltrace = """ 
     214=====First Error===== 
     215 
     216%s 
     217 
     218=====Second Error===== 
     219 
     220%s 
     221 
     222""" 
     223 
     224def handleError(exc): 
     225    """Set status, headers, and body when an error occurs.""" 
     226    try: 
     227        applyFilters('beforeErrorResponse') 
     228         
     229        # _cpOnError will probably change cpg.response.body. 
     230        # It may also change the headerMap, etc. 
     231        _cputil.getSpecialFunction('_cpOnError')() 
     232         
     233        finalize() 
     234         
     235        applyFilters('afterErrorResponse') 
     236    except: 
     237        # Failure in _cpOnError, error filter, or finalize. 
     238        # Bypass them all. 
     239        body = dbltrace % (formatExc(exc), formatExc()) 
     240        cpg.response.status, cpg.response.headers, body = bareError(body) 
     241        cpg.response.body = [body] 
     242 
     243def formatExc(exc=None): 
     244    """formatExc(exc=None) -> exc (or sys.exc_info), formatted.""" 
     245    if exc is None: 
     246        exc = sys.exc_info() 
     247    return "".join(traceback.format_exception(*exc)) 
     248 
     249def bareError(extrabody=None): 
     250    """bareError(extrabody=None) -> status, headers, body. 
     251     
     252    Returns a triple without calling any other questionable functions, 
     253    so it should be as error-free as possible. Call it from an HTTP server 
     254    if you get errors after Request() is done. 
     255     
     256    If extrabody is None, a friendly but rather unhelpful error message 
     257    is set in the body. If extrabody is a string, it will be appended 
     258    as-is to the body. 
     259    """ 
     260     
     261    body = "Unrecoverable error in the server." 
     262    if extrabody is not None: 
     263        body += "\n" + extrabody 
     264    return ("500 Internal Server Error", 
     265            [('Content-Type', 'text/plain'), 
     266             ('Content-Length', str(len(body)))], 
     267            body) 
     268 
     269 
     270 
     271# Response functions 
     272 
     273def main(): 
     274    """Obtain and set cpg.response.body.""" 
     275    path = cpg.request.path 
     276    # Remove leading and trailing slash 
     277    path = path.strip("/") 
     278    # Replace quoted chars (eg %20) from url 
     279    path = urllib.unquote(path) 
     280     
     281    try: 
     282        func, objectPathList, virtualPathList = mapPathToObject() 
     283    except IndexRedirect, inst: 
     284        # For an IndexRedirect, we don't go through the regular 
     285        # mechanism: we return the redirect immediately 
     286        newUrl = urlparse.urljoin(cpg.request.base, inst.args[0]) 
     287        cpg.response.status = '302 Found' 
     288        cpg.response.headerMap['Location'] = newUrl 
     289        cpg.response.body = [] 
     290        return 
     291      
     292    # Remove "root" from objectPathList and join it to get objectPath 
     293    cpg.request.objectPath = '/' + '/'.join(objectPathList[1:]) 
     294    body = func(*(virtualPathList + cpg.request.paramList), 
     295                **(cpg.request.paramMap)) 
     296     
     297    # build a uniform return type (iterable) 
     298    if isinstance(body, types.FileType): 
     299        body = fileGenerator(body) 
     300    elif isinstance(body, types.GeneratorType): 
     301        body = flattener(body) 
     302    elif isinstance(body, basestring): 
     303        body = [body] 
     304    elif body is None: 
     305        body = [""] 
     306    cpg.response.body = body 
     307    return body 
     308 
     309def checkStatus(): 
     310    """Test/set cpg.response.status. Provide Reason-phrase if missing.""" 
     311    if not cpg.response.status: 
     312        cpg.response.status = "200 OK" 
    58313    else: 
    59         cpg.request.path, cpg.request.queryString = cpg.request.path, "" 
    60      
    61     # build a paramMap dictionary from queryString 
    62     pm = cpg.request.paramMap = cgi.parse_qs(cpg.request.queryString, True) 
    63     for key, val in pm.items(): 
    64         if len(val) == 1: 
    65             pm[key] = val[0] 
    66  
    67 def cookHeaders(clientAddress, remoteHost, headers, requestLine): 
    68     """Process the headers into the request.headerMap""" 
    69     cpg.request.headerMap = {} 
    70     cpg.request.requestLine = requestLine 
    71     cpg.request.simpleCookie = Cookie.SimpleCookie() 
    72  
    73     # Build headerMap 
    74     for item in headers.items(): 
    75         # Warning: if there is more than one header entry for cookies (AFAIK, only Konqueror does that) 
    76         # only the last one will remain in headerMap (but they will be correctly stored in request.simpleCookie) 
    77         insertIntoHeaderMap(item[0],item[1]) 
    78  
    79     # Handle cookies differently because on Konqueror, multiple cookies come on different lines with the same key 
    80     cookieList = headers.getallmatchingheaders('cookie') 
    81     for cookie in cookieList: 
    82         cpg.request.simpleCookie.load(cookie) 
    83  
    84     cpg.request.remoteAddr = clientAddress 
    85     cpg.request.remoteHost = remoteHost 
    86  
    87     # Set peer_certificate (in SSL mode) so the web app can examinate the client certificate 
    88     try: cpg.request.peerCertificate = self.request.get_peer_certificate() 
    89     except: pass 
    90  
    91     _cputil.getSpecialFunction('_cpLogMessage')("%s - %s" % (cpg.request.remoteAddr, requestLine[:-2]), "HTTP") 
    92  
    93  
    94 def parsePostData(rfile): 
    95     # Read request body and put it in data 
    96     len = int(cpg.request.headerMap.get("Content-Length","0")) 
    97     if len: data = rfile.read(len) 
    98     else: data="" 
    99  
    100     # Put data in a StringIO so FieldStorage can read it 
    101     newRfile = StringIO.StringIO(data) 
    102     # Create a copy of headerMap with lowercase keys because 
    103     #   FieldStorage doesn't work otherwise 
    104     lowerHeaderMap = {} 
    105     for key, value in cpg.request.headerMap.items(): 
    106         lowerHeaderMap[key.lower()] = value 
    107     forms = cgi.FieldStorage(fp = newRfile, headers = lowerHeaderMap, environ = {'REQUEST_METHOD':'POST'}, keep_blank_values = 1) 
    108     for key in forms.keys(): 
    109         # Check if it's a