cgi-Modul bald auch tot

Django, Flask, Bottle, WSGI, CGI…
Antworten
Üpsilon
User
Beiträge: 225
Registriert: Samstag 15. September 2012, 19:23

Liebe Forengemeinde,

ich habe einen Server mit einer Webseite und ein paar einfachen CGI-Skripten. Dafür benutze ich nginx, fcgiwrap, meistens die POST-Methode und Python mit dem cgi-Modul. Bei einem dieser Skripte wird eine hochgeladene Datei verarbeitet, bei den anderen einfach nur Formular-Eingaben.

In meiner nginx-Konfigurationsdatei gibt es also einen Abschnitt, der so aussieht:

Code: Alles auswählen

location /cgiskript.py {
      include fastcgi_params;
      fastcgi_param SCRIPT_FILENAME /pfad/cgiskript.py;
      fastcgi_param QUERY_STRING $query_string;
      fastcgi_pass unix:/run/fcgiwrap.socket;
    }
Ich habe gesehen, dass diese Skripte mir seit dem Update auf Python 3.11 DeprecationWarnings in die Log-Datei schreiben. Deshalb bin ich auf https://peps.python.org/pep-0594/ gestoßen:
This PEP proposed a list of standard library modules to be removed from the standard library.
...
Back in the early days of Python, the interpreter came with a large set of useful modules. This was often referred to as “batteries included” philosophy and was one of the cornerstones to Python’s success story. Users didn’t have to figure out how to download and install separate packages in order to write a simple web server or parse email.

Times have changed. With the introduction of PyPI (née Cheeseshop), setuptools, and later pip, it became simple and straightforward to download and install packages. Nowadays Python has a rich and vibrant ecosystem of third-party packages. It’s pretty much standard to either install packages from PyPI or use one of the many Python or Linux distributions.
Mit anderen Worten, das Gerümpel fliegt jetzt aus der Standardbibliothek, wer's braucht soll sich geeignete Libraries per PIP installieren. Was ist der geeignete Ersatz für das cgi-Modul? Das steht in einer Tabelle in jenem PEP: es gibt keinen Ersatz.
Weiter unten im PEP steht dann:
cgi
The cgi module is a support module for Common Gateway Interface (CGI) scripts. CGI is deemed as inefficient because every incoming request is handled in a new process. PEP 206 considers the module as:
“[…] designed poorly and are now near-impossible to fix (cgi) […]”
Ja. Da meine Skripte aber nur sehr selten (circa einmal im Monat) aufgerufen werden, ist mir das mit den neuen Prozessen völlig egal. Nicht jeder entwickelt Web-Apps, die jeden Tag von hunderttausend Menschen benutzt werden.

Diese Python-Version werde ich noch mindestens 2 Jahre weiter benutzen, so lange muss ich noch nichts ändern. Nun ist die Frage: was soll ich statt Python und dem cgi-Modul benutzen?
Auf Perl/Ruby/Shellskripte/PHP umsteigen ist wohl keine gute Idee -- erstens möchte ich keine neue Sprache einführen, und zweitens nutzen manche der Skripte auch Bibliotheken, die es so nur für Python gibt. Und drittens: was ist, wenn auch diese Sprachen beschließen, ihre CGI-Funktionalität zu deprecaten?
Zur allergrößten Not würde ich auf Python mit Flask umsteigen, aber grundsätzlich gefällt mir die Vorgehensweise mit dem cgi-Modul.

Um die Benutzereingaben zu bekommen, verwende ich ein cgi.FieldStorage(). Dazu steht in dem PEP:
FieldStorage/MiniFieldStorage has no direct replacement, but can typically be replaced by using multipart (for POST and PUT requests) or urllib.parse.parse_qsl (for GET and HEAD requests)
Die multipart-Bibliothek https://pypi.org/project/multipart/ ist auf dem PyPI zu finden, sie wurde offenbar seit über 2 Jahren nicht aktualisiert und ist auch nicht dokumentiert. Vielleicht würde ich sie trotzdem verwenden, aber dass man verschiedene Vorgehensweisen für POST und GET braucht finde ich auch nicht toll.
Außerdem geht es bei multipart und urllib.parse.parse_qsl offenbar nur ums Parsen. Wie bekomme ich die Formulareingaben überhaupt in mein Programm rein? Landen die irgendwie auf stdin?

