Removed spurious static_path.
[smonitor.git] / monitor / cherrypy / process / servers.py
blob272e8436fef2ac8c1177c912cda85abf25760a1a
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.
8 Multiple servers/ports
9 ======================
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::
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()
21 .. index:: SCGI
23 FastCGI/SCGI
24 ============
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)
33 s.subscribe()
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.
40 .. _fastcgi:
41 .. index:: FastCGI
43 FastCGI
44 -------
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.
50 CherryPy code
51 ^^^^^^^^^^^^^
53 hello.py::
55 #!/usr/bin/python
56 import cherrypy
58 class HelloWorld:
59 \"""Sample request handler class.\"""
60 def index(self):
61 return "Hello world!"
62 index.exposed = True
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
72 Apache
73 ^^^^^^
75 At the top level in httpd.conf::
77 FastCgiIpcDir /tmp
78 FastCgiServer /path/to/cherry.fcgi -idle-timeout 120 -processes 4
80 And inside the relevant VirtualHost section::
82 # FastCGI config
83 AddHandler fastcgi-script .fcgi
84 ScriptAliasMatch (.*$) /path/to/cherry.fcgi$1
86 Lighttpd
87 ^^^^^^^^
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::
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
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.
114 import sys
115 import time
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))
127 s1.subscribe()
128 s2.subscribe()
129 bus.start()
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
139 def subscribe(self):
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)
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
157 if self.running:
158 self.bus.log("Already serving on %s" % on_what)
159 return
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)
169 import threading
170 t = threading.Thread(target=self._start_http_thread)
171 t.setName("HTTPServer " + t.getName())
172 t.start()
174 self.wait()
175 self.running = True
176 self.bus.log("Serving on %s" % on_what)
177 start.priority = 75
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.
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
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)
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)
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
230 def restart(self):
231 """Restart the HTTP server."""
232 self.stop()
233 self.start()
236 class FlupCGIServer(object):
237 """Adapter for a flup.server.cgi.WSGIServer."""
239 def __init__(self, *args, **kwargs):
240 self.args = args
241 self.kwargs = kwargs
242 self.ready = False
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
250 self.cgiserver = WSGIServer(*self.args, **self.kwargs)
251 self.ready = True
252 self.cgiserver.run()
254 def stop(self):
255 """Stop the HTTP server."""
256 self.ready = False
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:
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
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()
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
303 class FlupSCGIServer(object):
304 """Adapter for a flup.server.scgi.WSGIServer."""
306 def __init__(self, *args, **kwargs):
307 self.args = args
308 self.kwargs = kwargs
309 self.ready = False
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()
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
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
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)
358 import socket
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))]
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()
388 def wait_for_free_port(host, port):
389 """Wait for the specified port to become free (drop requests)."""
390 if not host:
391 raise ValueError("Host values of '' or None are not allowed.")
393 for trial in range(50):
394 try:
395 # we are expecting a free port, so reduce the timeout
396 check_port(host, port, timeout=0.1)
397 except IOError:
398 # Give the old server thread time to free the port.
399 time.sleep(0.1)
400 else:
401 return
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)."""
407 if not host:
408 raise ValueError("Host values of '' or None are not allowed.")
410 for trial in range(50):
411 try:
412 check_port(host, port)
413 except IOError:
414 return
415 else:
416 time.sleep(.1)
418 raise IOError("Port %r not bound on %r" % (port, host))