Package cherrypy :: Package process :: Module servers
[hide private]
[frames] | no frames]

Source Code for Module cherrypy.process.servers

  1  """Adapt an HTTP server.""" 
  2   
  3  import time 
  4   
  5   
6 -class ServerAdapter(object):
7 """Adapter for an HTTP server. 8 9 If you need to start more than one HTTP server (to serve on multiple 10 ports, or protocols, etc.), you can manually register each one and then 11 start them all with bus.start: 12 13 s1 = ServerAdapter(bus, MyWSGIServer(host='0.0.0.0', port=80)) 14 s2 = ServerAdapter(bus, another.HTTPServer(host='127.0.0.1', SSL=True)) 15 s1.subscribe() 16 s2.subscribe() 17 bus.start() 18 """ 19
20 - def __init__(self, bus, httpserver=None, bind_addr=None):
21 self.bus = bus 22 self.httpserver = httpserver 23 self.bind_addr = bind_addr 24 self.interrupt = None 25 self.running = False
26
27 - def subscribe(self):
28 self.bus.subscribe('start', self.start) 29 self.bus.subscribe('stop', self.stop)
30
31 - def unsubscribe(self):
32 self.bus.unsubscribe('start', self.start) 33 self.bus.unsubscribe('stop', self.stop)
34
35 - def start(self):
36 """Start the HTTP server.""" 37 if isinstance(self.bind_addr, tuple): 38 host, port = self.bind_addr 39 on_what = "%s:%s" % (host, port) 40 else: 41 on_what = "socket file: %s" % self.bind_addr 42 43 if self.running: 44 self.bus.log("Already serving on %s" % on_what) 45 return 46 47 self.interrupt = None 48 if not self.httpserver: 49 raise ValueError("No HTTP server has been created.") 50 51 # Start the httpserver in a new thread. 52 if isinstance(self.bind_addr, tuple): 53 wait_for_free_port(*self.bind_addr) 54 55 import threading 56 t = threading.Thread(target=self._start_http_thread) 57 t.setName("HTTPServer " + t.getName()) 58 t.start() 59 60 self.wait() 61 self.running = True 62 self.bus.log("Serving on %s" % on_what)
63 start.priority = 75 64
65 - def _start_http_thread(self):
66 """HTTP servers MUST be running in new threads, so that the 67 main thread persists to receive KeyboardInterrupt's. If an 68 exception is raised in the httpserver's thread then it's 69 trapped here, and the bus (and therefore our httpserver) 70 are shut down. 71 """ 72 try: 73 self.httpserver.start() 74 except KeyboardInterrupt, exc: 75 self.bus.log("<Ctrl-C> hit: shutting down HTTP server") 76 self.interrupt = exc 77 self.bus.exit() 78 except SystemExit, exc: 79 self.bus.log("SystemExit raised: shutting down HTTP server") 80 self.interrupt = exc 81 self.bus.exit() 82 raise 83 except: 84 import sys 85 self.interrupt = sys.exc_info()[1] 86 self.bus.log("Error in HTTP server: shutting down", 87 traceback=True, level=40) 88 self.bus.exit() 89 raise
90
91 - def wait(self):
92 """Wait until the HTTP server is ready to receive requests.""" 93 while not getattr(self.httpserver, "ready", False): 94 if self.interrupt: 95 raise self.interrupt 96 time.sleep(.1) 97 98 # Wait for port to be occupied 99 if isinstance(self.bind_addr, tuple): 100 host, port = self.bind_addr 101 wait_for_occupied_port(host, port)
102
103 - def stop(self):
104 """Stop the HTTP server.""" 105 if self.running: 106 # stop() MUST block until the server is *truly* stopped. 107 self.httpserver.stop() 108 # Wait for the socket to be truly freed. 109 if isinstance(self.bind_addr, tuple): 110 wait_for_free_port(*self.bind_addr) 111 self.running = False 112 self.bus.log("HTTP Server %s shut down" % self.httpserver) 113 else: 114 self.bus.log("HTTP Server %s already shut down" % self.httpserver)
115 stop.priority = 25 116
117 - def restart(self):
118 """Restart the HTTP server.""" 119 self.stop() 120 self.start()
121 122
123 -class FlupFCGIServer(object):
124 """Adapter for a flup.server.fcgi.WSGIServer.""" 125
126 - def __init__(self, *args, **kwargs):
127 self.args = args 128 self.kwargs = kwargs 129 self.ready = False
130
131 - def start(self):
132 """Start the FCGI server.""" 133 # We have to instantiate the server class here because its __init__ 134 # starts a threadpool. If we do it too early, daemonize won't work. 135 from flup.server.fcgi import WSGIServer 136 self.fcgiserver = WSGIServer(*self.args, **self.kwargs) 137 # TODO: report this bug upstream to flup. 138 # If we don't set _oldSIGs on Windows, we get: 139 # File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py", 140 # line 108, in run 141 # self._restoreSignalHandlers() 142 # File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py", 143 # line 156, in _restoreSignalHandlers 144 # for signum,handler in self._oldSIGs: 145 # AttributeError: 'WSGIServer' object has no attribute '_oldSIGs' 146 self.fcgiserver._installSignalHandlers = lambda: None 147 self.fcgiserver._oldSIGs = [] 148 self.ready = True 149 self.fcgiserver.run()
150
151 - def stop(self):
152 """Stop the HTTP server.""" 153 # Forcibly stop the fcgi server main event loop. 154 self.fcgiserver._keepGoing = False 155 # Force all worker threads to die off. 156 self.fcgiserver._threadPool.maxSpare = self.fcgiserver._threadPool._idleCount 157 self.ready = False
158 159
160 -class FlupSCGIServer(object):
161 """Adapter for a flup.server.scgi.WSGIServer.""" 162
163 - def __init__(self, *args, **kwargs):
164 self.args = args 165 self.kwargs = kwargs 166 self.ready = False
167
168 - def start(self):
169 """Start the SCGI server.""" 170 # We have to instantiate the server class here because its __init__ 171 # starts a threadpool. If we do it too early, daemonize won't work. 172 from flup.server.scgi import WSGIServer 173 self.scgiserver = WSGIServer(*self.args, **self.kwargs) 174 # TODO: report this bug upstream to flup. 175 # If we don't set _oldSIGs on Windows, we get: 176 # File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py", 177 # line 108, in run 178 # self._restoreSignalHandlers() 179 # File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py", 180 # line 156, in _restoreSignalHandlers 181 # for signum,handler in self._oldSIGs: 182 # AttributeError: 'WSGIServer' object has no attribute '_oldSIGs' 183 self.scgiserver._installSignalHandlers = lambda: None 184 self.scgiserver._oldSIGs = [] 185 self.ready = True 186 self.scgiserver.run()
187
188 - def stop(self):
189 """Stop the HTTP server.""" 190 self.ready = False 191 # Forcibly stop the scgi server main event loop. 192 self.scgiserver._keepGoing = False 193 # Force all worker threads to die off. 194 self.scgiserver._threadPool.maxSpare = 0
195 196
197 -def client_host(server_host):
198 """Return the host on which a client can connect to the given listener.""" 199 if server_host == '0.0.0.0': 200 # 0.0.0.0 is INADDR_ANY, which should answer on localhost. 201 return '127.0.0.1' 202 if server_host == '::': 203 # :: is IN6ADDR_ANY, which should answer on localhost. 204 return '::1' 205 return server_host
206
207 -def check_port(host, port, timeout=1.0):
208 """Raise an error if the given port is not free on the given host.""" 209 if not host: 210 raise ValueError("Host values of '' or None are not allowed.") 211 host = client_host(host) 212 port = int(port) 213 214 import socket 215 216 # AF_INET or AF_INET6 socket 217 # Get the correct address family for our host (allows IPv6 addresses) 218 for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, 219 socket.SOCK_STREAM): 220 af, socktype, proto, canonname, sa = res 221 s = None 222 try: 223 s = socket.socket(af, socktype, proto) 224 # See http://groups.google.com/group/cherrypy-users/ 225 # browse_frm/thread/bbfe5eb39c904fe0 226 s.settimeout(timeout) 227 s.connect((host, port)) 228 s.close() 229 raise IOError("Port %s is in use on %s; perhaps the previous " 230 "httpserver did not shut down properly." % 231 (repr(port), repr(host))) 232 except socket.error: 233 if s: 234 s.close()
235
236 -def wait_for_free_port(host, port):
237 """Wait for the specified port to become free (drop requests).""" 238 if not host: 239 raise ValueError("Host values of '' or None are not allowed.") 240 241 for trial in xrange(50): 242 try: 243 # we are expecting a free port, so reduce the timeout 244 check_port(host, port, timeout=0.1) 245 except IOError: 246 # Give the old server thread time to free the port. 247 time.sleep(0.1) 248 else: 249 return 250 251 raise IOError("Port %r not free on %r" % (port, host))
252
253 -def wait_for_occupied_port(host, port):
254 """Wait for the specified port to become active (receive requests).""" 255 if not host: 256 raise ValueError("Host values of '' or None are not allowed.") 257 258 for trial in xrange(50): 259 try: 260 check_port(host, port) 261 except IOError: 262 return 263 else: 264 time.sleep(.1) 265 266 raise IOError("Port %r not bound on %r" % (port, host))
267