mirror of
				https://github.com/Mikaela/Limnoria.git
				synced 2025-10-30 23:27:24 +01:00 
			
		
		
		
	Add an embedded HTTP server to Supybot.
This commit is contained in:
		
							parent
							
								
									2c996b7459
								
							
						
					
					
						commit
						13e4f45e30
					
				| @ -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)) | ||||
| @ -331,6 +332,10 @@ if __name__ == '__main__': | ||||
| 
 | ||||
|     owner = Owner.Class() | ||||
| 
 | ||||
|     # These may take some resources, and it does not need to be run while boot, so | ||||
|     # we import it as late as possible. | ||||
|     import supybot.utils.httpserver as httpserver | ||||
| 
 | ||||
|     if options.profile: | ||||
|         import profile | ||||
|         world.profiling = True | ||||
|  | ||||
							
								
								
									
										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 | ||||
|  | ||||
							
								
								
									
										197
									
								
								src/utils/httpserver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								src/utils/httpserver.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,197 @@ | ||||
| ### | ||||
| # 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 callbacks: | ||||
|             raise KeyError('This subdir is already hooked.') | ||||
|         else: | ||||
|             callbacks[subdir] = callback | ||||
|     def unhook(self, subdir): | ||||
|         callback = 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: | ||||
|     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): | ||||
|     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): | ||||
|     name = "index" | ||||
|     defaultResponse = "Request not handled.""" | ||||
|     template = """ | ||||
|     <html> | ||||
|      <head> | ||||
|       <title>Supybot Web server index</title> | ||||
|      </head> | ||||
|      <body> | ||||
|       Here is a list of the plugins that have a Web interface: | ||||
|       %s | ||||
|      </body> | ||||
|     </html>""" | ||||
|     def doGet(self, handler, path): | ||||
|         plugins = [x for x in handler.server.callbacks.items()] | ||||
|         if plugins == []: | ||||
|             response = 'No plugins available.' | ||||
|         else: | ||||
|             response = '<ul><li>%s</li></ul>' % '</li><li>'.join( | ||||
|                     ['<a href="/%s">%s</a>' % x in 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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Valentin Lorentz
						Valentin Lorentz