Eigentlich ist meine Frage nur: wenn es das cgi-Modul nicht mehr gibt, was ist dann die einfachste verbliebene Möglichkeit, serverseitig mit Python Eingaben aus Webformularen zu verarbeiten?

Nette Grüße -- ich wünsche euch eine schöne Woche.
Sirius3
User
Beiträge: 18051
Registriert: Sonntag 21. Oktober 2012, 17:20

CGI ist tot, und das schon seit 20 Jahren. Dass die Standardbibliothek solche Altlasten entsorgt, ist nur konsequent.
Der Standardweg, wie man Webseiten an Python anbindet ist WSGI: https://www.fullstackpython.com/wsgi-servers.html
nezzcarth
User
Beiträge: 1686
Registriert: Samstag 16. April 2011, 12:47

Zur allergrößten Not würde ich auf Python mit Flask umsteigen, aber grundsätzlich gefällt mir die Vorgehensweise mit dem cgi-Modul.
Worauf begründet sich denn dein Widerstand gegenüber Flask? Flask oder Bottle wären nun mal das, wie man heute eine minimalistische Webanwendung in Python bauen würde. CGI ist Technik aus den Frühzeiten des Webs. In den 90ern war das mal okay, aber seit allerspätestens Mitte der 2000er ist das eben nichts mehr, was man verwenden sollte. Gerade bei Formularen kann man ja auch genug falsch machen, was einem ein anständiges Webframework bereits abnimmt. Die "Einfacheit" von CGI ist aus meiner Sicht illusorisch. Das Deployment moderner WSGI Anwendungen mag ein kleines bisschen komplizierter sein, aber das ist es wirklich wert. Es geht bei der Kritik an CGI außerdem nicht nur um die Prozesse.
narpfel
User
Beiträge: 658
Registriert: Freitag 20. Oktober 2017, 16:10

„Bald“ ist ein bisschen... überdramatisch? 3.12 wird bis Oktober 2028 unterstützt, und mit einer LTS-Distribution hast du nochmal länger Support. Im Zweifelsfall bis mindestens 2034 (3.13 wird nicht in Ubuntu 24.04 sein).

WSGI ist 20 Jahre alt; bei CGI ist seit mindestens 23 Jahren bekannt, dass es eigentlich keine gute Idee war. Wo ist also das Problem?

Und der geeignete Ersatz ist Flask (oder eins der vielen anderen WSGI- oder ASGI-Frameworks). Auch schon über 13 Jahre alt.

Merke: Jahre, nicht Monate. Dann würde ich das ja verstehen, aber bei Zeitspannen, die in Jahrzehnten gemessen werden...
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Üpsilon: Also grundsätzlich verstehe ich die Aufregung und so Sachen wie „wenn auch diese Sprachen beschließen, ihre CGI-Funktionalität zu deprecaten“ nicht so ganz, denn das schöne an CGI ist ja das man gegen diese Schnittstelle im Grunde mit jeder Programmiersprache programmieren kann, die Text von stdin lesen und auf stdout schreiben kann. Ich habe selbst schon mal was in klassischem BASIC, also mit Zeilennummern und GOTO, kompiliert mit FreeBasic geschrieben und schon Bash-Skripte gewartet die per CGI was ins Web ausgegeben haben. Von der Programmiersprache selbst oder der Standardbibliothek *braucht* man *keine* spezielle Unterstützung für CGI.

