OLD | NEW |
(Empty) | |
| 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._idle
Count |
| 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_A
NY. |
| 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)) |
OLD | NEW |