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

Changeset 967

Show
Ignore:
Timestamp:
02/14/06 15:33:41
Author:
fumanchu
Message:

Final fix for #461 and #455:

  1. There's a new _cpengine module which isolates the "app server" from the "HTTP server" state-management logic. _cpserver.Server inherits from _cpengine.Engine. The _cpengine module should remain clean so that it can be used with few changes in CP 3, at which point we can rewrite the http server API.
  2. Fixed an issue with the native HTTP server--it wasn't truly restartable (closed but didn't rebind on restart).
  3. Server.start now takes a "server" arg (an instance of an HTTP server). Use it or server_class, not both.
  4. Fixed _cpwsgiserver's handling of the URI: "*".
Files:

Legend:

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

    r959 r967  
    186186         
    187187        self.request_queue_size = cherrypy.config.get('server.socket_queue_size') 
     188     
     189    def server_bind(self): 
     190        """Called by constructor to bind the socket.""" 
    188191         
    189192        # Select the appropriate server based on config options 
     
    203206            self.server_address = sockFile 
    204207            self.socket = socket.socket(self.address_family, self.socket_type) 
    205             self.server_bind() 
     208            self._server_bind() 
    206209        else: 
    207210            # AF_INET or AF_INET6 socket 
     
    217220                # Must...refuse...temptation..to..guess... 
    218221                self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    219                 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
    220                 self.socket.bind(self.server_address) 
     222                self._server_bind() 
    221223            else: 
    222224                # Get the correct address family for our host (allows IPv6 addresses) 
     
    227229                    try: 
    228230                        self.socket = socket.socket(af, socktype, proto) 
    229                         self.server_bind() 
     231                        self._server_bind() 
    230232                    except socket.error, msg: 
    231233                        if self.socket: 
     
    236238                if not self.socket: 
    237239                    raise socket.error, msg 
    238          
    239         self.server_activate() 
     240     
     241    def _server_bind(self): 
     242        if self.allow_reuse_address: 
     243            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
     244        self.socket.bind(self.server_address) 
    240245     
    241246    def server_activate(self): 
     
    243248        self.socket.settimeout(1) 
    244249        self.socket.listen(self.request_queue_size) 
    245      
    246     def server_bind(self): 
    247         """Called by constructor to bind the socket.""" 
    248         if self.allow_reuse_address: 
    249             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
    250         self.socket.bind(self.server_address) 
    251250     
    252251    def close_request(self, request): 
     
    295294            self.handle_request() 
    296295        self.server_close() 
    297     start = serve_forever 
     296     
     297    def start(self): 
     298        self.server_bind() 
     299        self.server_activate() 
     300        self.serve_forever() 
    298301     
    299302    def server_close(self): 
     
    346349         
    347350        CherryHTTPServer.serve_forever(self) 
    348     start = serve_forever 
    349351     
    350352    def server_close(self): 
  • trunk/cherrypy/_cpserver.py

    r966 r967  
    11"""Create and manage the CherryPy server.""" 
    22 
    3 import cgi 
    4 import sys 
    53import threading 
    64import time 
    7 import warnings 
    85 
    96import cherrypy 
    10 from cherrypy import _cphttptools, filters, _cpwsgi 
    11 from cherrypy.lib import autoreload, profiler, cptools 
    12  
    13 # Use a flag to indicate the state of the application server. 
    14 STOPPED = 0 
    15 STARTING = None 
    16 STARTED = 1 
     7from cherrypy import _cphttptools 
     8from cherrypy.lib import cptools 
     9 
     10from cherrypy._cpengine import Engine, STOPPED, STARTING, STARTED 
    1711 
    1812_missing = object() 
    1913 
    2014 
    21 class Server(object): 
    22      
    23     request_class = _cphttptools.Request 
     15class Server(Engine): 
    2416     
    2517    def __init__(self): 
    26         self.state = STOPPED 
    27         self.seen_threads = {} 
     18        Engine.__init__(self) 
     19        self._is_setup = False 
     20        self.blocking = True 
    2821         
    2922        self.httpserver = None 
     23        # Starting in 2.2, the "httpserverclass" attr is essentially dead; 
     24        # no CP code uses it. Inspect "httpserver" instead. 
    3025        self.httpserverclass = None 
    31         self.interrupt = None 
    32          
    33         # Set some special attributes for adding hooks 
    34         self.on_start_server_list = [] 
    35         self.on_start_thread_list = [] 
    36         self.on_stop_server_list = [] 
    37         self.on_stop_thread_list = [] 
    38  
     26         
    3927        # Backward compatibility: 
    40         self.onStopServerList = [] 
    41         self.onStartThreadList = [] 
    42         self.onStartServerList = [] 
    43         self.onStopThreadList = [] 
    44  
    45         self.wsgi_app = _cpwsgi.wsgiApp 
    46          
    47     def start(self, init_only = False, server_class = _missing, **kwargs): 
     28        self.onStopServerList = self.on_stop_server_list 
     29        self.onStartThreadList = self.on_start_thread_list 
     30        self.onStartServerList = self.on_start_server_list 
     31        self.onStopThreadList = self.on_stop_thread_list 
     32     
     33    def start(self, init_only=False, server_class=_missing, server=None, **kwargs): 
    4834        """Main function. MUST be called from the main thread. 
    4935         
    5036        Set initOnly to True to keep this function from blocking. 
    51         Set serverClass to None to skip starting any HTTP server. 
     37        Set serverClass and server to None to skip starting any HTTP server. 
    5238        """ 
    5339         
     
    5844            server_class = kwargs['serverClass'] 
    5945         
     46        conf = cherrypy.config.get 
     47        if server is None: 
     48            if server_class is _missing: 
     49                server_class = conf("server.class", _missing) 
     50            if server_class is _missing: 
     51                import _cpwsgi 
     52                server_class = _cpwsgi.WSGIServer 
     53            elif server_class and isinstance(server_class, basestring): 
     54                # Dynamically load the class from the given string 
     55                server_class = cptools.attributes(server_class) 
     56            self.httpserverclass = server_class 
     57            if server_class is not None: 
     58                self.httpserver = server_class() 
     59        else: 
     60            self.httpserverclass = server.__class__ 
     61            self.httpserver = server 
     62         
     63        self.blocking = not init_only 
     64        Engine.start(self) 
     65     
     66    def _start(self): 
     67        if not self._is_setup: 
     68            self.setup() 
     69            self._is_setup = True 
     70        Engine._start(self) 
     71        self.start_http_server() 
     72        if self.blocking: 
     73            self.block() 
     74     
     75    def restart(self): 
     76        """Restart the application server engine.""" 
     77        self.stop() 
    6078        self.state = STARTING 
    6179        self.interrupt = None 
    62          
    63         conf = cherrypy.config.get 
    64          
    65         if server_class is _missing: 
    66             server_class = conf("server.class", _missing) 
    67         if server_class is _missing: 
    68             import _cpwsgi 
    69             server_class = _cpwsgi.WSGIServer 
    70         elif server_class and isinstance(server_class, basestring): 
    71             # Dynamically load the class from the given string 
    72             server_class = cptools.attributes(server_class) 
    73          
    74         self.blocking = not init_only 
    75         self.httpserverclass = server_class 
    76          
    77         # Hmmm...we *could* check config in _start instead, but I think 
    78         # most people would like CP to fail before autoreload kicks in. 
    79         check_config() 
    80          
    81         # Autoreload, but check server_class. If None, we're not starting 
    82         # our own webserver, and therefore could do Very Bad Things when 
    83         # autoreload calls sys.exit. 
    84         if server_class is not None: 
    85             if conf('autoreload.on', False): 
    86                 try: 
    87                     freq = conf('autoreload.frequency', 1) 
    88                     autoreload.main(self._start, freq=freq) 
    89                 except KeyboardInterrupt: 
    90                     cherrypy.log("<Ctrl-C> hit: shutting down autoreloader", "HTTP") 
    91                     self.stop() 
    92                 except SystemExit: 
    93                     cherrypy.log("SystemExit raised: shutting down autoreloader", "HTTP") 
    94                     self.stop() 
    95                     # We must raise here: if this is a process spawned by 
    96                     # autoreload, then it must return its error code to 
    97                     # the parent. 
    98                     raise 
    99                 return 
    100          
    10180        self._start() 
    102      
    103     def _start(self): 
    104         # Output config options to log 
    105         if cherrypy.config.get("server.log_config_options", True): 
    106             cherrypy.config.outputConfigMap() 
    107          
    108         try: 
    109             configure() 
    110              
    111             for func in cherrypy.server.on_start_server_list + cherrypy.server.onStartServerList: 
    112                 func() 
    113             self.start_http_server() 
    114             self.state = STARTED 
    115              
    116             if self.blocking: 
    117                 # Block forever (wait for KeyboardInterrupt or SystemExit). 
    118                 while True: 
    119                     time.sleep(.1) 
    120                     if self.interrupt: 
    121                         raise self.interrupt 
    122         except KeyboardInterrupt: 
    123             cherrypy.log("<Ctrl-C> hit: shutting down server", "HTTP") 
    124             self.stop() 
    125         except SystemExit: 
    126             cherrypy.log("SystemExit raised: shutting down server", "HTTP") 
    127             self.stop() 
    128         except: 
    129             # Don't bother logging, since we're going to re-raise. 
    130             self.interrupt = sys.exc_info()[1] 
    131             self.stop() 
    132             raise 
    13381     
    13482    def start_http_server(self, blocking=True): 
    13583        """Start the requested HTTP server.""" 
    136         if self.httpserver is not None: 
    137             msg = ("You seem to have an HTTP server still running." 
    138                    "Please call server.stop_http_server() " 
    139                    "before continuing.") 
    140             warnings.warn(msg) 
    141          
    142         if self.httpserverclass is None: 
     84        if not self.httpserver: 
    14385            return 
    14486         
     
    15597            on_what = "socket file: %s" % cherrypy.config.get('server.socket_file') 
    15698         
    157         # Instantiate the server. 
    158         self.httpserver = self.httpserverclass() 
    159          
    16099        # HTTP servers MUST be started in a new thread, so that the 
    161         # main thread persists to receive KeyboardInterrupt's. This 
    162         # wrapper traps an interrupt in the http server's main thread 
    163         # and shutdowns CherryPy. 
     100        # main thread persists to receive KeyboardInterrupt's. If an 
     101        # exception is raised in the http server's main thread then it's 
     102        # trapped here, and the CherryPy app server is shut down (via 
     103        # self.interrupt). 
    164104        def _start_http(): 
    165105            try: 
     
    167107            except (KeyboardInterrupt, SystemExit), exc: 
    168108                self.interrupt = exc 
     109                self.stop() 
    169110        threading.Thread(target=_start_http).start() 
    170111         
     
    174115        cherrypy.log("Serving HTTP on %s" % on_what, 'HTTP') 
    175116     
     117    def wait(self): 
     118        """Block the caller until ready to receive requests (or error).""" 
     119        Engine.wait(self) 
     120        self.wait_for_http_ready() 
     121     
    176122    def wait_for_http_ready(self): 
    177         if self.httpserverclass is not None
    178             while not getattr(self.httpserver, "ready", True)
     123        if self.httpserver
     124            while not getattr(self.httpserver, "ready", True) and not self.interrupt
    179125                time.sleep(.1) 
    180126             
     
    183129                host = cherrypy.config.get('server.socket_host') 
    184130                port = cherrypy.config.get('server.socket_port') 
    185                 wait_for_occupied_port(host, port) 
    186      
    187     def request(self, clientAddress, remoteHost, scheme="http"): 
    188         """Obtain an HTTP Request object. 
    189          
    190         clientAddress: the (IP address, port) of the client 
    191         remoteHost: the IP address of the client 
    192         scheme: either "http" or "https"; defaults to "http" 
    193         """ 
    194         if self.state == STOPPED: 
    195             raise cherrypy.NotReady("The CherryPy server has stopped.") 
    196         elif self.state == STARTING: 
    197             raise cherrypy.NotReady("The CherryPy server could not start.") 
    198          
    199         threadID = threading._get_ident() 
    200         if threadID not in self.seen_threads: 
    201              
    202             if cherrypy.codecoverage: 
    203                 from cherrypy.lib import covercp 
    204                 covercp.start() 
    205              
    206             i = len(self.seen_threads) + 1 
    207             self.seen_threads[threadID] = i 
    208              
    209             for func in self.on_start_thread_list + self.onStartThreadList: 
    210                 func(i) 
    211          
    212         r = self.request_class(clientAddress[0], clientAddress[1], 
    213                               remoteHost, scheme) 
    214         cherrypy.serving.request = r 
    215         cherrypy.serving.response = _cphttptools.Response() 
    216         return r 
     131                if not host: 
     132                    host = 'localhost' 
     133                 
     134                for trial in xrange(50): 
     135                    if self.interrupt: 
     136                        break 
     137                    try: 
     138                        check_port(host, port) 
     139                    except IOError: 
     140                        break 
     141                    else: 
     142                        time.sleep(.1) 
     143                else: 
     144                    cherrypy.log("Port %s not bound on %s" % 
     145                                 (repr(port), repr(host)), 'HTTP') 
     146                    raise cherrypy.NotReady("Port not bound.") 
    217147     
    218148    def stop(self): 
    219149        """Stop, including any HTTP servers.""" 
    220150        self.stop_http_server() 
    221          
    222         for thread_ident, i in self.seen_threads.iteritems(): 
    223             for func in self.on_stop_thread_list + self.onStopThreadList: 
    224                 func(i) 
    225         self.seen_threads.clear() 
    226          
    227         for func in self.on_stop_server_list + self.onStopServerList: 
    228             func() 
    229          
    230         self.state = STOPPED 
    231         cherrypy.log("CherryPy shut down", "HTTP") 
     151        Engine.stop(self) 
    232152     
    233153    def stop_http_server(self): 
     
    241161            httpstop() 
    242162            cherrypy.log("HTTP Server shut down", "HTTP") 
    243          
    244         self.httpserver = None 
    245      
    246     def restart(self): 
    247         """Restart, including any HTTP servers.""" 
    248         self.stop() 
    249         for func in self.on_start_server_list + self.onStartServerList: 
    250             func() 
    251         self.start_http_server() 
    252         self.state = STARTED 
    253      
    254     def wait(self): 
    255         """Block the caller until ready to receive requests (or error).""" 
    256         while not self.ready: 
    257             time.sleep(.1) 
    258             if self.interrupt: 
    259                 # Something went wrong in server.start, 
    260                 # possibly in another thread. Stop this thread. 
    261                 raise cherrypy.NotReady("The CherryPy server errored", "HTTP") 
    262      
    263     def _is_ready(self): 
    264         return bool(self.state == STARTED) 
    265     ready = property(_is_ready, doc="Return True if the server is ready to" 
    266                                     " receive requests, False otherwise.") 
    267163     
    268164    def start_with_callback(self, func, args=None, kwargs=None, 
    269165                            server_class = _missing, serverClass = None): 
    270166        """Start, then callback the given func in a new thread.""" 
    271  
     167         
    272168        # Read old name for backward compatibility 
    273169        if serverClass is not None: 
    274170            server_class = None 
    275  
     171         
    276172        if args is None: 
    277173            args = () 
     
    286182         
    287183        self.start(server_class = server_class) 
    288  
    289  
    290 def check_config(): 
    291     err = cherrypy.WrongConfigValue 
    292     for name, section in cherrypy.config.configs.iteritems(): 
    293         for k, v in section.iteritems(): 
    294             if k == "server.environment": 
    295                 if v and v not in cherrypy.config.environments: 
    296                     raise err("'%s' is not a registered environment." % v) 
    297  
    298  
    299 def configure(): 
    300     """Perform one-time actions to prepare the CherryPy core.""" 
    301     if cherrypy.codecoverage: 
    302         from cherrypy.lib import covercp 
    303         covercp.start() 
    304      
    305     conf = cherrypy.config.get 
    306     # TODO: config.checkConfigOptions() 
    307      
    308     # If sessions are stored in files and we 
    309     # use threading, we need a lock on the file 
    310     if (conf('server.thread_pool') > 1 
    311         and conf('session.storage_type') == 'file'): 
    312         cherrypy._sessionFileLock = threading.RLock() 
    313      
    314     # set cgi.maxlen which will limit the size of POST request bodies 
    315     cgi.maxlen = conf('server.max_request_size') 
    316      
    317     # Set up the profiler if requested. 
    318     if conf("profiling.on", False): 
    319         ppath = conf("profiling.path", "") 
    320         cherrypy.profiler = profiler.Profiler(ppath) 
    321     else: 
    322         cherrypy.profiler = None 
    323      
    324     # Initialize the built in filters 
    325     filters.init() 
    326184 
    327185 
  • trunk/cherrypy/_cpwsgiserver.py

    r965 r967  
    6767         
    6868        for mount_point, wsgi_app in self.server.mount_points: 
     69            if path == "*": 
     70                # This means, of course, that the first wsgi_app will 
     71                # always handle a URI of "*". 
     72                self.environ["SCRIPT_NAME"] = "" 
     73                self.environ["PATH_INFO"] = "*" 
     74                self.wsgi_app = wsgi_app 
     75                break 
    6976            # The mount_points list should be sorted by length, descending. 
    7077            if path.startswith(mount_point): 
  • trunk/cherrypy/test/test_core.py

    r942 r967  
    632632         
    633633        # Test server.throw_errors (ticket #186). 
    634         httpcls = cherrypy.server.httpserverclass 
    635         if httpcls: 
     634        s = cherrypy.server.httpserver 
     635        if s: 
    636636            self.getPage("/error/rethrow") 
    637637            self.assertBody("THROWN ERROR: ValueError") 
     
    837837        self.assertBody("OK") 
    838838         
    839         httpcls = cherrypy.server.httpserverclass 
    840         if httpcls: 
     839        s = cherrypy.server.httpserver 
     840        if s: 
    841841            cherrypy.config.update({'server.max_request_header_size': 10}) 
    842842            self.getPage("/maxrequestsize/index") 
     
    866866        self.assertBody('Size: 5') 
    867867         
    868         httpcls = cherrypy.server.httpserverclass 
    869         if httpcls: 
     868        s = cherrypy.server.httpserver 
     869        if s: 
    870870            cherrypy.config.update({ 
    871871                '%s/maxrequestsize' % self.prefix(): {'server.max_request_body_size': 3}}) 
  • trunk/cherrypy/test/test_logdebuginfo_filter.py

    r910 r967  
    3535 
    3636    def testBug326(self): 
    37         httpcls = cherrypy.server.httpserverclass 
    38         if httpcls and httpcls.__name__ == "WSGIServer": 
     37        from cherrypy import _cpwsgi 
     38        s = cherrypy.server.httpserver 
     39        if s and isinstance(s, _cpwsgi.WSGIServer): 
    3940            h = [("Content-type", "multipart/form-data; boundary=x"), 
    4041                 ("Content-Length", "110")] 
     
    4748""" 
    4849            cherrypy.config.update({ 
    49                 ('%s/bug326' % self.prefix): { 
     50                ('%s/bug326' % self.prefix()): { 
    5051                    'server.max_request_body_size': 3, 
    5152                    'server.environment': 'development', 
  • trunk/cherrypy/test/test_states.py

    r943 r967  
    5555     
    5656    def test_0_NormalStateFlow(self): 
    57         # Without having called "cherrypy.server.start()", we should 
    58         # get a NotReady error 
    59         self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
     57        if not self.server_class: 
     58            # Without having called "cherrypy.server.start()", we should 
     59            # get a NotReady error 
     60            self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    6061         
    6162        # And our db_connection should not be running 
     
    8687        self.assertEqual(cherrypy.server.state, 0) 
    8788         
    88         # Once the server has stopped, we should get a NotReady error again. 
    89         self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    90          
    9189        # Verify that the on_stop_server function was called 
    9290        self.assertEqual(db_connection.running, False) 
    9391        self.assertEqual(len(db_connection.threads), 0) 
     92         
     93        if not self.server_class: 
     94            # Once the server has stopped, we should get a NotReady 
     95            # error again. (If we were running an HTTP server, 
     96            # then the connection should not even be processed). 
     97            self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    9498     
    9599    def test_1_Restart(self): 
     
    140144            cherrypy.server.start(False, self.server_class) 
    141145            # Time passes... 
    142             self.assertEqual(cherrypy.server.httpserver, None) 
    143146            self.assertEqual(cherrypy.server.state, 0) 
    144             self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    145147            self.assertEqual(db_connection.running, False) 
    146148            self.assertEqual(len(db_connection.threads), 0) 
     
    158160            cherrypy.server.start(False, self.server_class) 
    159161            # Time passes... 
    160             self.assertEqual(cherrypy.server.httpserver, None) 
    161162            self.assertEqual(cherrypy.server.state, 0) 
    162             self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    163163            self.assertEqual(db_connection.running, False) 
    164164            self.assertEqual(len(db_connection.threads), 0) 
    165      
    166     def test_3_ConfigErrors(self): 
    167         cherrypy.config.update({'server.environment': 'destruction'}) 
    168          
    169         try: 
    170             self.assertRaises(cherrypy.WrongConfigValue, 
    171                     cherrypy.server.start, True, self.server_class) 
    172         finally: 
    173             cherrypy.server.stop() 
    174165 
    175166 

Hosted by WebFaction

Log in as guest/cpguest to create tickets