Das CGI-Modul ist in Python geschrieben. Das einfachste wird sein das aus der Standardbibliothek zu kopieren. Irgendwer wird das, wenn es akut wird, sicher dann auch als Package im PyPI hochladen. Und dort wird sich das Paket dann Jahre bis Jahrzehntelang nicht ändern. Weil sich CGI nicht ändern wird, so wie es das seit Jahrzehnten vorher nicht geändert hat. Das `cgi`-Modul musste zum `str` → `unicode` Wechsel mal angefasst werden, aber ansonsten hat sich da doch nicht wirklich getan, seit es das Modul gibt. Weil sich die CGI-Schnittstelle nicht geändert hat. Darum spricht auch 2 Jahre keine Änderung am `multipart`-Package nicht gegen dieses Package. Im Gegenteil: dauernde Aktivität dort könnte man eher so deuten, dass da lauter Fehler drin waren, denn ansonsten muss man das ja gar nicht anfassen und ändern.

Wo man sich Sorgen machen müsste ist wenn Webserver diese Schnittstelle als veraltet markieren und rauswerfen.

Ich persönlich hätte schon längst Bottle (das auch vom `cgi`-Wegfall betroffen sein wird!) oder Flask verwendet. Denn selbst wenn der Server nur CGI bietet, ist WSGI über so ein Mikrowebrahmenwerk IMHO einfacher zu verwenden, vor allem weil man nicht zwischen verschiedenen Anwendungen unterscheiden muss, und eine kleine Anwendung auch wachsen kann, ohne das man dafür das Rahmenwerk wechseln muss. Und WSGI-Anwendungen kann man auch über CGI anbinden.

Absolut minimal wäre `wsgiref` in der Standardbibliothek und in `wsgiref.handler` gibt es einen Handler um eine WSGI-Anwendung per CGI anzubinden. Nächste Stufe wäre da das externe `werkzeug` drauf zu setzen. Da hat man dann eine Low-Level-API für die Anfragedaten und die Antwort, oder High-Level `Request`- und `Response`-Objekte. Ab da muss man dann aufpassen das man zu Werkzeug nicht selbst so viel dazu programmiert, dass man am Ende nicht gleich Flask hätte nehmen sollen. Denn Flask setzt auf Werkzeug auf.

Falls Du nicht von CGI weg möchtest was *Deine* Seite der Schnittstelle angeht, würde ich an Deiner Stelle ein Datum innerhalb der nächsten 2 Jahre festlegen an dem Du Dich darum kümmerst. Also schauen ob schon jemand `cgi` als Modul/Package in PyPI anbietet, oder wie schwierig das ist, das selbst mit in das eigene Programm als Modul aufzunehmen.

Oder Du schaust Dir WSGI an und wie man das als CGI ausführen lässt, und wie viel Arbeit das machen würde Deine Quelltexte daran anzupassen.

Ansonsten wäre Werkzeug auch einen Blick wert wenn Du bei CGI bleiben willst, denn auch WSGI-Anwendungen müssen mit den Grundlagen von HTTP klar kommen, also zum Beispiel Multipart/Form-Encoded-Daten verarbeiten, folglich hat Werkzeug da auch Funktionen für.

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Benutzeravatar
kbr
User
Beiträge: 1494
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Üpsilon: oder Du schaust Dir mal https://cherrypy.dev/ an, das bringt bereits einen integrierten Webserver mit und Du kannst die Requests von z.B. nginx oder apache einfach durchreichen.
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Nachteil ist, dass bei dem Webserver auch Cherrypy dabei ist. 😉

Den Webserver (`cheroot`) bekommt man auch ohne Cherrypy, aber was ist dann daran der Vorteil, beispielsweise gegenüber Gunicorn?

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Benutzeravatar
kbr
User
Beiträge: 1494
Registriert: Mittwoch 15. Oktober 2008, 09:27

Es ist eher umgekehrt: Der Vorteil von CherryPy besteht darin, dass ein Webserver bereits dabei ist ;-)
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kbr: Wie gesagt, der ist eigentlich nicht dabei. Der wird halt als Abhängigkeit dazu installiert. Also kann man den auch ohne Cherrypy installieren. Und hat dann halt diesen Murks nicht, der einem beibringen will lauter Klassen zu schreiben die keine Klassen sind, mit Methoden die eigentlich nur Funktionen sind. Und dann trauen die sich auch noch das „objektorieniert“ zu nennen. Ist das Mut oder Unwissen‽

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Benutzeravatar
kbr
User
Beiträge: 1494
Registriert: Mittwoch 15. Oktober 2008, 09:27

