Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/trunk/cherrypy/process/servers.py

Revision 1974 (checked in by fumanchu, 2 months ago)

Buried a couple socket, threading module imports for easier Google App Engine integration.

  • Property svn:eol-style set to native
Line 
1 """Adapt an HTTP server."""
2
3 import time
4
5
6 class ServerAdapter(object):
7     """Adapter for an HTTP server.
8     
9     If you need to start more than one HTTP server (to serve on multiple
10     ports, or protocols, etc.), you can manually register each one and then
11     start them all with bus.start:
12     
13         s1 = ServerAdapter(bus, MyWSGIServer(host='0.0.0.0', port=80))
14         s2 = ServerAdapter(bus, another.HTTPServer(host='127.0.0.1', SSL=True))
15         s1.subscribe()
16         s2.subscribe()
17         bus.start()
18     """
19    
20     def __init__(self, bus, httpserver=None, bind_addr=None):
21         self.bus = bus
22         self.httpserver = httpserver
23         self.bind_addr = bind_addr
24         self.interrupt = None
25         self.running = False
26    
27     def subscribe(self):
28         self.bus.subscribe('start', self.start)
29         self.bus.subscribe('stop', self.stop)
30    
31     def unsubscribe(self):
32         self.bus.unsubscribe('start', self.start)
33         self.bus.unsubscribe('stop', self.stop)
34    
35     def start(self):
36         """Start the HTTP server."""
37         if isinstance(self.bind_addr, tuple):
38             host, port = self.bind_addr
39             on_what = "%s:%s" % (host, port)
40         else:
41             on_what = "socket file: %s" % self.bind_addr
42        
43         if self.running:
44             self.bus.log("Already serving on %s" % on_what)
45             return
46        
47         self.interrupt = None
48         if not self.httpserver:
49             raise ValueError("No HTTP server has been created.")
50        
51         # Start the httpserver in a new thread.
52         if isinstance(self.bind_addr, tuple):
53             wait_for_free_port(*self.bind_addr)
54        
55         import threading
56         t = threading.Thread(target=self._start_http_thread)
57         t.setName("HTTPServer " + t.getName())
58         t.start()
59        
60         self.wait()
61         self.running = True
62         self.bus.log("Serving on %s" % on_what)
63     start.priority = 75
64    
65     def _start_http_thread(self):
66         """HTTP servers MUST be running in new threads, so that the
67         main thread persists to receive KeyboardInterrupt's. If an
68         exception is raised in the httpserver's thread then it's
69         trapped here, and the bus (and therefore our httpserver)
70         are shut down.
71         """
72         try:
73             self.httpserver.start()
74         except KeyboardInterrupt, exc:
75             self.bus.log("<Ctrl-C> hit: shutting down HTTP server")
76             self.interrupt = exc
77             self.bus.exit()
78         except SystemExit, exc:
79             self.bus.log("SystemExit raised: shutting down HTTP server")
80             self.interrupt = exc
81             self.bus.exit()
82             raise
83         except:
84             import sys
85             self.interrupt = sys.exc_info()[1]
86             self.bus.log("Error in HTTP server: shutting down",
87                          traceback=True, level=40)
88             self.bus.exit()
89             raise
90    
91     def wait(self):
92         """Wait until the HTTP server is ready to receive requests."""
93         while not getattr(self.httpserver, "ready", False):
94             if self.interrupt:
95                 raise self.interrupt
96             time.sleep(.1)
97        
98         # Wait for port to be occupied
99         if isinstance(self.bind_addr, tuple):
100             host, port = self.bind_addr
101             wait_for_occupied_port(host, port)
102    
103     def stop(self):
104         """Stop the HTTP server."""
105         if self.running:
106             # stop() MUST block until the server is *truly* stopped.
107             self.httpserver.stop()
108             # Wait for the socket to be truly freed.
109             if isinstance(self.bind_addr, tuple):
110                 wait_for_free_port(*self.bind_addr)
111             self.running = False
112             self.bus.log("HTTP Server %s shut down" % self.httpserver)
113         else:
114             self.bus.log("HTTP Server %s already shut down" % self.httpserver)
115     stop.priority = 25
116    
117     def restart(self):
118         """Restart the HTTP server."""
119         self.stop()
120         self.start()
121
122
123 class FlupFCGIServer(object):
124     """Adapter for a flup.server.fcgi.WSGIServer."""
125    
126     def __init__(self, *args, **kwargs):
127         from flup.server.fcgi import WSGIServer
128         self.fcgiserver = WSGIServer(*args, **kwargs)
129         # TODO: report this bug upstream to flup.
130         # If we don't set _oldSIGs on Windows, we get:
131         #   File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py",
132         #   line 108, in run
133         #     self._restoreSignalHandlers()
134         #   File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py",
135         #   line 156, in _restoreSignalHandlers
136         #     for signum,handler in self._oldSIGs:
137         #   AttributeError: 'WSGIServer' object has no attribute '_oldSIGs'
138         self.fcgiserver._oldSIGs = []
139         self.ready = False
140    
141     def start(self):
142         """Start the FCGI server."""
143         self.ready = True
144         self.fcgiserver.run()
145    
146     def stop(self):
147         """Stop the HTTP server."""
148         self.ready = False
149         # Forcibly stop the fcgi server main event loop.
150         self.fcgiserver._keepGoing = False
151         # Force all worker threads to die off.
152         self.fcgiserver._threadPool.maxSpare = 0
153
154
155 def client_host(server_host):
156     """Return the host on which a client can connect to the given listener."""
157     if server_host == '0.0.0.0':
158         # 0.0.0.0 is INADDR_ANY, which should answer on localhost.
159         return '127.0.0.1'
160     if server_host == '::':
161         # :: is IN6ADDR_ANY, which should answer on localhost.
162         return '::1'
163     return server_host
164
165 def check_port(host, port, timeout=1.0):
166     """Raise an error if the given port is not free on the given host."""
167     if not host:
168         raise ValueError("Host values of '' or None are not allowed.")
169     host = client_host(host)
170     port = int(port)
171    
172     import socket
173    
174     # AF_INET or AF_INET6 socket
175     # Get the correct address family for our host (allows IPv6 addresses)
176     for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
177                                   socket.SOCK_STREAM):
178         af, socktype, proto, canonname, sa = res
179         s = None
180         try:
181             s = socket.socket(af, socktype, proto)
182             # See http://groups.google.com/group/cherrypy-users/
183             #        browse_frm/thread/bbfe5eb39c904fe0
184             s.settimeout(timeout)
185             s.connect((host, port))
186             s.close()
187             raise IOError("Port %s is in use on %s; perhaps the previous "
188                           "httpserver did not shut down properly." %
189                           (repr(port), repr(host)))
190         except socket.error:
191             if s:
192                 s.close()
193
194 def wait_for_free_port(host, port):
195     """Wait for the specified port to become free (drop requests)."""
196     if not host:
197         raise ValueError("Host values of '' or None are not allowed.")
198    
199     for trial in xrange(50):
200         try:
201             # we are expecting a free port, so reduce the timeout
202             check_port(host, port, timeout=0.1)
203         except IOError:
204             # Give the old server thread time to free the port.
205             time.sleep(0.1)
206         else:
207             return
208    
209     raise IOError("Port %r not free on %r" % (port, host))
210
211 def wait_for_occupied_port(host, port):
212     """Wait for the specified port to become active (receive requests)."""
213     if not host:
214         raise ValueError("Host values of '' or None are not allowed.")
215    
216     for trial in xrange(50):
217         try:
218             check_port(host, port)
219         except IOError:
220             return
221         else:
222             time.sleep(.1)
223    
224     raise IOError("Port %r not bound on %r" % (port, host))
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets