mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-12-03 16:39:25 +01:00
Merge branch 'httpserver' into testing
This commit is contained in:
commit
14393a6959
@ -121,6 +121,7 @@ def main():
|
||||
print 'raise an exception, but freaking-a, it was a string'
|
||||
print 'exception. People who raise string exceptions should'
|
||||
print 'die a slow, painful death.'
|
||||
httpserver.stopServer()
|
||||
now = time.time()
|
||||
seconds = now - world.startedAt
|
||||
log.info('Total uptime: %s.', utils.gen.timeElapsed(seconds))
|
||||
@ -329,6 +330,10 @@ if __name__ == '__main__':
|
||||
import supybot.callbacks as callbacks
|
||||
import supybot.plugins.Owner as Owner
|
||||
|
||||
# These may take some resources, and it does not need to be run while boot, so
|
||||
# we import it as late as possible (but before plugins are loaded).
|
||||
import supybot.utils.httpserver as httpserver
|
||||
|
||||
owner = Owner.Class()
|
||||
|
||||
if options.profile:
|
||||
|
30
src/conf.py
30
src/conf.py
@ -1042,13 +1042,10 @@ utils.web.proxy = supybot.protocols.http.proxy
|
||||
|
||||
|
||||
###
|
||||
# Especially boring stuff.
|
||||
# HTTP server
|
||||
###
|
||||
registerGlobalValue(supybot, 'defaultIgnore',
|
||||
registry.Boolean(False, _("""Determines whether the bot will ignore
|
||||
unregistered users by default. Of course, that'll make it particularly
|
||||
hard for those users to register or identify with the bot, but that's your
|
||||
problem to solve.""")))
|
||||
registerGroup(supybot, 'servers')
|
||||
registerGroup(supybot.servers, 'http')
|
||||
|
||||
class IP(registry.String):
|
||||
"""Value must be a valid IP."""
|
||||
@ -1058,6 +1055,27 @@ class IP(registry.String):
|
||||
else:
|
||||
registry.String.setValue(self, v)
|
||||
|
||||
registerGlobalValue(supybot.servers.http, 'host',
|
||||
IP('0.0.0.0', _("Determines what host the HTTP server will bind.")))
|
||||
registerGlobalValue(supybot.servers.http, 'port',
|
||||
registry.Integer(8080, _("""Determines what port the HTTP server will
|
||||
bind.""")))
|
||||
registerGlobalValue(supybot.servers.http, 'keepAlive',
|
||||
registry.Boolean(True, _("""Defines whether the server will stay alive if
|
||||
no plugin is using it. This also means that the server will start even
|
||||
if it is not used.""")))
|
||||
|
||||
|
||||
###
|
||||
# Especially boring stuff.
|
||||
###
|
||||
registerGlobalValue(supybot, 'defaultIgnore',
|
||||
registry.Boolean(False, _("""Determines whether the bot will ignore
|
||||
unregistered users by default. Of course, that'll make it particularly
|
||||
hard for those users to register or identify with the bot, but that's your
|
||||
problem to solve.""")))
|
||||
|
||||
|
||||
registerGlobalValue(supybot, 'externalIP',
|
||||
IP('', _("""A string that is the external IP of the bot. If this is the
|
||||
empty string, the bot will attempt to find out its IP dynamically (though
|
||||
|
202
src/utils/httpserver.py
Normal file
202
src/utils/httpserver.py
Normal file
@ -0,0 +1,202 @@
|
||||
###
|
||||
# Copyright (c) 2011, Valentin Lorentz
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
###
|
||||
|
||||
"""
|
||||
An embedded and centralized HTTP server for Supybot's plugins.
|
||||
"""
|
||||
|
||||
from threading import Event, Thread
|
||||
from cStringIO import StringIO
|
||||
from SocketServer import ThreadingMixIn
|
||||
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
import supybot.log as log
|
||||
import supybot.conf as conf
|
||||
import supybot.world as world
|
||||
|
||||
configGroup = conf.supybot.servers.http
|
||||
|
||||
if world.testing:
|
||||
class TestHTTPServer(HTTPServer):
|
||||
"""A fake HTTP server for testing purpose."""
|
||||
def __init__(self, address, handler):
|
||||
self.server_address = address
|
||||
self.RequestHandlerClass = handler
|
||||
self.socket = StringIO()
|
||||
self._notServing = Event()
|
||||
self._notServer.set()
|
||||
|
||||
def fileno(self):
|
||||
return hash(self)
|
||||
|
||||
def serve_forever(self, poll_interval=None):
|
||||
self._notServing.clear()
|
||||
self._notServing.wait()
|
||||
|
||||
def shutdown(self):
|
||||
self._notServing.set()
|
||||
|
||||
HTTPServer = TestHTTPServer
|
||||
|
||||
class SupyHTTPServer(HTTPServer):
|
||||
# TODO: make this configurable
|
||||
timeout = 0.5
|
||||
callbacks = {}
|
||||
running = False
|
||||
def hook(self, subdir, callback):
|
||||
if subdir in self.callbacks:
|
||||
raise KeyError('This subdir is already hooked.')
|
||||
else:
|
||||
self.callbacks[subdir] = callback
|
||||
def unhook(self, subdir):
|
||||
callback = self.callbacks.pop(subdir) # May raise a KeyError. We don't care.
|
||||
callback.doUnhook(self)
|
||||
return callback
|
||||
|
||||
class SupyHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path == '/':
|
||||
callback = SupyIndex()
|
||||
else:
|
||||
subdir = self.path.split('/')[1]
|
||||
try:
|
||||
callback = self.server.callbacks[subdir]
|
||||
except KeyError:
|
||||
callback = Supy404()
|
||||
|
||||
# Some shortcuts
|
||||
for name in ('send_response', 'send_header', 'end_headers', 'rfile',
|
||||
'wfile'):
|
||||
setattr(callback, name, getattr(self, name))
|
||||
# We call doGet, because this is more supybotic than do_GET.
|
||||
callback.doGet(self, '/'.join(self.path[2:]))
|
||||
|
||||
def log_message(self, format, *args):
|
||||
log.info('HTTP request: %s - %s' %
|
||||
(self.address_string(), format % args))
|
||||
|
||||
|
||||
class SupyHTTPServerCallback:
|
||||
"""This is a base class that should be overriden by any plugin that want
|
||||
to have a Web interface."""
|
||||
name = "Unnamed plugin"
|
||||
defaultResponse = """
|
||||
This is a default response of the Supybot HTTP server. If you see this
|
||||
message, it probably means you are developping a plugin, and you have
|
||||
neither overriden this message or defined an handler for this query."""
|
||||
|
||||
def doGet(self, handler, path):
|
||||
handler.send_response(400)
|
||||
self.send_header('Content_type', 'text/plain')
|
||||
self.send_header('Content-Length', len(self.defaultResponse))
|
||||
self.end_headers()
|
||||
self.wfile.write(self.defaultResponse)
|
||||
|
||||
def doUnhook(self, handler):
|
||||
"""Method called when unhooking this callback."""
|
||||
pass
|
||||
|
||||
class Supy404(SupyHTTPServerCallback):
|
||||
"""A 404 Not Found error."""
|
||||
name = "Error 404"
|
||||
response = """
|
||||
I am a pretty clever IRC bot, but I suck at serving Web pages, particulary
|
||||
if I don't know what to serve.
|
||||
What I'm saying is you just triggered a 404 Not Found, and I am not
|
||||
trained to help you in such a case."""
|
||||
def doGet(self, handler, path):
|
||||
handler.send_response(404)
|
||||
self.send_header('Content_type', 'text/plain')
|
||||
self.send_header('Content-Length', len(self.response))
|
||||
self.end_headers()
|
||||
self.wfile.write(self.response)
|
||||
|
||||
class SupyIndex(SupyHTTPServerCallback):
|
||||
"""Displays the index of available plugins."""
|
||||
name = "index"
|
||||
defaultResponse = "Request not handled."""
|
||||
template = """
|
||||
<html>
|
||||
<head>
|
||||
<title>Supybot Web server index</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Here is a list of the plugins that have a Web interface:</p>
|
||||
%s
|
||||
</body>
|
||||
</html>"""
|
||||
def doGet(self, handler, path):
|
||||
plugins = [x for x in handler.server.callbacks.items()]
|
||||
if plugins == []:
|
||||
plugins = 'No plugins available.'
|
||||
else:
|
||||
plugins = '<ul><li>%s</li></ul>' % '</li><li>'.join(
|
||||
['<a href="/%s">%s</a>' % (x,y.name) for x,y in plugins])
|
||||
response = self.template % plugins
|
||||
handler.send_response(200)
|
||||
self.send_header('Content_type', 'text/html')
|
||||
self.send_header('Content-Length', len(response))
|
||||
self.end_headers()
|
||||
self.wfile.write(response)
|
||||
|
||||
httpServer = None
|
||||
|
||||
def startServer():
|
||||
"""Starts the HTTP server. Shouldn't be called from other modules.
|
||||
The callback should be an instance of a child of SupyHTTPServerCallback."""
|
||||
global httpServer
|
||||
log.info('Starting HTTP server.')
|
||||
address = (configGroup.host(), configGroup.port())
|
||||
httpServer = SupyHTTPServer(address, SupyHTTPRequestHandler)
|
||||
Thread(target=httpServer.serve_forever, name='HTTP Server').start()
|
||||
|
||||
def stopServer():
|
||||
"""Stops the HTTP server. Should be run only from this module or from
|
||||
when the bot is dying (ie. from supybot.world)"""
|
||||
global httpServer
|
||||
log.info('Stopping HTTP server.')
|
||||
httpServer.shutdown()
|
||||
httpServer = None
|
||||
|
||||
if configGroup.keepAlive():
|
||||
startServer()
|
||||
|
||||
def hook(subdir, callback):
|
||||
"""Sets a callback for a given subdir."""
|
||||
if httpServer is None:
|
||||
startServer()
|
||||
httpServer.hook(subdir, callback)
|
||||
|
||||
def unhook(subdir):
|
||||
"""Unsets the callback assigned to the given subdir, and return it."""
|
||||
global httpServer
|
||||
assert httpServer is not None
|
||||
callback = httpServer.unhook(subdir)
|
||||
if len(httpServer.callbacks) <= 0 and not configGroup.keepAlive():
|
||||
stopServer()
|
Loading…
Reference in New Issue
Block a user