httpserver: Add support for multiple hosts and IPv6. Closes GH-387.

This commit is contained in:
Valentin Lorentz 2012-09-30 18:54:30 +02:00
parent 247ed460b0
commit 784b8c37f8
2 changed files with 50 additions and 21 deletions

View File

@ -1085,8 +1085,12 @@ class IP(registry.String):
else: else:
registry.String.setValue(self, v) registry.String.setValue(self, v)
registerGlobalValue(supybot.servers.http, 'host', registerGlobalValue(supybot.servers.http, 'hosts4',
IP('0.0.0.0', _("Determines what host the HTTP server will bind."))) IP('0.0.0.0', _("""Space-separated list of IPv4 hosts the HTTP server
will bind.""")))
registerGlobalValue(supybot.servers.http, 'hosts6',
IP('', _("""Space-separated list of IPv6 hosts the HTTP server will
bind.""")))
registerGlobalValue(supybot.servers.http, 'port', registerGlobalValue(supybot.servers.http, 'port',
registry.Integer(8080, _("""Determines what port the HTTP server will registry.Integer(8080, _("""Determines what port the HTTP server will
bind."""))) bind.""")))

View File

@ -33,6 +33,7 @@ An embedded and centralized HTTP server for Supybot's plugins.
import os import os
import cgi import cgi
import socket
from threading import Event, Thread from threading import Event, Thread
from cStringIO import StringIO from cStringIO import StringIO
from SocketServer import ThreadingMixIn from SocketServer import ThreadingMixIn
@ -54,8 +55,18 @@ class RequestNotHandled(Exception):
class RealSupyHTTPServer(HTTPServer): class RealSupyHTTPServer(HTTPServer):
# TODO: make this configurable # TODO: make this configurable
timeout = 0.5 timeout = 0.5
callbacks = {}
running = False running = False
def __init__(self, address, protocol, callback):
if protocol == 4:
self.address_family = socket.AF_INET
elif protocol == 6:
self.address_family = socket.AF_INET6
else:
raise AssertionError(protocol)
HTTPServer.__init__(self, address, callback)
self.callbacks = {}
def hook(self, subdir, callback): def hook(self, subdir, callback):
if subdir in self.callbacks: if subdir in self.callbacks:
log.warning(('The HTTP subdirectory `%s` was already hooked but ' log.warning(('The HTTP subdirectory `%s` was already hooked but '
@ -68,6 +79,9 @@ class RealSupyHTTPServer(HTTPServer):
callback.doUnhook(self) callback.doUnhook(self)
return callback return callback
def __str__(self):
return 'server at %s %i' % self.server_address[0:2]
class TestSupyHTTPServer(RealSupyHTTPServer): class TestSupyHTTPServer(RealSupyHTTPServer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
pass pass
@ -241,39 +255,50 @@ class Favicon(SupyHTTPServerCallback):
self.end_headers() self.end_headers()
self.wfile.write(response) self.wfile.write(response)
httpServer = None http_servers = None
def startServer(): def startServer():
"""Starts the HTTP server. Shouldn't be called from other modules. """Starts the HTTP server. Shouldn't be called from other modules.
The callback should be an instance of a child of SupyHTTPServerCallback.""" The callback should be an instance of a child of SupyHTTPServerCallback."""
global httpServer global http_servers
log.info('Starting HTTP server.') addresses4 = [(4, (x, configGroup.port()))
address = (configGroup.host(), configGroup.port()) for x in configGroup.hosts4().split(' ') if x != '']
httpServer = SupyHTTPServer(address, SupyHTTPRequestHandler) addresses6 = [(6, (x, configGroup.port()))
Thread(target=httpServer.serve_forever, name='HTTP Server').start() for x in configGroup.hosts6().split(' ') if x != '']
http_servers = []
for protocol, address in (addresses4 + addresses6):
server = SupyHTTPServer(address, protocol, SupyHTTPRequestHandler)
Thread(target=server.serve_forever, name='HTTP Server').start()
http_servers.append(server)
log.info('Starting HTTP server: %s' % str(server))
def stopServer(): def stopServer():
"""Stops the HTTP server. Should be run only from this module or from """Stops the HTTP server. Should be run only from this module or from
when the bot is dying (ie. from supybot.world)""" when the bot is dying (ie. from supybot.world)"""
global httpServer global http_servers
if httpServer is not None: if http_servers is not None:
log.info('Stopping HTTP server.') for server in http_servers:
httpServer.shutdown() log.info('Stopping HTTP server: %s' % str(server))
httpServer = None server.shutdown()
server = None
if configGroup.keepAlive(): if configGroup.keepAlive():
startServer() startServer()
def hook(subdir, callback): def hook(subdir, callback):
"""Sets a callback for a given subdir.""" """Sets a callback for a given subdir."""
if httpServer is None: if http_servers is None:
startServer() startServer()
httpServer.hook(subdir, callback) assert isinstance(http_servers, list)
for server in http_servers:
server.hook(subdir, callback)
def unhook(subdir): def unhook(subdir):
"""Unsets the callback assigned to the given subdir, and return it.""" """Unsets the callback assigned to the given subdir, and return it."""
global httpServer global http_servers
assert httpServer is not None assert isinstance(http_servers, list)
callback = httpServer.unhook(subdir) for server in http_servers:
if len(httpServer.callbacks) <= 0 and not configGroup.keepAlive(): callback = server.unhook(subdir)
stopServer() if len(server.callbacks) <= 0 and not configGroup.keepAlive():
server.shutdown()
http_servers.remove(server)