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