1
"""Adapt an HTTP server."""
6
class ServerAdapter(object):
7
"""Adapter for an HTTP server.
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:
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))
20
def __init__(self, bus, httpserver=None, bind_addr=None):
22
self.httpserver = httpserver
23
self.bind_addr = bind_addr
28
self.bus.subscribe('start', self.start)
29
self.bus.subscribe('stop', self.stop)
31
def unsubscribe(self):
32
self.bus.unsubscribe('start', self.start)
33
self.bus.unsubscribe('stop', self.stop)
36
"""Start the HTTP server."""
37
if self.bind_addr is None:
38
on_what = "unknown interface (dynamic?)"
39
elif isinstance(self.bind_addr, tuple):
40
host, port = self.bind_addr
41
on_what = "%s:%s" % (host, port)
43
on_what = "socket file: %s" % self.bind_addr
46
self.bus.log("Already serving on %s" % on_what)
50
if not self.httpserver:
51
raise ValueError("No HTTP server has been created.")
53
# Start the httpserver in a new thread.
54
if isinstance(self.bind_addr, tuple):
55
wait_for_free_port(*self.bind_addr)
58
t = threading.Thread(target=self._start_http_thread)
59
t.setName("HTTPServer " + t.getName())
64
self.bus.log("Serving on %s" % on_what)
67
def _start_http_thread(self):
68
"""HTTP servers MUST be running in new threads, so that the
69
main thread persists to receive KeyboardInterrupt's. If an
70
exception is raised in the httpserver's thread then it's
71
trapped here, and the bus (and therefore our httpserver)
75
self.httpserver.start()
76
except KeyboardInterrupt, exc:
77
self.bus.log("<Ctrl-C> hit: shutting down HTTP server")
80
except SystemExit, exc:
81
self.bus.log("SystemExit raised: shutting down HTTP server")
87
self.interrupt = sys.exc_info()[1]
88
self.bus.log("Error in HTTP server: shutting down",
89
traceback=True, level=40)
94
"""Wait until the HTTP server is ready to receive requests."""
95
while not getattr(self.httpserver, "ready", False):
100
# Wait for port to be occupied
101
if isinstance(self.bind_addr, tuple):
102
host, port = self.bind_addr
103
wait_for_occupied_port(host, port)
106
"""Stop the HTTP server."""
108
# stop() MUST block until the server is *truly* stopped.
109
self.httpserver.stop()
110
# Wait for the socket to be truly freed.
111
if isinstance(self.bind_addr, tuple):
112
wait_for_free_port(*self.bind_addr)
114
self.bus.log("HTTP Server %s shut down" % self.httpserver)
116
self.bus.log("HTTP Server %s already shut down" % self.httpserver)
120
"""Restart the HTTP server."""
125
class FlupFCGIServer(object):
126
"""Adapter for a flup.server.fcgi.WSGIServer."""
128
def __init__(self, *args, **kwargs):
129
if kwargs.get('bindAddress', None) is None:
131
if not hasattr(socket.socket, 'fromfd'):
133
'Dynamic FCGI server not available on this platform. '
134
'You must use a static or external one by providing a '
135
'legal bindAddress.')
141
"""Start the FCGI server."""
142
# We have to instantiate the server class here because its __init__
143
# starts a threadpool. If we do it too early, daemonize won't work.
144
from flup.server.fcgi import WSGIServer
145
self.fcgiserver = WSGIServer(*self.args, **self.kwargs)
146
# TODO: report this bug upstream to flup.
147
# If we don't set _oldSIGs on Windows, we get:
148
# File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py",
150
# self._restoreSignalHandlers()
151
# File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py",
152
# line 156, in _restoreSignalHandlers
153
# for signum,handler in self._oldSIGs:
154
# AttributeError: 'WSGIServer' object has no attribute '_oldSIGs'
155
self.fcgiserver._installSignalHandlers = lambda: None
156
self.fcgiserver._oldSIGs = []
158
self.fcgiserver.run()
161
"""Stop the HTTP server."""
162
# Forcibly stop the fcgi server main event loop.
163
self.fcgiserver._keepGoing = False
164
# Force all worker threads to die off.
165
self.fcgiserver._threadPool.maxSpare = self.fcgiserver._threadPool._idleCount
169
class FlupSCGIServer(object):
170
"""Adapter for a flup.server.scgi.WSGIServer."""
172
def __init__(self, *args, **kwargs):
178
"""Start the SCGI server."""
179
# We have to instantiate the server class here because its __init__
180
# starts a threadpool. If we do it too early, daemonize won't work.
181
from flup.server.scgi import WSGIServer
182
self.scgiserver = WSGIServer(*self.args, **self.kwargs)
183
# TODO: report this bug upstream to flup.
184
# If we don't set _oldSIGs on Windows, we get:
185
# File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py",
187
# self._restoreSignalHandlers()
188
# File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py",
189
# line 156, in _restoreSignalHandlers
190
# for signum,handler in self._oldSIGs:
191
# AttributeError: 'WSGIServer' object has no attribute '_oldSIGs'
192
self.scgiserver._installSignalHandlers = lambda: None
193
self.scgiserver._oldSIGs = []
195
self.scgiserver.run()
198
"""Stop the HTTP server."""
200
# Forcibly stop the scgi server main event loop.
201
self.scgiserver._keepGoing = False
202
# Force all worker threads to die off.
203
self.scgiserver._threadPool.maxSpare = 0
206
def client_host(server_host):
207
"""Return the host on which a client can connect to the given listener."""
208
if server_host == '0.0.0.0':
209
# 0.0.0.0 is INADDR_ANY, which should answer on localhost.
211
if server_host == '::':
212
# :: is IN6ADDR_ANY, which should answer on localhost.
216
def check_port(host, port, timeout=1.0):
217
"""Raise an error if the given port is not free on the given host."""
219
raise ValueError("Host values of '' or None are not allowed.")
220
host = client_host(host)
225
# AF_INET or AF_INET6 socket
226
# Get the correct address family for our host (allows IPv6 addresses)
228
info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
230
except socket.gaierror:
232
info = [(socket.AF_INET6, socket.SOCK_STREAM, 0, "", (host, port, 0, 0))]
234
info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", (host, port))]
237
af, socktype, proto, canonname, sa = res
240
s = socket.socket(af, socktype, proto)
241
# See http://groups.google.com/group/cherrypy-users/
242
# browse_frm/thread/bbfe5eb39c904fe0
243
s.settimeout(timeout)
244
s.connect((host, port))
246
raise IOError("Port %s is in use on %s; perhaps the previous "
247
"httpserver did not shut down properly." %
248
(repr(port), repr(host)))
253
def wait_for_free_port(host, port):
254
"""Wait for the specified port to become free (drop requests)."""
256
raise ValueError("Host values of '' or None are not allowed.")
258
for trial in range(50):
260
# we are expecting a free port, so reduce the timeout
261
check_port(host, port, timeout=0.1)
263
# Give the old server thread time to free the port.
268
raise IOError("Port %r not free on %r" % (port, host))
270
def wait_for_occupied_port(host, port):
271
"""Wait for the specified port to become active (receive requests)."""
273
raise ValueError("Host values of '' or None are not allowed.")
275
for trial in range(50):
277
check_port(host, port)
283
raise IOError("Port %r not bound on %r" % (port, host))