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
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):
142
146
150
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
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
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
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
233
235 """Stop the HTTP server."""
236 if self.running:
237
238 self.httpserver.stop()
239
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
249 """Restart the HTTP server."""
250 self.stop()
251 self.start()
252
253
255
256 """Adapter for a flup.server.cgi.WSGIServer."""
257
262
264 """Start the CGI server."""
265
266
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
274 """Stop the HTTP server."""
275 self.ready = False
276
277
279
280 """Adapter for a flup.server.fcgi.WSGIServer."""
281
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
295 """Start the FCGI server."""
296
297
298 from flup.server.fcgi import WSGIServer
299 self.fcgiserver = WSGIServer(*self.args, **self.kwargs)
300
301
302
303
304
305
306
307
308
309 self.fcgiserver._installSignalHandlers = lambda: None
310 self.fcgiserver._oldSIGs = []
311 self.ready = True
312 self.fcgiserver.run()
313
315 """Stop the HTTP server."""
316
317 self.fcgiserver._keepGoing = False
318
319 self.fcgiserver._threadPool.maxSpare = (
320 self.fcgiserver._threadPool._idleCount)
321 self.ready = False
322
323
325
326 """Adapter for a flup.server.scgi.WSGIServer."""
327
332
334 """Start the SCGI server."""
335
336
337 from flup.server.scgi import WSGIServer
338 self.scgiserver = WSGIServer(*self.args, **self.kwargs)
339
340
341
342
343
344
345
346
347
348 self.scgiserver._installSignalHandlers = lambda: None
349 self.scgiserver._oldSIGs = []
350 self.ready = True
351 self.scgiserver.run()
352
354 """Stop the HTTP server."""
355 self.ready = False
356
357 self.scgiserver._keepGoing = False
358
359 self.scgiserver._threadPool.maxSpare = 0
360
361
363 """Return the host on which a client can connect to the given listener."""
364 if server_host == '0.0.0.0':
365
366 return '127.0.0.1'
367 if server_host in ('::', '::0', '::0.0.0.0'):
368
369
370
371 return '::1'
372 return server_host
373
374
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
385
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
403
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
417 free_port_timeout = 0.1
418 occupied_port_timeout = 1.0
419
420
439
440
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
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
461
462
463
464 msg = "Unable to verify that the server is bound on %r" % port
465 warnings.warn(msg)
466