Commits

Robert Brewer committed 14e410d

Fix for #537 (Support for listening on multiple ports). cherrypy.server now has no "httpserver" attribute; instead, it has an "httpservers" attribute, a dict of the form {server object: bind_addr} where bind_addr is usually (host, port). New start_all method. Calling stop stops all httpservers, and restart stops all then restarts all.

Comments (0)

Files changed (4)

 """Manage an HTTP server with CherryPy."""
 
+import socket
 import threading
 import time
 
 
 
 class Server(object):
-    """Manager for an HTTP server."""
+    """Manager for a set of HTTP servers."""
     
     def __init__(self):
-        self.httpserver = None
+        self.httpservers = {}
         self.interrupt = None
     
     def start(self, server=None):
         """Main function. MUST be called from the main thread."""
         self.interrupt = None
-        
+        httpserver, bind_addr = self.httpserver_from_config(server)
+        self.httpservers[httpserver] = bind_addr
+        self._start_http(httpserver)
+    
+    def httpserver_from_config(self, httpserver=None):
+        """Return a (httpserver, bind_addr) pair based on config settings."""
         conf = cherrypy.config.get
-        if server is None:
-            server = conf('server.instance', None)
-        if server is None:
+        if httpserver is None:
+            httpserver = conf('server.instance', None)
+        if httpserver is None:
             import _cpwsgi
-            server = _cpwsgi.WSGIServer()
-        if isinstance(server, basestring):
-            server = attributes(server)()
-        self.httpserver = server
+            httpserver = _cpwsgi.WSGIServer()
+        if isinstance(httpserver, basestring):
+            httpserver = attributes(httpserver)()
         
         if conf('server.socket_port'):
             host = conf('server.socket_host')
             port = conf('server.socket_port')
-            wait_for_free_port(host, port)
             if not host:
                 host = 'localhost'
-            on_what = "http://%s:%s/" % (host, port)
+            return httpserver, (host, port)
         else:
-            on_what = "socket file: %s" % conf('server.socket_file')
+            return httpserver, conf('server.socket_file')
+    
+    def start_all(self):
+        """Start all registered HTTP servers."""
+        for httpserver in self.httpservers:
+            self._start_http(httpserver)
+    
+    def _start_http(self, httpserver):
+        """Start the given httpserver in a new thread."""
+        bind_addr = self.httpservers[httpserver]
+        if isinstance(bind_addr, tuple):
+            wait_for_free_port(*bind_addr)
+            on_what = "http://%s:%s/" % bind_addr
+        else:
+            on_what = "socket file: %s" % bind_addr
         
-        # HTTP servers MUST be started in a new thread, so that the
-        # main thread persists to receive KeyboardInterrupt's. If an
-        # exception is raised in the http server's thread then it's
-        # trapped here, and the http server and engine are shut down.
-        def _start_http():
-            try:
-                self.httpserver.start()
-            except KeyboardInterrupt, exc:
-                cherrypy.log("<Ctrl-C> hit: shutting down HTTP server", "SERVER")
-                self.interrupt = exc
-                self.stop()
-                cherrypy.engine.stop()
-            except SystemExit, exc:
-                cherrypy.log("SystemExit raised: shutting down HTTP server", "SERVER")
-                self.interrupt = exc
-                self.stop()
-                cherrypy.engine.stop()
-                raise
-        t = threading.Thread(target=_start_http)
+        t = threading.Thread(target=self._start_http_thread, args=(httpserver,))
         t.setName("CPHTTPServer " + t.getName())
         t.start()
         
-        self.wait()
+        self.wait(httpserver)
         cherrypy.log("Serving HTTP on %s" % on_what, 'HTTP')
     
