Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(56)

Side by Side Diff: third_party/cherrypy/process/servers.py

Issue 9368042: Add CherryPy to third_party. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build/
Patch Set: '' Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/cherrypy/process/plugins.py ('k') | third_party/cherrypy/process/win32.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(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))
OLDNEW
« no previous file with comments | « third_party/cherrypy/process/plugins.py ('k') | third_party/cherrypy/process/win32.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698