@__blackjack__: CherryPy kommt standardmäßig mit integriertem Webserver (auch wenn dieser später als stand-alone Anwendung separiert und nun als Abhängigkeit installiert wird) und das kann manchen das Leben erleichtern – auch wenn Dir CherryPy offenbar nicht zu gefallen scheint.
Benutzeravatar
noisefloor
User
Beiträge: 3939
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

@Üpsilon: ist das "dein" Server, d.h. kannst du darauf Software (Python Module) selber installieren? Dann ist der Umstiege auf WSGI (was seit > 10 Jahren Standard bei Python ist) ja supereinfach. Und mit einem WSGI ebframework wie Bottle oder Flask oder Django oder... ist alles einfacher als mit CGI.

Gruß, noisefloor
Üpsilon
User
Beiträge: 225
Registriert: Samstag 15. September 2012, 19:23

Danke für eure Antworten! Also ist Flask oder Bottle offenbar das, was die meisten Menschen benutzen würden.

Habe jetzt einen Teil meiner Skripte auf das wsgiref-Modul umgestellt. Das hat den Vorteil, dass es in der Standardbibliothek ist (auch wenn es "mein" Server ist und ich auch andere Libraries installieren könnte). Und man kann die Skripte damit auch per CGI anbinden, wenn man wsgiref.handlers.CGIHandler().run(app) benutzt (wie Blackjack angedeutet hat) -- damit kann ich auch die Nginx-Konfiguration die ich vorher hatte beibehalten. :D
Das ist zwar nicht die "offizielle" Vorgehensweise, denn scheinbar soll man das WSGI-Programm eigentlich die ganze Zeit auf einem anderen Port lauschen lassen und die relevanten Requests dann irgendwie reverse-proxy-mäßig durchreichen, so wie es auf der von Sirius3 verlinkten Webseite steht. Das ist ja eine verbreitete Vorgehensweise, aber dann müsste ich diese Programme die nur selten gebraucht werden immer laufen lassen und das fände ich blöd. Könnte ich aber so machen, wenn es nötig wäre oder wenn die CGI-Funktionalität aus Nginx rausfliegt.

Mir gefällt auch, dass man mittels wsgiref.simple_server die Skripte lokal testen kann ohne gleich Nginx o.ä. anzuschmeißen. Das ging beim cgi-Modul so viel ich weiß nicht.

Um die Eingaben zu parsen, benutze ich jetzt urllib.parse.parse_qs sowohl für GET als auch für POST.

Ich hab noch gesehen, dass es als letzten Notnagel schon den Fork https://pypi.org/project/legacy-cgi/ gibt. Aber den werde ich nicht brauchen.
Zuletzt geändert von Üpsilon am Montag 7. August 2023, 14:54, insgesamt 1-mal geändert.
PS: Die angebotene Summe ist beachtlich.
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kbr: „Nicht gefallen“ klingt so als wenn das Geschmackssache wäre. Blick ins Tutorial: lauter Klassen die (explizit) von `object` erben, keine `__init__()` und keinen Zustand haben, sondern nur ”Methoden” die ein `self` übergeben bekommen was aber nicht verwendet wird. Das ist objektiv falsch. Weil das keine sinnvollen Klassen und keine echten Methoden sind. Klassen werden hier als Container für Funktionen missbraucht. Dafür sind Module vorgesehen. Und das funktioniert in anderen Rahmenwerken ja auch prima mit Modulen. Die können sich also auch nicht damit rausreden, das würde ohne Klassen nicht funktionieren.

@Üpsilon: Es gibt in der Standardbibliothek das `http.server`-Modul, wo man sich entweder im eigenen Programm einen kleinen Webserver starten kann, oder das man auch direkt ausführen kann um einen kleinen Webserver zu starten. Mit dem `CGIHTTPRequestHandler` oder der ``--cgi``-Option kann der auch CGI-Skripte ausführen.

https://docs.python.org/3.8/library/http.server.html

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Benutzeravatar
kbr
User
Beiträge: 1494
Registriert: Mittwoch 15. Oktober 2008, 09:27

