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

Source Code for Module cherrypy.process.servers

  1  """ 
  2  Starting in CherryPy 3.1, cherrypy.server is implemented as an 
  3  :ref:`Engine Plugin<plugins>`. It's an instance of 
  4  :class:`cherrypy._cpserver.Server`, which is a subclass of 
  5  :class:`cherrypy.process.servers.ServerAdapter`. The ``ServerAdapter`` class 
  6  is designed to control other servers, as well. 
  7   
  8  Multiple servers/ports 
  9  ====================== 
 10   
 11  If you need to start more than one HTTP server (to serve on multiple ports, or 
 12  protocols, etc.), you can manually register each one and then start them all 
 13  with engine.start:: 
 14   
 15      s1 = ServerAdapter(cherrypy.engine, MyWSGIServer(host='0.0.0.0', port=80)) 
 16      s2 = ServerAdapter(cherrypy.engine, another.HTTPServer(host='127.0.0.1', SSL=True)) 
 17      s1.subscribe() 
 18      s2.subscribe() 
 19      cherrypy.engine.start() 
 20   
 21  .. index:: SCGI 
 22   
 23  FastCGI/SCGI 
 24  ============ 
 25   
 26  There are also Flup\ **F**\ CGIServer and Flup\ **S**\ CGIServer classes in 
 27  :mod:`cherrypy.process.servers`. To start an fcgi server, for example, 
 28  wrap an instance of it in a ServerAdapter:: 
 29   
 30      addr = ('0.0.0.0', 4000) 
 31      f = servers.FlupFCGIServer(application=cherrypy.tree, bindAddress=addr) 
 32      s = servers.ServerAdapter(cherrypy.engine, httpserver=f, bind_addr=addr) 
 33      s.subscribe() 
 34   
 35  The :doc:`cherryd</deployguide/cherryd>` startup script will do the above for 
 36  you via its `-f` flag. 
 37  Note that you need to download and install `flup <http://trac.saddi.com/flup>`_ 
 38  yourself, whether you use ``cherryd`` or not. 
 39   
 40  .. _fastcgi: 
 41  .. index:: FastCGI 
 42   
 43  FastCGI 
 44  ------- 
 45   
 46  A very simple setup lets your cherry run with FastCGI. 
 47  You just need the flup library, 
 48  plus a running Apache server (with ``mod_fastcgi``) or lighttpd server. 
 49   
 50  CherryPy code 
 51  ^^^^^^^^^^^^^ 
 52   
 53  hello.py:: 
 54   
 55      #!/usr/bin/python 
 56      import cherrypy 
 57       
 58      class HelloWorld: 
 59          \"""Sample request handler class.\""" 
 60          def index(self): 
 61              return "Hello world!" 
 62          index.exposed = True 
 63       
 64      cherrypy.tree.mount(HelloWorld()) 
 65      # CherryPy autoreload must be disabled for the flup server to work 
 66      cherrypy.config.update({'engine.autoreload_on':False}) 
 67   
 68  Then run :doc:`/deployguide/cherryd` with the '-f' arg:: 
 69   
 70      cherryd -c <myconfig> -d -f -i hello.py 
 71   
 72  Apache 
 73  ^^^^^^ 
 74   
 75  At the top level in httpd.conf:: 
 76   
 77      FastCgiIpcDir /tmp 
 78      FastCgiServer /path/to/cherry.fcgi -idle-timeout 120 -processes 4 
 79   
 80  And inside the relevant VirtualHost section:: 
 81   
 82      # FastCGI config 
 83      AddHandler fastcgi-script .fcgi 
 84      ScriptAliasMatch (.*$) /path/to/cherry.fcgi$1 
 85   
 86  Lighttpd 
 87  ^^^^^^^^ 
 88   
 89  For `Lighttpd <http://www.lighttpd.net/>`_ you can follow these 
 90  instructions. Within ``lighttpd.conf`` make sure ``mod_fastcgi`` is 
 91  active within ``server.modules``. Then, within your ``$HTTP["host"]`` 
 92  directive, configure your fastcgi script like the following:: 
 93   
 94      $HTTP["url"] =~ "" { 
 95        fastcgi.server = ( 
 96          "/" => ( 
 97            "script.fcgi" => ( 
 98              "bin-path" => "/path/to/your/script.fcgi", 
 99              "socket"          => "/tmp/script.sock", 
100              "check-local"     => "disable", 
101              "disable-time"    => 1, 
102              "min-procs"       => 1, 
103              "max-procs"       => 1, # adjust as needed 
104            ), 
105          ), 
106        ) 
107      } # end of $HTTP["url"] =~ "^/" 
108   
109  Please see `Lighttpd FastCGI Docs 
110  <http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModFastCGI>`_ for an explanation  
111  of the possible configuration options. 
112  """ 
113   
114  import sys 
115  import time 
116   
117   
118 -class ServerAdapter(object):
119 """Adapter for an HTTP server. 120 121 If you need to start more than one HTTP server (to serve on multiple 122 ports, or protocols, etc.), you can manually register each one and then 123 start them all with bus.start: 124 125 s1 = ServerAdapter(bus, MyWSGIServer(host='0.0.0.0', port=80)) 126 s2 = ServerAdapter(bus, another.HTTPServer(host='127.0.0.1', SSL=True)) 127 s1.subscribe() 128 s2.subscribe() 129 bus.start() 130 """ 131
132 - def __init__(self, bus, httpserver=None, bind_addr=None):
133 self.bus = bus 134 self.httpserver = httpserver 135 self.bind_addr = bind_addr 136 self.interrupt = None 137 self.running = False
138
139 - def subscribe(self):
140 self.bus.subscribe('start', self.start) 141 self.bus.subscribe('stop', self.stop)
142
143 - def unsubscribe(self):
144 self.bus.unsubscribe('start', self.start) 145 self.bus.unsubscribe('stop', self.stop)
146
147 - def start(self):
148 """Start the HTTP server.""" 149 if self.bind_addr is None: 150 on_what = "unknown interface (dynamic?)" 151 elif isinstance(self.bind_addr, tuple): 152 host, port = self.bind_addr 153 on_what = "%s:%s" % (host, port) 154 else: 155 on_what = "socket file: %s" % self.bind_addr 156 157 if self.running: 158 self.bus.log("Already serving on %s" % on_what) 159 return 160 161 self.interrupt = None 162 if not self.httpserver: 163 raise ValueError("No HTTP server has been created.") 164 165 # Start the httpserver in a new thread. 166 if isinstance(self.bind_addr, tuple): 167 wait_for_free_port(*self.bind_addr) 168 169 import threading 170 t = threading.Thread(target=self._start_http_thread) 171 t.setName("HTTPServer " + t.getName()) 172 t.start() 173 174 self.wait() 175 self.running = True 176 self.bus.log("Serving on %s" % on_what)
177 start.priority = 75 178
179 - def _start_http_thread(self):
180 """HTTP servers MUST be running in new threads, so that the 181 main thread persists to receive KeyboardInterrupt's. If an 182 exception is raised in the httpserver's thread then it's 183 trapped here, and the bus (and therefore our httpserver) 184 are shut down. 185 """ 186 try: 187 self.httpserver.start() 188 except KeyboardInterrupt: 189 self.bus.log("<Ctrl-C> hit: shutting down HTTP server") 190 self.interrupt = sys.exc_info()[1] 191 self.bus.exit() 192 except SystemExit: 193 self.bus.log("SystemExit raised: shutting down HTTP server") 194 self.interrupt = sys.exc_info()[1] 195 self.bus.exit() 196 raise 197 except: 198 self.interrupt = sys.exc_info()[1] 199 self.bus.log("Error in HTTP server: shutting down", 200 traceback=True, level=40) 201 self.bus.exit() 202 raise
203
204 - def wait(self):
205 """Wait until the HTTP server is ready to receive requests.""" 206 while not getattr(self.httpserver, "ready", False): 207 if self.interrupt: 208 raise self.interrupt 209 time.sleep(.1) 210 211 # Wait for port to be occupied 212 if isinstance(self.bind_addr, tuple): 213 host, port = self.bind_addr 214 wait_for_occupied_port(host, port)
215
216 - def stop(self):
217 """Stop the HTTP server.""" 218 if self.running: 219 # stop() MUST block until the server is *truly* stopped. 220 self.httpserver.stop() 221 # Wait for the socket to be truly freed. 222 if isinstance(self.bind_addr, tuple): 223 wait_for_free_port(*self.bind_addr) 224 self.running = False 225 self.bus.log("HTTP Server %s shut down" % self.httpserver) 226 else: 227 self.bus.log("HTTP Server %s already shut down" % self.httpserver)
228 stop.priority = 25 229
230 - def restart(self):
231 """Restart the HTTP server.""" 232 self.stop() 233 self.start()
234 235
236 -class FlupCGIServer(object):
237 """Adapter for a flup.server.cgi.WSGIServer.""" 238
239 - def __init__(self, *args, **kwargs):
240 self.args = args 241 self.kwargs = kwargs 242 self.ready = False
243
244 - def start(self):
245 """Start the CGI server.""" 246 # We have to instantiate the server class here because its __init__ 247 # starts a threadpool. If we do it too early, daemonize won't work. 248 from flup.server.cgi import WSGIServer 249 250 self.cgiserver = WSGIServer(*self.args, **self.kwargs) 251 self.ready = True 252 self.cgiserver.run()
253
254 - def stop(self):
255 """Stop the HTTP server.""" 256 self.ready = False
257 258
259 -class FlupFCGIServer(object):
260 """Adapter for a flup.server.fcgi.WSGIServer.""" 261
262 - def __init__(self, *args, **kwargs):
263 if kwargs.get('bindAddress', None) is None: 264 import socket 265 if not hasattr(socket, 'fromfd'): 266 raise ValueError( 267 'Dynamic FCGI server not available on this platform. ' 268 'You must use a static or external one by providing a ' 269 'legal bindAddress.') 270 self.args = args 271 self.kwargs = kwargs 272 self.ready = False
273
274 - def start(self):
275 """Start the FCGI server.""" 276 # We have to instantiate the server class here because its __init__ 277 # starts a threadpool. If we do it too early, daemonize won't work. 278 from flup.server.fcgi import WSGIServer 279 self.fcgiserver = WSGIServer(*self.args, **self.kwargs) 280 # TODO: report this bug upstream to flup. 281 # If we don't set _oldSIGs on Windows, we get: 282 # File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py", 283 # line 108, in run 284 # self._restoreSignalHandlers() 285 # File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py", 286 # line 156, in _restoreSignalHandlers 287 # for signum,handler in self._oldSIGs: 288 # AttributeError: 'WSGIServer' object has no attribute '_oldSIGs' 289 self.fcgiserver._installSignalHandlers = lambda: None 290 self.fcgiserver._oldSIGs = [] 291 self.ready = True 292 self.fcgiserver.run()
293
294 - def stop(self):
295 """Stop the HTTP server.""" 296 # Forcibly stop the fcgi server main event loop. 297 self.fcgiserver._keepGoing = False 298 # Force all worker threads to die off. 299 self.fcgiserver._threadPool.maxSpare = self.fcgiserver._threadPool._idleCount 300 self.ready = False
301 302
303 -class FlupSCGIServer(object):
304 """Adapter for a flup.server.scgi.WSGIServer.""" 305
306 - def __init__(self, *args, **kwargs):
307 self.args = args 308 self.kwargs = kwargs 309 self.ready = False
310
311 - def start(self):
312 """Start the SCGI server.""" 313 # We have to instantiate the server class here because its __init__ 314 # starts a threadpool. If we do it too early, daemonize won't work. 315 from flup.server.scgi import WSGIServer 316 self.scgiserver = WSGIServer(*self.args, **self.kwargs) 317 # TODO: report this bug upstream to flup. 318 # If we don't set _oldSIGs on Windows, we get: 319 # File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py", 320 # line 108, in run 321 # self._restoreSignalHandlers() 322 # File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py", 323 # line 156, in _restoreSignalHandlers 324 # for signum,handler in self._oldSIGs: 325 # AttributeError: 'WSGIServer' object has no attribute '_oldSIGs' 326 self.scgiserver._installSignalHandlers = lambda: None 327 self.scgiserver._oldSIGs = [] 328 self.ready = True 329 self.scgiserver.run()
330
331 - def stop(self):
332 """Stop the HTTP server.""" 333 self.ready = False 334 # Forcibly stop the scgi server main event loop. 335 self.scgiserver._keepGoing = False 336 # Force all worker threads to die off. 337 self.scgiserver._threadPool.maxSpare = 0
338 339
340 -def client_host(server_host):
341 """Return the host on which a client can connect to the given listener.""" 342 if server_host == '0.0.0.0': 343 # 0.0.0.0 is INADDR_ANY, which should answer on localhost. 344 return '127.0.0.1' 345 if server_host in ('::', '::0', '::0.0.0.0'): 346 # :: is IN6ADDR_ANY, which should answer on localhost. 347 # ::0 and ::0.0.0.0 are non-canonical but common ways to write IN6ADDR_ANY. 348 return '::1' 349 return server_host
350
351 -def check_port(host, port, timeout=1.0):
352 """Raise an error if the given port is not free on the given host.""" 353 if not host: 354 raise ValueError("Host values of '' or None are not allowed.") 355 host = client_host(host) 356 port = int(port) 357 358 import socket 359 360 # AF_INET or AF_INET6 socket 361 # Get the correct address family for our host (allows IPv6 addresses) 362 try: 363 info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, 364 socket.SOCK_STREAM) 365 except socket.gaierror: 366 if ':' in host: 367 info = [(socket.AF_INET6, socket.SOCK_STREAM, 0, "", (host, port, 0, 0))] 368 else: 369 info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", (host, port))] 370 371 for res in info: 372 af, socktype, proto, canonname, sa = res 373 s = None 374 try: 375 s = socket.socket(af, socktype, proto) 376 # See http://groups.google.com/group/cherrypy-users/ 377 # browse_frm/thread/bbfe5eb39c904fe0 378 s.settimeout(timeout) 379 s.connect((host, port)) 380 s.close() 381 raise IOError("Port %s is in use on %s; perhaps the previous " 382 "httpserver did not shut down properly." % 383 (repr(port), repr(host))) 384 except socket.error: 385 if s: 386 s.close()
387 388 389 # Feel free to increase these defaults on slow systems: 390 free_port_timeout = 0.1 391 occupied_port_timeout = 1.0 392
393 -def wait_for_free_port(host, port, timeout=None):
394 """Wait for the specified port to become free (drop requests).""" 395 if not host: 396 raise ValueError("Host values of '' or None are not allowed.") 397 if timeout is None: 398 timeout = free_port_timeout 399 400 for trial in range(50): 401 try: 402 # we are expecting a free port, so reduce the timeout 403 check_port(host, port, timeout=timeout) 404 except IOError: 405 # Give the old server thread time to free the port. 406 time.sleep(timeout) 407 else: 408 return 409 410 raise IOError("Port %r not free on %r" % (port, host))
411
412 -def wait_for_occupied_port(host, port, timeout=None):
413 """Wait for the specified port to become active (receive requests).""" 414 if not host: 415 raise ValueError("Host values of '' or None are not allowed.") 416 if timeout is None: 417 timeout = occupied_port_timeout 418 419 for trial in range(50): 420 try: 421 check_port(host, port, timeout=timeout) 422 except IOError: 423 return 424 else: 425 time.sleep(timeout) 426 427 raise IOError("Port %r not bound on %r" % (port, host))
428