| 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 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 107 |
self.httpserver.stop() |
|---|
| 108 |
|
|---|
| 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 |
|
|---|
| 130 |
|
|---|
| 131 |
|
|---|
| 132 |
|
|---|
| 133 |
|
|---|
| 134 |
|
|---|
| 135 |
|
|---|
| 136 |
|
|---|
| 137 |
|
|---|
| 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 |
|
|---|
| 150 |
self.fcgiserver._keepGoing = False |
|---|
| 151 |
|
|---|
| 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 |
|
|---|
| 159 |
return '127.0.0.1' |
|---|
| 160 |
if server_host == '::': |
|---|
| 161 |
|
|---|
| 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 |
|
|---|
| 175 |
|
|---|
| 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 |
|
|---|
| 183 |
|
|---|
| 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 |
|
|---|
| 202 |
check_port(host, port, timeout=0.1) |
|---|
| 203 |
except IOError: |
|---|
| 204 |
|
|---|
| 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)) |
|---|