aurweb.spawn: Integrate FastAPI and nginx

aurweb.spawn used to launch only PHP’s built-in server. Now it spawns a
dummy FastAPI application too. Since both stacks spawn their own HTTP
server, aurweb.spawn also spawns nginx as a reverse proxy to mount them
under the same base URL, defined by aur_location in the configuration.

Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
This commit is contained in:
Frédéric Mangano-Tarumi 2020-06-01 18:49:37 +02:00 committed by Lukas Fleischer
parent d4abe0b72d
commit 5be07a8a9e
5 changed files with 86 additions and 14 deletions

View file

@ -11,7 +11,7 @@ before_script:
base-devel git gpgme protobuf pyalpm python-mysql-connector base-devel git gpgme protobuf pyalpm python-mysql-connector
python-pygit2 python-srcinfo python-bleach python-markdown python-pygit2 python-srcinfo python-bleach python-markdown
python-sqlalchemy python-alembic python-pytest python-werkzeug python-sqlalchemy python-alembic python-pytest python-werkzeug
python-pytest-tap python-pytest-tap python-fastapi uvicorn nginx
test: test:
script: script:

View file

@ -12,7 +12,8 @@ INSTALL.
2) Install the necessary packages: 2) Install the necessary packages:
# pacman -S --needed php php-sqlite sqlite words fortune-mod \ # pacman -S --needed php php-sqlite sqlite words fortune-mod \
python python-sqlalchemy python-alembic python python-sqlalchemy python-alembic \
python-fastapi uvicorn nginx
Ensure to enable the pdo_sqlite extension in php.ini. Ensure to enable the pdo_sqlite extension in php.ini.

8
aurweb/asgi.py Normal file
View file

@ -0,0 +1,8 @@
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello/")
async def hello():
return {"message": "Hello from FastAPI!"}

View file

@ -10,8 +10,10 @@ configuration anyway.
import atexit import atexit
import argparse import argparse
import os
import subprocess import subprocess
import sys import sys
import tempfile
import time import time
import urllib import urllib
@ -20,6 +22,7 @@ import aurweb.schema
children = [] children = []
temporary_dir = None
verbosity = 0 verbosity = 0
@ -35,10 +38,42 @@ class ProcessExceptions(Exception):
super().__init__("\n- ".join(messages)) super().__init__("\n- ".join(messages))
def generate_nginx_config():
"""
Generate an nginx configuration based on aurweb's configuration.
The file is generated under `temporary_dir`.
Returns the path to the created configuration file.
"""
aur_location = aurweb.config.get("options", "aur_location")
aur_location_parts = urllib.parse.urlsplit(aur_location)
config_path = os.path.join(temporary_dir, "nginx.conf")
config = open(config_path, "w")
# We double nginx's braces because they conflict with Python's f-strings.
config.write(f"""
events {{}}
daemon off;
error_log /dev/stderr info;
pid {os.path.join(temporary_dir, "nginx.pid")};
http {{
access_log /dev/stdout;
server {{
listen {aur_location_parts.netloc};
location / {{
proxy_pass http://{aurweb.config.get("php", "bind_address")};
}}
location /hello {{
proxy_pass http://{aurweb.config.get("fastapi", "bind_address")};
}}
}}
}}
""")
return config_path
def spawn_child(args): def spawn_child(args):
"""Open a subprocess and add it to the global state.""" """Open a subprocess and add it to the global state."""
if verbosity >= 1: if verbosity >= 1:
print(f"Spawning {args}", file=sys.stderr) print(f":: Spawning {args}", file=sys.stderr)
children.append(subprocess.Popen(args)) children.append(subprocess.Popen(args))
@ -52,10 +87,29 @@ def start():
if children: if children:
return return
atexit.register(stop) atexit.register(stop)
aur_location = aurweb.config.get("options", "aur_location")
aur_location_parts = urllib.parse.urlsplit(aur_location) print("{ruler}\n"
htmldir = aurweb.config.get("options", "htmldir") "Spawing PHP and FastAPI, then nginx as a reverse proxy.\n"
spawn_child(["php", "-S", aur_location_parts.netloc, "-t", htmldir]) "Check out {aur_location}\n"
"Hit ^C to terminate everything.\n"
"{ruler}"
.format(ruler=("-" * os.get_terminal_size().columns),
aur_location=aurweb.config.get('options', 'aur_location')))
# PHP
php_address = aurweb.config.get("php", "bind_address")
htmldir = aurweb.config.get("php", "htmldir")
spawn_child(["php", "-S", php_address, "-t", htmldir])
# FastAPI
host, port = aurweb.config.get("fastapi", "bind_address").rsplit(":", 1)
spawn_child(["python", "-m", "uvicorn",
"--host", host,
"--port", port,
"aurweb.asgi:app"])
# nginx
spawn_child(["nginx", "-p", temporary_dir, "-c", generate_nginx_config()])
def stop(): def stop():
@ -73,7 +127,7 @@ def stop():
try: try:
p.terminate() p.terminate()
if verbosity >= 1: if verbosity >= 1:
print(f"Sent SIGTERM to {p.args}", file=sys.stderr) print(f":: Sent SIGTERM to {p.args}", file=sys.stderr)
except Exception as e: except Exception as e:
exceptions.append(e) exceptions.append(e)
for p in children: for p in children:
@ -99,9 +153,11 @@ if __name__ == '__main__':
help='increase verbosity') help='increase verbosity')
args = parser.parse_args() args = parser.parse_args()
verbosity = args.verbose verbosity = args.verbose
start() with tempfile.TemporaryDirectory(prefix="aurweb-") as tmpdirname:
try: temporary_dir = tmpdirname
while True: start()
time.sleep(60) try:
except KeyboardInterrupt: while True:
stop() time.sleep(60)
except KeyboardInterrupt:
stop()

View file

@ -41,9 +41,16 @@ cache = none
cache_pkginfo_ttl = 86400 cache_pkginfo_ttl = 86400
memcache_servers = 127.0.0.1:11211 memcache_servers = 127.0.0.1:11211
[php]
; Address PHP should bind when spawned in development mode by aurweb.spawn.
bind_address = 127.0.0.1:8081
; Directory containing aurweb's PHP code, required by aurweb.spawn. ; Directory containing aurweb's PHP code, required by aurweb.spawn.
;htmldir = /path/to/web/html ;htmldir = /path/to/web/html
[fastapi]
; Address uvicorn should bind when spawned in development mode by aurweb.spawn.
bind_address = 127.0.0.1:8082
[ratelimit] [ratelimit]
request_limit = 4000 request_limit = 4000
window_length = 86400 window_length = 86400