From ffb2e8488f703dd145ddcfd5d8ec39d5aba91ad6 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Mon, 31 Oct 2022 22:13:34 +0100 Subject: [PATCH] httpserver: Add support for WSGI apps For example, with Flask: ``` from supybot import utils, plugins, ircutils, callbacks, httpserver from supybot.commands import * from supybot.i18n import PluginInternationalization _ = PluginInternationalization('TestWsgi') from flask import Flask, request, render_template app = Flask(__name__) @app.route("/") def hello_world(): return "

Hello, World!

" @app.route('/login/', methods=['POST', 'GET']) def login(): error = None if request.method == 'POST': if request.form['username'] == 'root' \ and request.form['password'] == 'admin': return 'Hello root' else: return 'Error: Invalid username/password' # the code below is executed if the request method # was GET or the credentials were invalid return '''
''' class TestWsgi(callbacks.Plugin): """Test Flask""" def __init__(self, irc): self.__parent = super(TestWsgi, self) callbacks.Plugin.__init__(self, irc) httpserver.hook('testwsgi', app) def die(self): self.__parent.die() httpserver.unhook('testwsgi') Class = TestWsgi ``` --- src/httpserver.py | 61 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/src/httpserver.py b/src/httpserver.py index 3b150fc11..0fe1f02f4 100644 --- a/src/httpserver.py +++ b/src/httpserver.py @@ -34,7 +34,10 @@ An embedded and centralized HTTP server for Supybot's plugins. import os import cgi import socket +import sys from threading import Thread +from wsgiref.handlers import BaseHandler, SimpleHandler +from wsgiref.simple_server import WSGIRequestHandler import supybot.log as log import supybot.conf as conf @@ -181,6 +184,22 @@ class SupyHTTPRequestHandler(BaseHTTPRequestHandler): except KeyError: callback = Supy404() + if not isinstance(callback, SupyHTTPServerCallback): + # WSGI-based callback + environ = WSGIRequestHandler.get_environ(self) + environ.update({ + 'SERVER_NAME': '0.0.0.0', # TODO + 'SERVER_PORT': '80', # TODO + 'PATH_INFO': '/' + environ['PATH_INFO'].split('/', 2)[2] + }) + SimpleHandler( + stdin=self.rfile, stdout=self.wfile, stderr=sys.stderr, environ=environ, + multithread=False + ).run(callback) + return + + # BaseHTTPRequestHandler-based callback + # Some shortcuts for name in ('send_response', 'send_header', 'end_headers', 'rfile', 'wfile', 'headers'): @@ -189,6 +208,22 @@ class SupyHTTPRequestHandler(BaseHTTPRequestHandler): path = self.path if not callback.fullpath: path = '/' + path.split('/', 2)[-1] + + if callback == 'doPost': + if 'Content-Type' not in self.headers: + self.headers['Content-Type'] = 'application/x-www-form-urlencoded' + if self.headers['Content-Type'] == 'application/x-www-form-urlencoded': + form = cgi.FieldStorage( + fp=self.rfile, + headers=self.headers, + environ={'REQUEST_METHOD':'POST', + 'CONTENT_TYPE':self.headers['Content-Type'], + }) + else: + content_length = int(self.headers.get('Content-Length', '0')) + form = self.rfile.read(content_length) + kwargs['form'] = form + getattr(callback, callbackMethod)(self, path, *args, **kwargs) @@ -196,19 +231,7 @@ class SupyHTTPRequestHandler(BaseHTTPRequestHandler): self.do_X('doGet') def do_POST(self): - if 'Content-Type' not in self.headers: - self.headers['Content-Type'] = 'application/x-www-form-urlencoded' - if self.headers['Content-Type'] == 'application/x-www-form-urlencoded': - form = cgi.FieldStorage( - fp=self.rfile, - headers=self.headers, - environ={'REQUEST_METHOD':'POST', - 'CONTENT_TYPE':self.headers['Content-Type'], - }) - else: - content_length = int(self.headers.get('Content-Length', '0')) - form = self.rfile.read(content_length) - self.do_X('doPost', form=form) + self.do_X('doPost') def do_HEAD(self): self.do_X('doHead') @@ -312,7 +335,7 @@ class SupyIndex(SupyHTTPServerCallback): plugins = [ (name, cb) for (name, cb) in handler.server.callbacks.items() - if cb.public] + if getattr(cb, 'public', True)] if plugins == []: plugins = _('No plugins available.') else: @@ -417,6 +440,9 @@ class RealSupyHTTPServer(HTTPServer): timeout = 0.5 running = False + + base_environ = {} + def __init__(self, address, protocol, callback): self.protocol = protocol if protocol == 4: @@ -441,10 +467,11 @@ class RealSupyHTTPServer(HTTPServer): 'reloaded the plugin and it didn\'t properly unhook. ' 'Forced unhook.') % subdir) self.callbacks[subdir] = callback - callback.doHook(self, subdir) + if hasattr(callback, 'doHook'): + callback.doHook(self, subdir) def unhook(self, subdir): callback = self.callbacks.pop(subdir, None) - if callback: + if callback and hasattr(callback, 'doUnhook'): callback.doUnhook(self) return callback @@ -452,6 +479,8 @@ class RealSupyHTTPServer(HTTPServer): return 'server at %s %i' % self.server_address[0:2] class TestSupyHTTPServer(RealSupyHTTPServer): + base_environ = {} + def __init__(self, *args, **kwargs): self.callbacks = {} self.server_address = ("0.0.0.0", 0)