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.
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
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))
19 cherrypy.engine.start()
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::
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)
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.
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.
59 \"""Sample request handler class.\"""
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})
68 Then run :doc:`/deployguide/cherryd` with the '-f' arg::
70 cherryd -c <myconfig> -d -f -i hello.py
75 At the top level in httpd.conf::
78 FastCgiServer /path/to/cherry.fcgi -idle-timeout 120 -processes 4
80 And inside the relevant VirtualHost section::
83 AddHandler fastcgi-script .fcgi
84 ScriptAliasMatch (.*$) /path/to/cherry.fcgi$1
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::
98 "bin-path" => "/path/to/your/script.fcgi",
99 "socket" => "/tmp/script.sock",
100 "check-local" => "disable",
103 "max-procs" => 1, # adjust as needed
107 } # end of $HTTP["url"] =~ "^/"
109 Please see `Lighttpd FastCGI Docs
110 <http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModFastCGI>`_ for an explanation
111 of the possible configuration options.
118 class ServerAdapter(object):
119 """Adapter for an HTTP server.
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:
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))
132 def __init__(self
, bus
, httpserver
=None, bind_addr
=None):
134 self
.httpserver
= httpserver
135 self
.bind_addr
= bind_addr
136 self
.interrupt
= None
140 self
.bus
.subscribe('start', self
.start
)
141 self
.bus
.subscribe('stop', self
.stop
)
143 def unsubscribe(self
):
144 self
.bus
.unsubscribe('start', self
.start
)
145 self
.bus
.unsubscribe('stop', self
.stop
)
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
)
155 on_what
= "socket file: %s" % self
.bind_addr
158 self
.bus
.log("Already serving on %s" % on_what
)
161 self
.interrupt
= None
162 if not self
.httpserver
:
163 raise ValueError("No HTTP server has been created.")
165 # Start the httpserver in a new thread.
166 if isinstance(self
.bind_addr
, tuple):
167 wait_for_free_port(*self
.bind_addr
)
170 t
= threading
.Thread(target
=self
._start
_http
_thread
)
171 t
.setName("HTTPServer " + t
.getName())
176 self
.bus
.log("Serving on %s" % on_what
)
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)
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]
193 self
.bus
.log("SystemExit raised: shutting down HTTP server")
194 self
.interrupt
= sys
.exc_info()[1]
198 self
.interrupt
= sys
.exc_info()[1]
199 self
.bus
.log("Error in HTTP server: shutting down",
200 traceback
=True, level
=40)
205 """Wait until the HTTP server is ready to receive requests."""
206 while not getattr(self
.httpserver
, "ready", False):
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
)
217 """Stop the HTTP server."""
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
)
225 self
.bus
.log("HTTP Server %s shut down" % self
.httpserver
)
227 self
.bus
.log("HTTP Server %s already shut down" % self
.httpserver
)
231 """Restart the HTTP server."""
236 class FlupCGIServer(object):
237 """Adapter for a flup.server.cgi.WSGIServer."""
239 def __init__(self
, *args
, **kwargs
):
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
250 self
.cgiserver
= WSGIServer(*self
.args
, **self
.kwargs
)
255 """Stop the HTTP server."""
259 class FlupFCGIServer(object):
260 """Adapter for a flup.server.fcgi.WSGIServer."""
262 def __init__(self
, *args
, **kwargs
):
263 if kwargs
.get('bindAddress', None) is None:
265 if not hasattr(socket
, 'fromfd'):
267 'Dynamic FCGI server not available on this platform. '
268 'You must use a static or external one by providing a '
269 'legal bindAddress.')
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",
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
= []
292 self
.fcgiserver
.run()
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
303 class FlupSCGIServer(object):
304 """Adapter for a flup.server.scgi.WSGIServer."""
306 def __init__(self
, *args
, **kwargs
):
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",
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
= []
329 self
.scgiserver
.run()
332 """Stop the HTTP server."""
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
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.
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.
351 def check_port(host
, port
, timeout
=1.0):
352 """Raise an error if the given port is not free on the given host."""
354 raise ValueError("Host values of '' or None are not allowed.")
355 host
= client_host(host
)
360 # AF_INET or AF_INET6 socket
361 # Get the correct address family for our host (allows IPv6 addresses)
363 info
= socket
.getaddrinfo(host
, port
, socket
.AF_UNSPEC
,
365 except socket
.gaierror
:
367 info
= [(socket
.AF_INET6
, socket
.SOCK_STREAM
, 0, "", (host
, port
, 0, 0))]
369 info
= [(socket
.AF_INET
, socket
.SOCK_STREAM
, 0, "", (host
, port
))]
372 af
, socktype
, proto
, canonname
, sa
= res
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
))
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
)))
388 def wait_for_free_port(host
, port
):
389 """Wait for the specified port to become free (drop requests)."""
391 raise ValueError("Host values of '' or None are not allowed.")
393 for trial
in range(50):
395 # we are expecting a free port, so reduce the timeout
396 check_port(host
, port
, timeout
=0.1)
398 # Give the old server thread time to free the port.
403 raise IOError("Port %r not free on %r" % (port
, host
))
405 def wait_for_occupied_port(host
, port
):
406 """Wait for the specified port to become active (receive requests)."""
408 raise ValueError("Host values of '' or None are not allowed.")
410 for trial
in range(50):
412 check_port(host
, port
)
418 raise IOError("Port %r not bound on %r" % (port
, host
))