Gibt es dafür Schläge oder sogar eine spezielle Hölle? Weil, es ist überaus praktisch.
Es gibt ca. 60 Funktionen int foo(const char*); Die werden bisher alle in einem einzigen main() benutzt. Jetzt soll jede Funktion ein eigenständiges Programm werden und per klassischem CGI vom httpd aufgerufen werden. An den Funktionen selbst muss fast nichts geändert werden, die brauchen nur ein main() drum herum. Diese ca. 60 main() wären alle identisch, nur ist main.c z.Zt. noch ziemlich ausbaufähig:
cc -I include/ src/server_framework/main.c -c -o server_framework_main.o
2
cc -I include/ src/plugin/a/main.c -c -o plugin_a.o
3
cc -I include/ src/plugin/b/main.c -c -o plugin_b.o
4
cc server_framework_main.o plugin_a.o -o plugin_a
5
cc server_framework_main.o plugin_b.o -o plugin_b
src/server_framework/* kann man natürlich auch in eine Library packen, statisch oder dynamisch geht beides. (Zumindest unter Linux, unter Windows geht vermutlich nur statisch, wenn da die main drin ist).
1
mkdir lib
2
cc -fPIC -I include/ src/server_framework/main.c -c -o server_framework_main.o # -fPIC brauchts nur für shared libs
3
ar r lib/libserver_framework.a server_framework_main.o # Statische lib
4
cc -shared lib/libserver_framework.a -o lib/libserver_framework.so # Optional, dynamische lib
5
6
cc -Llib plugin_a.o -o plugin_a -lserver_framework
7
cc -Llib plugin_b.o -o plugin_b -lserver_framework
8
9
// LD_LIBRARY_PATH falls dynamische Library verwendet wird. Alternativ, in /usr/local/lib installieren und ldconfig aufrufen
10
LD_LIBRARY_PATH=$PWD/lib/ ./plugin_a
(Unter Windows kann man nicht das selbe Archiv für die Statische lib und die DLL nehmen, dort braucht man für die .o / .obj schon andere Optionen)
Muss man halt einmal ein Makefile dafür erstellen.
PS: Ich nehme gerne -Wl,--whole-archive libserver_framework.a -Wl,--no-whole-archive damit keine undefinierten oder doppelten Symbole wegen Linkerreihenfolge rumschwirren
Muss es denn unbedingt #include sein? Warum nicht die main.c ganz normal kompilieren und dazu linken? Ggf. per "-D" Compiler Option den Namen der gewünschten Funktion als Makro definieren.
src/server_framework/* kann man natürlich auch in eine Library packen,
statisch oder dynamisch geht beides.
Diese Funktionen benutzen sowieso eine shared library. Ich hab' jetzt mal nichts weiter gemacht als main da rein zu packen (die include-Datei ist jetzt leer). Das funktioniert ohne weitere Änderung sofort. Das Beste: die 60 Programme müssen nach Änderungen an main nicht neu gelinkt werden. Allerdings ist main() jetzt wirklich gut versteckt ;)
Die Namen der Funktionen möchte ich nicht ändern, manchmal ist func ganz nützlich. Also behalte ich den Funktionspointer; ein Makro funktioniert ja nicht, wenn main() in der lib steckt.
Es gibt ca. 60 Funktionen int foo(const char*); Die werden bisher alle
in einem einzigen main() benutzt. Jetzt soll jede Funktion ein
eigenständiges Programm werden und per klassischem CGI vom httpd
aufgerufen werden.
Per CGI? Wow, sowas habe ich schon lange nicht mehr gesehen. Die Performance der schnellen C-Binaries wird dann auch weitgehend zunichte gemacht, und ein Microservice (zur Not über einen Reverse Proxy, sowas kann der Apache httpd) oder FastCGI wären womöglich klügere Lösungen.
Aber sei es drum: wenn es unbedingt ein CGI sein soll, würde es die Geschichte vielleicht vereinfachen, einen ähnlichen Weg wie bei Busybox zu gehen. Das ist nur ein Binary, das die gewünschte Funktion über den Dateinamen ermittelt, für jede Funktion wird dann also ein Softlink angelegt (benötigt beim Apaches fürs Cgi-Bin-Verzeichnis eine Option "FollowSymLinks" oder "SymLinksIfOwnerMatch"). Hardlinks würden natürlich auch gehen.
Ein statisch kompilierbares C++-Programm, das das Busybox-Konzept realisiert und je nach dem Namen des Binary eine C-Funktionen aufruft oder einen Fehler aus- und !=0 zurück gibt, hab ich hier einmal angehängt. Es ist aber nur ein schneller Hack ohne große Ambitionen bezüglich Codequalität etc.. HTH, HF!
Wenn das jetzt Dein Problem einmalig löst und es damit gut ist, mache es.
Mit Script/Make/Linker-Skills gibt es viele elegantere Alternativen. Aber ganz ehrlich: Für den Anwendungsfall gibt es keinen Schönheitspreis.
Wenn Du für jeden Batchbuild (der 60) jetzt >> 10 Minuten an Arbeit brauchst und das noch zig Male die nächsten Tage, dann kann man das sicher reduzieren. Dann frag einfach nochmal nach.
Per CGI? Wow, sowas habe ich schon lange nicht mehr gesehen. Die
Performance der schnellen C-Binaries wird dann auch weitgehend zunichte
gemacht
Aber sei es drum: wenn es unbedingt ein CGI sein soll, würde es die
Geschichte vielleicht vereinfachen, einen ähnlichen Weg wie bei Busybox
zu gehen.
Mit einem fetten Binary wird es doch noch langsamer? Ich sehe das so: Speziell bei Linux ist ein fork() noch relativ preisgünstig, das execve() kostet extra. Insofern sollten viele kleine CGI-Programme mit einer shared library günstiger sein als etwas nach Art der Busybox. In dieser speziellen Anwendung spielt das sowieso keine Rolle, es passt praktisch alles in den Cache, inkl. Daten und man erwartet vielleicht ein Dutzend Benutzer gleichzeitig.
Solange ich noch dran rumbastel, hat fork() noch den Vorteil, dass ein CGI abstürzen darf ohne den Rest zu stören. Der httpd selbst muss nur neu starten, wenn /seine/ Config geändert würde. Und übersetzen geht auch 60 Mal schneller ;)
Die alte Version war noch extremer als der ganze neumodische Kram wie FastCGI. Alle Funktionen waren in den eigentlichen httpd eingebaut, also brauchte man nur ein fork() pro Request. Das hätte man auch noch sparen können, aber es hat auch Vorteile, siehe oben.
Für den Anwendungsfall gibt es keinen Schönheitspreis.
Schade :)
Wenn Du für jeden Batchbuild (der 60) jetzt >> 10 Minuten an Arbeit
brauchst und das noch zig Male die nächsten Tage, dann kann man das
sicher reduzieren.
Insofern sollten viele kleine CGI-Programme mit
einer shared library günstiger sein als etwas nach Art der Busybox.
Shared Objects können im Speicher bleiben sodass mehrere Prozesse davon profitieren können. Bei einem Binary mit allen Funktionalitäten (umgeschaltet per argv[0] oder argv[1] oder whatever) dürften diese vollständig im Cache landen, wodurch die Aufrufe an alle Funktionen auch ziemlich schnell gehen. Insbesondere weil ja nur der Programmcode (.text, .rodata) groß ist, und der auch einfach in mehrere Prozesse einblendet werden kann.
Die interessante Frage ist ob eine permanent geladene Web-App (Flask, Java Servlet...?) nicht doch schneller wäre...
Shared Objects können im Speicher bleiben sodass mehrere Prozesse davon
profitieren können.
Bei Programmcode ist mir das klar.
Aber wie geht das mit Daten, insbesondere Session Daten und Caches für DB Abfragen? Ich schätze, dass dazu wieder zusätzliche Software nötig ist, die nicht bei jedem Request neu geladen wird (Redis?).
Ich schätze, dass dazu wieder zusätzliche Software nötig ist, die nicht
bei jedem Request neu geladen wird (Redis?).
Klar, diese Daten muss jeder Prozess dann immer irgendwie neu beschaffen. Das wäre der Gag von Techniken wie Servlets oder auch node.js wo die Applikation permanent läuft, beliebige Daten im Speicher halten kann, und immer wieder neue Anfragen verarbeitet.
Shared Objects können im Speicher bleiben sodass mehrere Prozesse davon
profitieren können.
Bei Programmcode ist mir das klar.
Aber wie geht das mit Daten, insbesondere Session Daten und Caches für
DB Abfragen?
Im Prinzip ganz genauso, es gibt shared memory für Daten. Jedes Programm mapped statt Programmcode einen gemeinsamen Datenblock. Oder auch einen privaten, der aber länger lebt als das Programm.
Braucht man sowas wirklich? Ja, man möchte natürlich verschiedene Benutzer unterscheiden können, z.B. mit einem Session Cookie. Aber das heißt doch nur so, weil man Username und Passwort nicht direkt in so ein Cookie packen will, oder?
Im Prinzip ganz genauso, es gibt shared memory für Daten. Jedes Programm
mapped statt Programmcode einen gemeinsamen Datenblock. Oder auch einen
privaten, der aber länger lebt als das Programm.
Mit Shared Memory zu arbeiten ist allerdings eine ziemlich... sagen wir mal, klassische Designentscheidung, insbesondere weil sich derartige Architekturen nicht besonders gut verteilen bzw Loadbalancen lassen. Da könnten ein Valkey oder Redis mehr Freiheiten anbieten.
Braucht man sowas wirklich? Ja, man möchte natürlich verschiedene
Benutzer unterscheiden können, z.B. mit einem Session Cookie. Aber das
heißt doch nur so, weil man Username und Passwort nicht direkt in so ein
Cookie packen will, oder?
Je nach Anwendungsfall kann man das schon machen, aber... okay, Paßworte eher nicht. Aber den Benutzernamen, seine Gruppen, Rollen, und Berechtigungen in die Cookies zu packen, ist zum Beispiel bei den JWTs von OpenID Connect der Standard. Und da diese Daten kryptografisch signiert sind, besteht dann auch keine Gefahr von unbefugter Manipulation. Derartige Cookies enthalten je nach Anwendungsfall bereits alle benötigten Daten, so daß keine weiteren Netzwerk-Roundtrips zum Server mehr notwendig sind.
Aber wie geht das mit Daten, insbesondere Session Daten und Caches für
DB Abfragen? Ich schätze, dass dazu wieder zusätzliche Software nötig
ist, die nicht bei jedem Request neu geladen wird (Redis?).
Je nach Anforderung tut es auch ein stinkendes tmpfs.
Klar, diese Daten muss jeder Prozess dann immer irgendwie neu
beschaffen. Das wäre der Gag von Techniken wie Servlets oder auch
node.js wo die Applikation permanent läuft, beliebige Daten im Speicher
halten kann, und immer wieder neue Anfragen verarbeitet.
So oft wie man node.js patchen muss kann man da nicht von permanent laufen reden.