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
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):
138
142
146
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
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
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
215
217 """Stop the HTTP server."""
218 if self.running:
219
220 self.httpserver.stop()
221
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
231 """Restart the HTTP server."""
232 self.stop()
233 self.start()
234
235
237 """Adapter for a flup.server.cgi.WSGIServer."""
238
243
245 """Start the CGI server."""
246
247
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
255 """Stop the HTTP server."""
256 self.ready = False
257
258
260 """Adapter for a flup.server.fcgi.WSGIServer."""
261
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
275 """Start the FCGI server."""
276
277
278 from flup.server.fcgi import WSGIServer
279 self.fcgiserver = WSGIServer(*self.args, **self.kwargs)
280
281
282
283
284
285
286
287
288
289 self.fcgiserver._installSignalHandlers = lambda: None
290 self.fcgiserver._oldSIGs = []
291 self.ready = True
292 self.fcgiserver.run()
293
295 """Stop the HTTP server."""
296
297 self.fcgiserver._keepGoing = False
298
299 self.fcgiserver._threadPool.maxSpare = self.fcgiserver._threadPool._idleCount
300 self.ready = False
301
302
304 """Adapter for a flup.server.scgi.WSGIServer."""
305
310
312 """Start the SCGI server."""
313
314
315 from flup.server.scgi import WSGIServer
316 self.scgiserver = WSGIServer(*self.args, **self.kwargs)
317
318
319
320
321
322
323
324
325
326 self.scgiserver._installSignalHandlers = lambda: None
327 self.scgiserver._oldSIGs = []
328 self.ready = True
329 self.scgiserver.run()
330
332 """Stop the HTTP server."""
333 self.ready = False
334
335 self.scgiserver._keepGoing = False
336
337 self.scgiserver._threadPool.maxSpare = 0
338
339
341 """Return the host on which a client can connect to the given listener."""
342 if server_host == '0.0.0.0':
343
344 return '127.0.0.1'
345 if server_host in ('::', '::0', '::0.0.0.0'):
346
347
348 return '::1'
349 return server_host
350
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
361
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
377
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
390 free_port_timeout = 0.1
391 occupied_port_timeout = 1.0
392
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
403 check_port(host, port, timeout=timeout)
404 except IOError:
405
406 time.sleep(timeout)
407 else:
408 return
409
410 raise IOError("Port %r not free on %r" % (port, host))
411
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