Heute zeige ich euch, wie man ohne den Seuchenhaufen ( ) PHP Webservices betreiben kann, und dabei kein CGI schreiben muss.
Der Stack, den ich euch demonstrieren möchte, besteht aus nginx (wobei das eher Geschmackssache ist, lighttpd würde es auch tun), gunicorn und einem Webservice in python.
nginx kümmert sich um den http-Teil und leitet die Anfragen an einen unix-Socket weiter, gunicorn stellt den Socket und verteilt die Anfragen auf worker. Um den Vergleich mit LAMP und DAMPF vom Tag 1 ein bisschen realistischer zu gestalten, hab ich noch ne NoSQL-DB integriert - das beabsichtigte Konfliktpotential hab ich dabei aber versehentlich eliminiert
Üblicherweise setze ich da auf leichtgewichtige Lösungen auf Dateibasis wie tinydb oder sqlite, die aber nicht mit mehreren Workern funktionieren würden, und mongodb war zu meiner Enttäuschung nicht in den Repos...
Genug geschwafelt, hier die Schritte zum Setup:
1. nginx-site:
Code: Alles auswählen
$ cat /etc/nginx/sites-enabled/down-with-php
server {
listen 8008;
server_name magni;
location / {
include proxy_params;
proxy_pass http://unix:/home/me/Projekte/down-with-php/app.sock;
}
}
Code: Alles auswählen
$ systemctl cat down-with-php.service
# /etc/systemd/system/down-with-php.service
[Unit]
Description=Gunicorn instance to serve my project
After=network.target
[Service]
User=thorsten
Group=www-data
WorkingDirectory=/home/me/Projekte/down-with-php
ExecStart=/home/me/.virtualenvs/down-with-php/bin/gunicorn --workers 3 --bind unix:app.sock -m 007 main:app
[Install]
WantedBy=multi-user.target
3. Der App-Code ist hier zu finden:
main.py: 42024
templates/index.html: 42025
templates/view_paste.html: 42026
Und damit man die Performance irgendwie greifbar machen kann, ein Performance-Test in/für python3-locust:
42027
Die Grenzen hab ich damit nicht ausgelotet, das dürft ihr tun Sehr viel Spielraum ist da bei gunicorn, wobei der Flaschenhals aber eher bei der App zu suchen wäre. Für mich ist das aber selten wichtig. Das war auch mein erster Ausflug mit Redis als NoSQL-Ersatz (ohne konfigurierte Persistenz auch eher witzlos ), viel anders als mit der mongodb wars aber nicht (auf dem Niveau).
Für die Interessierten, wie funktioniert das unter der Haube?
- nginx (oder lighttpd) übernimmt die Annahme von http-Verbindungen, bei nginx spricht man beim Ziel von "upstream". Ist das nicht da oder kaputt, sieht man HTTP/502 oder 503 - Gateway kaputt. In der obigen Konfiguration ist das ein unix-Socket, man kann aber auch eine TCP-Verbindung verwenden (wenn das Ziel ebenfalls HTTP spricht). Braucht man denn dann noch einen Webserver? Naja, streng genommen nicht. Es gibt nur viele Dinge, die nginx und co besser machen als die wsgi-Server. Außerdem kann man damit auch gleich sowas wie vhosts aufsetzen
- gunicorn: alternativ kann man auch uwsgi verwenden, die Tools tun ähnliches, die Kernfunktionalität ist gleich. Ist wie vim vs emacs (von außen betrachtet). gunicorn bekommt erklärt, was es zu starten hat und wie viele Resourcen es dafür verwenden soll - also wie viele "Worker" gestartet werden sollen, ob die multithreaded sein sollen... Das ist aber nicht zu verwechseln mit php-fpm, was meines Wissens nach zustandslose Worker in einem Pool sind. Hat die Anwendung irgendwie state, hat jeder der Worker diesen. Darum konnte ich da auch nicht sowas wie ne sqlite-Verbindung verwenden (wobei PHP mit parallelen Zugriffen da auch früher oder später Konflikte produziert hätte, das hat man eben mit Parallelität - es hätte mit python vermutlich nur bereits sehr viel früher geknallt).
- die eigentliche Anwendung: muss ein WSGI-Interface implementieren, flask tut das (bottle würds auch tun). Der python-Code der main.py wird beim import ausgeführt, das main:app (in gunicorns Kommandozeile) zeigt auf die Variable app (vom Typ flask.Flask) und da findet gunicorn alles zum Starten der Anwendung. Für Entwicklungszwecke startet man in der Regel direkt den eingebauten httpd in python via flask, siehe main.py am Ende.
Und natürlich hätte ich auch mysql anbinden können - entweder roh und ungeschminkt mit python-mysql oder mit dem ORM-Layer von sqlalchemy - aber ich mag die notwendige Komplexität von RBDMS nicht, wenn ich sie nicht brauche.
Mein persönliches Fazit zu python-Webservices: man kann damit unheimlich schnell sehr akzeptable Ergebnisse erzielen, und die Sprache ist vielleicht nicht so sicher wie rust, aber php in dem Aspekt weit überlegen
Ein paar Links für weiterführende Informationen:
Doku zu flask: https://flask.palletsprojects.com/en/2.3.x/
Alternative bottle: https://bottlepy.org/
gunicorn: https://gunicorn.org/
oder uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/
Was pex wäre: https://pex.readthedocs.io