[PATCH] Proof-of-concept of a dual PHP–Python stack

Frédéric Mangano-Tarumi fmang at mg0.fr
Sat Mar 7 21:01:52 UTC 2020

Here’s a demonstration of a Python web stack running next to the current
PHP stack, such that it’s invisible to the client.

This approach aims at providing a way to migrate the PHP code base to
Python, one endpoint after another, with as little glue as possible to
have the two backends collaborate. Since they are both mounted on the
web root, Python could implement, say, /packages/ and leave the rest to
PHP. As soon as PHP yields it by returning 404 on this URL, Python will
take over automatically.

To run it, you need python-flask and nginx. Then, you need to start PHP,
Flask, and nginx, in whatever order:

    $ cd path/to/aurweb
    $ AUR_CONFIG="$PWD/conf/config" php -S -t web/html
    $ FLASK_APP=aurweb.wsgi flask run
    $ nginx -p . -c conf/nginx.conf

You may then open http://localhost:8081/ and http://localhost:8081/hello
to check that the former URL goes to PHP and the latter to Flask.

The key concept is nginx’s proxy_next_upstream feature. We set Flask as
a fallback backend, and tell nginx to use the fallback only on 404 from
PHP. See http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream

The main limitation of this approach is that PHP and Python need to use
the same gateway protocol, probably FastCGI or HTTP.

A minor caveat with this system is that the body of the 404 returned by
PHP is lost, though it could contain useful information like “Package X
doesn’t exist”, rather than a generic “Page not found”. Luckily, all the
404 cases are handled by 404.php, so we could port its logic to Flask
and preserve the current behavior.
 aurweb/wsgi.py  | 15 +++++++++++++++
 conf/nginx.conf | 23 +++++++++++++++++++++++
 2 files changed, 38 insertions(+)
 create mode 100644 aurweb/wsgi.py
 create mode 100644 conf/nginx.conf

diff --git a/aurweb/wsgi.py b/aurweb/wsgi.py
new file mode 100644
index 00000000..fd6b67d3
--- /dev/null
+++ b/aurweb/wsgi.py
@@ -0,0 +1,15 @@
+from flask import Flask, request
+def create_app():
+    app = Flask(__name__)
+    @app.route('/hello', methods=['GET', 'POST'])
+    def hello():
+        return (
+            f"{request.method} {request.url}\n"
+            f"{request.headers}"
+            f"{request.get_data(as_text=True)}\n"
+        ), {'Content-Type': 'text/plain'}
+    return app
diff --git a/conf/nginx.conf b/conf/nginx.conf
new file mode 100644
index 00000000..8e6e4edb
--- /dev/null
+++ b/conf/nginx.conf
@@ -0,0 +1,23 @@
+events {
+daemon off;
+error_log /dev/stderr info;
+pid nginx.pid;
+http {
+	access_log /dev/stdout;
+	upstream aurweb {
+		server [::1]:8080 max_fails=0;
+		server backup max_fails=0;
+	}
+	server {
+		listen 8081;
+		location / {
+			proxy_pass http://aurweb;
+			proxy_next_upstream http_404 non_idempotent;
+		}
+	}

More information about the aur-dev mailing list