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 "<p>Hello, World!</p>"

@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 '''
        <form method="POST" action=".">
            <label for="username">Username:
                <input name="username" id="username" />
            </label>
            <label for="password">Password:
                <input name="password" id="password" type="password" />
            </label>
            <input type="Submit" />
        </form>
    '''

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
```
This commit is contained in:
Valentin Lorentz 2022-10-31 22:13:34 +01:00
parent 4da1291876
commit ffb2e8488f

View File

@ -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)