source file: /Library/Python/2.3/site-packages/CherryPy-3.0.1-py2.3.egg/cherrypy/_cpserver.py
file stats: 157 lines, 111 executed: 70.7% covered
   1. """Manage an HTTP server with CherryPy."""
   2. 
   3. import socket
   4. import threading
   5. import time
   6. 
   7. import cherrypy
   8. from cherrypy.lib import attributes
   9. 
  10. 
  11. class Server(object):
  12.     """Manager for a set of HTTP servers.
  13. 
  14.     This is both a container and controller for "HTTP server" objects,
  15.     which are kept in Server.httpservers, a dictionary of the form:
  16.     {httpserver: bind_addr} where 'bind_addr' is usually a (host, port)
  17.     tuple.
  18. 
  19.     Most often, you will only be starting a single HTTP server. In this
  20.     common case, you can set attributes (like socket_host and socket_port)
  21.     on *this* object (which is probably cherrypy.server), and call
  22.     quickstart. For example:
  23. 
  24.         cherrypy.server.socket_port = 80
  25.         cherrypy.server.quickstart()
  26. 
  27.     But if you need to start more than one HTTP server (to serve on multiple
  28.     ports, or protocols, etc.), you can manually register each one and then
  29.     control them all through this object:
  30. 
  31.         s1 = MyWSGIServer(host='', port=80)
  32.         s2 = another.HTTPServer(host='localhost', SSL=True)
  33.         cherrypy.server.httpservers = {s1: ('', 80), s2: ('localhost', 443)}
  34.         # Note we do not use quickstart when we define our own httpservers
  35.         cherrypy.server.start()
  36. 
  37.     Whether you use quickstart(), or define your own httpserver entries and
  38.     use start(), you'll find that the start, wait, restart, and stop methods
  39.     work the same way, controlling all registered httpserver objects at once.
  40.     """
  41. 
  42.     socket_port = 8080
  43.     socket_host = ''
  44.     socket_file = ''
  45.     socket_queue_size = 5
  46.     socket_timeout = 10
  47.     protocol_version = 'HTTP/1.1'
  48.     reverse_dns = False
  49.     thread_pool = 10
  50.     max_request_header_size = 500 * 1024
  51.     max_request_body_size = 100 * 1024 * 1024
  52.     instance = None
  53.     ssl_certificate = None
  54.     ssl_private_key = None
  55. 
  56.     def __init__(self):
  57.         self.httpservers = {}
  58.         self.interrupt = None
  59. 
  60.     def quickstart(self, server=None):
  61.         """Start from defaults. MUST be called from the main thread.
  62. 
  63.         This function works like CherryPy 2's server.start(). It loads and
  64.         starts an httpserver based on the given server object (if provided)
  65.         and attributes of self.
  66.         """
  67.         httpserver, bind_addr = self.httpserver_from_self(server)
  68.         self.httpservers[httpserver] = bind_addr
  69.         self.start()
  70. 
  71.     def httpserver_from_self(self, httpserver=None):
  72.         """Return a (httpserver, bind_addr) pair based on self attributes."""
  73.         if httpserver is None:
  74.             httpserver = self.instance
  75.         if httpserver is None:
  76.             from cherrypy import _cpwsgi
  77.             httpserver = _cpwsgi.CPWSGIServer()
  78.         if isinstance(httpserver, basestring):
  79.             httpserver = attributes(httpserver)()
  80. 
  81.         if self.socket_file:
  82.             return httpserver, self.socket_file
  83. 
  84.         host = self.socket_host
  85.         port = self.socket_port
  86.         return httpserver, (host, port)
  87. 
  88.     def start(self):
  89.         """Start all registered HTTP servers."""
  90.         self.interrupt = None
  91.         if not self.httpservers:
  92.             raise ValueError("No HTTP servers have been created. "
  93.                              "Try server.quickstart instead.")
  94.         for httpserver in self.httpservers:
  95.             self._start_http(httpserver)
  96. 
  97.     def _start_http(self, httpserver):
  98.         """Start the given httpserver in a new thread."""
  99.         scheme = "http"
 100.         if getattr(httpserver, "ssl_certificate", None):
 101.             scheme = "https"
 102.         bind_addr = self.httpservers[httpserver]
 103.         if isinstance(bind_addr, tuple):
 104.             wait_for_free_port(*bind_addr)
 105.             host, port = bind_addr
 106.             if not host:
 107.                 host = '0.0.0.0'
 108.             on_what = "%s://%s:%s/" % (scheme, host, port)
 109.         else:
 110.             on_what = "socket file: %s" % bind_addr
 111. 
 112.         t = threading.Thread(target=self._start_http_thread, args=(httpserver,))
 113.         t.setName("CPHTTPServer " + t.getName())
 114.         t.start()
 115. 
 116.         self.wait(httpserver)
 117.         cherrypy.log("Serving %s on %s" % (scheme.upper(), on_what), 'HTTP')
 118. 
 119.     def _start_http_thread(self, httpserver):
 120.         """HTTP servers MUST be started in new threads, so that the
 121.         main thread persists to receive KeyboardInterrupt's. If an
 122.         exception is raised in the httpserver's thread then it's
 123.         trapped here, and the httpserver(s) and engine are shut down.
 124.         """
 125.         try:
 126.             httpserver.start()
 127.         except KeyboardInterrupt, exc:
 128.             cherrypy.log("<Ctrl-C> hit: shutting down HTTP servers", "SERVER")
 129.             self.interrupt = exc
 130.             self.stop()
 131.             cherrypy.engine.stop()
 132.         except SystemExit, exc:
 133.             cherrypy.log("SystemExit raised: shutting down HTTP servers", "SERVER")
 134.             self.interrupt = exc
 135.             self.stop()
 136.             cherrypy.engine.stop()
 137.             raise
 138. 
 139.     def wait(self, httpserver=None):
 140.         """Wait until the HTTP server is ready to receive requests.
 141. 
 142.         If no httpserver is specified, wait for all registered httpservers.
 143.         """
 144.         if httpserver is None:
 145.             httpservers = self.httpservers.items()
 146.         else:
 147.             httpservers = [(httpserver, self.httpservers[httpserver])]
 148. 
 149.         for httpserver, bind_addr in httpservers:
 150.             while not (getattr(httpserver, "ready", False) or self.interrupt):
 151.                 time.sleep(.1)
 152.             if self.interrupt:
 153.                 raise self.interrupt
 154. 
 155.             # Wait for port to be occupied
 156.             if isinstance(bind_addr, tuple):
 157.                 wait_for_occupied_port(*bind_addr)
 158. 
 159.     def stop(self):
 160.         """Stop all HTTP servers."""
 161.         for httpserver, bind_addr in self.httpservers.items():
 162.             try:
 163.                 httpstop = httpserver.stop
 164.             except AttributeError:
 165.                 pass
 166.             else:
 167.                 # httpstop() MUST block until the server is *truly* stopped.
 168.                 httpstop()
 169.                 if isinstance(bind_addr, tuple):
 170.                     wait_for_free_port(*bind_addr)
 171.                 cherrypy.log("HTTP Server shut down", "HTTP")
 172. 
 173.     def restart(self):
 174.         """Restart all HTTP servers."""
 175.         self.stop()
 176.         self.start()
 177. 
 178.     def base(self):
 179.         """Return the base (scheme://host) for this server manager."""
 180.         if self.socket_file:
 181.             return self.socket_file
 182. 
 183.         host = self.socket_host
 184.         if not host:
 185.             # The empty string signifies INADDR_ANY. Look up the host name,
 186.             # which should be the safest thing to spit out in a URL.
 187.             host = socket.gethostname()
 188. 
 189.         port = self.socket_port
 190. 
 191.         if self.ssl_certificate:
 192.             scheme = "https"
 193.             if port != 443:
 194.                 host += ":%s" % port
 195.         else:
 196.             scheme = "http"
 197.             if port != 80:
 198.                 host += ":%s" % port
 199. 
 200.         return "%s://%s" % (scheme, host)
 201. 
 202. 
 203. def check_port(host, port):
 204.     """Raise an error if the given port is not free on the given host."""
 205.     if not host:
 206.         # The empty string signifies INADDR_ANY,
 207.         # which should respond on localhost.
 208.         host = 'localhost'
 209.     port = int(port)
 210. 
 211.     # AF_INET or AF_INET6 socket
 212.     # Get the correct address family for our host (allows IPv6 addresses)
 213.     for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
 214.                                   socket.SOCK_STREAM):
 215.         af, socktype, proto, canonname, sa = res
 216.         s = None
 217.         try:
 218.             s = socket.socket(af, socktype, proto)
 219.             # See http://groups.google.com/group/cherrypy-users/
 220.             #        browse_frm/thread/bbfe5eb39c904fe0
 221.             s.settimeout(1.0)
 222.             s.connect((host, port))
 223.             s.close()
 224.             raise IOError("Port %s is in use on %s; perhaps the previous "
 225.                           "httpserver did not shut down properly." %
 226.                           (repr(port), repr(host)))
 227.         except socket.error:
 228.             if s:
 229.                 s.close()
 230. 
 231. 
 232. def wait_for_free_port(host, port):
 233.     """Wait for the specified port to become free (drop requests)."""
 234.     if not host:
 235.         # The empty string signifies INADDR_ANY,
 236.         # which should respond on localhost.
 237.         host = 'localhost'
 238. 
 239.     for trial in xrange(50):
 240.         try:
 241.             check_port(host, port)
 242.         except IOError:
 243.             # Give the old server thread time to free the port.
 244.             time.sleep(.1)
 245.         else:
 246.             return
 247. 
 248.     msg = "Port %s not free on %s" % (repr(port), repr(host))
 249.     cherrypy.log(msg, 'HTTP')
 250.     raise IOError(msg)
 251. 
 252. def wait_for_occupied_port(host, port):
 253.     """Wait for the specified port to become active (receive requests)."""
 254.     if not host:
 255.         # The empty string signifies INADDR_ANY,
 256.         # which should respond on localhost.
 257.         host = 'localhost'
 258. 
 259.     for trial in xrange(50):
 260.         try:
 261.             check_port(host, port)
 262.         except IOError:
 263.             return
 264.         else:
 265.             time.sleep(.1)
 266. 
 267.     msg = "Port %s not bound on %s" % (repr(port), repr(host))
 268.     cherrypy.log(msg, 'HTTP')
 269.     raise IOError(msg)