-    def wait(self):
-        """Wait until the HTTP server is ready to receive requests."""
-        while (not getattr(self.httpserver, "ready", False)
-               and not self.interrupt):
-            time.sleep(.1)
-        if self.interrupt:
-            raise self.interrupt
+    def _start_http_thread(self, httpserver):
+        """HTTP servers MUST be started in new threads, so that the
+        main thread persists to receive KeyboardInterrupt's. If an
+        exception is raised in the httpserver's thread then it's
+        trapped here, and the httpserver(s) and engine are shut down.
+        """
+        try:
+            httpserver.start()
+        except KeyboardInterrupt, exc:
+            cherrypy.log("<Ctrl-C> hit: shutting down HTTP servers", "SERVER")
+            self.interrupt = exc
+            self.stop()
+            cherrypy.engine.stop()
+        except SystemExit, exc:
+            cherrypy.log("SystemExit raised: shutting down HTTP servers", "SERVER")
+            self.interrupt = exc
+            self.stop()
+            cherrypy.engine.stop()
+            raise
+    
+    def wait(self, httpserver=None):
+        """Wait until the HTTP server is ready to receive requests.
         
-        # Wait for port to be occupied
-        if cherrypy.config.get('server.socket_port'):
-            host = cherrypy.config.get('server.socket_host')
-            port = cherrypy.config.get('server.socket_port')
-            wait_for_occupied_port(host, port)
+        If no httpserver is specified, wait for all registered httpservers.
+        """
+        if httpserver is None:
+            httpservers = self.httpservers.items()
+        else:
+            httpservers = [(httpserver, self.httpservers[httpserver])]
+        
+        for httpserver, bind_addr in httpservers:
+            while not (getattr(httpserver, "ready", False) or self.interrupt):
+                time.sleep(.1)
+            if self.interrupt:
+                raise self.interrupt
+            
+            # Wait for port to be occupied
+            if isinstance(bind_addr, tuple):
+                wait_for_occupied_port(*bind_addr)
     
     def stop(self):
-        """Stop the HTTP server."""
-        try:
-            httpstop = self.httpserver.stop
-        except AttributeError:
-            pass
-        else:
-            # httpstop() MUST block until the server is *truly* stopped.
-            httpstop()
-            conf = cherrypy.config.get
-            if conf('server.socket_port'):
-                host = conf('server.socket_host')
-                port = conf('server.socket_port')
-                wait_for_free_port(host, port)
-            cherrypy.log("HTTP Server shut down", "HTTP")
+        """Stop all HTTP server(s)."""
+        for httpserver, bind_addr in self.httpservers.items():
+            try:
+                httpstop = httpserver.stop
+            except AttributeError:
+                pass
+            else:
+                # httpstop() MUST block until the server is *truly* stopped.
+                httpstop()
+                if isinstance(bind_addr, tuple):
+                    wait_for_free_port(*bind_addr)
+                cherrypy.log("HTTP Server shut down", "HTTP")
     
     def restart(self):
         """Restart the HTTP server."""
         self.stop()
         self.interrupt = None
-        self.start()
+        self.start_all()
 
 
 def check_port(host, port):
     """Raise an error if the given port is not free on the given host."""
-    sock_file = cherrypy.config.get('server.socket_file')
-    if sock_file:
-        return
-    
     if not host:
         host = 'localhost'
     port = int(port)
     
-    import socket
-    
     # AF_INET or AF_INET6 socket
     # Get the correct address family for our host (allows IPv6 addresses)
     for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
             s.connect((host, port))
             s.close()
             raise IOError("Port %s is in use on %s; perhaps the previous "
-                          "server did not shut down properly." %
+                          "httpserver did not shut down properly." %
                           (repr(port), repr(host)))
         except socket.error:
             if s:
     environ["PATH_INFO"] = cherrypy.request.path_info
     environ["QUERY_STRING"] = cherrypy.request.query_string
     environ["SERVER_PROTOCOL"] = cherrypy.request.protocol
-    server_name = getattr(cherrypy.server.httpserver, 'server_name', "None")
-    environ["SERVER_NAME"] = server_name 
-    environ["SERVER_PORT"] = cherrypy.config.get('server.socket_port')
+    environ["SERVER_NAME"] = cherrypy.request.wsgi_environ['SERVER_NAME']
+    environ["SERVER_PORT"] = cherrypy.request.wsgi_environ['SERVER_PORT']
     environ["REMOTE_HOST"] = cherrypy.request.remote_host
     environ["REMOTE_ADDR"] = cherrypy.request.remote_addr
     environ["REMOTE_PORT"] = cherrypy.request.remote_port
 ##            apps.append((base, _cpwsgi.make_app(app)))
         apps.sort()
         apps.reverse()
-        cherrypy.server.httpserver.mount_points = apps
+        for s in cherrypy.server.httpservers:
+            s.mount_points = apps
         
         suite = CPTestLoader.loadTestsFromName(testmod)
         CPTestRunner.run(suite)

test/test_states.py

             # We must start the server in this, the main thread
             cherrypy.engine.start(blocking=False)
             cherrypy.server.start(self.server_class)
-            cherrypy.server.httpserver.interrupt = KeyboardInterrupt
+            cherrypy.server.httpservers.values()[0].interrupt = KeyboardInterrupt
             while cherrypy.engine.state != 0:
                 time.sleep(0.1)
             
             tr.stop()
             tr.out.close()
     finally:
-        if cherrypy.server.httpserver.ready:
-            cherrypy.server.stop()
+        cherrypy.server.stop()
         cherrypy.engine.stop()
 
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.