@__blackjack__: In der CherryPy-Dokumentation finde ich sehr wohl Beispiele mit `__init__` und Verwendung von `self`. "Nicht gefallen" finde ich gar nicht so schlecht in dem Zusammenhang. Mir gefällt zum Beispiel Flask nicht, was sicher als Geschmacksache gelten darf. Jedenfalls werde ich hier weder CherryPy noch Flask kritisieren oder verteidigen – ich habe weder das eine noch das andere geschrieben, anderweitig dazu beigetragen oder bin Maintainer. Und falls doch, wäre so manche Designentscheidung vermutlich vor einiger Zeit getroffen worden und würden heute womöglich anders betrachtet.
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kbr: Schön das Du die Beispiele findest. Und wie genau macht dass die ganzen Klassen als Container für Funktionen Beispiele wieder weg? Die sind da ja auch, und das scheint die normale Verwendung zu sein. Selbst wenn die diese Entscheidung heute anders treffen würden, ist das ja kein Grund so etwas als Rahmenwerk zu empfehlen.

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Benutzeravatar
noisefloor
User
Beiträge: 3939
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Üpsilon hat geschrieben: Montag 7. August 2023, 14:44 Danke für eure Antworten! Also ist Flask oder Bottle offenbar das, was die meisten Menschen benutzen würden.
.
Na ja, im Pythonumfeld sind Flask und Django wohl die (mit Abstand) populärsten Webframeworks. Bottle ist für kleine Sachen ein gute Wahl. Wenn du bis jetzt mit CGI ausgekommen bist und jetzt mit wsgiref auskommen würdest, dann brauchst du dir Django aber eigentlich nicht anschauen, weil überqualifiziert für dich.
Habe jetzt einen Teil meiner Skripte auf das wsgiref-Modul umgestellt.
.
Ich sag mal so: kann man machen, macht in der Regel aber keine, weil zu low-level. Es sei denn, man möchte ein eigenes Webframework von Grund auf entwickelt oder man kann partout nichts zur Python Standardinstallation dazu installieren oder man hat zu viel Zeit oder man masochistisch veranlagt ist.
... denn scheinbar soll man das WSGI-Programm eigentlich die ganze Zeit auf einem anderen Port lauschen lassen und die relevanten Requests dann irgendwie reverse-proxy-mäßig durchreichen, so wie es auf der von Sirius3 verlinkten Webseite steht. Das ist ja eine verbreitete Vorgehensweise, aber dann müsste ich diese Programme die nur selten gebraucht werden immer laufen lassen und das fände ich blöd.
.
Nein. Eine WSGI-kompatible Python Webanwendung wird von einem WSGI Server ausgeliefert. Super gängig ist Gunicorn, es gibt aber auch noch einen ganzen Schwung andere. Der Server lauscht auf einem Port auf einen Request und wirft dann bei Bedarf die Python WSGI Applikation an. Es ist in der Tat gängig, ngixnx als Reverse Proxy davor zu schalten - muss man aber nicht. Der WSGI Server kann auch direkt anfragen beantworten.

Was halt DER Vorteil von Webframeworks ist, ist das sie einem den ganzen Overhead von WSGI abnehmen. Außerdem machen Webframeworks wie Flask oder Bottle das Form Handling viel einfacher, weil sie das komplette Parsing fertig implementiert haben. Und zum Entwicklen sind Webframeworks einfacher, weil die einen kleinen Server eingebaut haben, den man dafür nutzen kann. D.h. man kann lokal entwickeln, ohne laufenden Webserver.

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Vielleicht noch ein Vorteil von den kleinen Entwicklungsservern der noch nicht erwähnt wurde: Die überwachen in der Regel die Quelltextdateien und laden die Webanwendung neu wenn sich da was geändert hat. Dann muss man nur zwischen Editor/IDE und Browser wechseln, und nicht zwischendurch nach Änderungen noch mal den Umweg über ein Terminal gehen um dort den Server neu zu starten oder zumindest zu informieren, dass sich was an den Quelltexten geändert hat.

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Antworten