PDF Dokumente auf dem Server erzeugen

Es gibt viele Wege auf einem (Web)-Server ein PDF Dokument zu erstellen, eine, die besondere Vorteile hat, möchte ich Euch heute vorstellen.

PDF Dokumente auf dem Server erzeugen

Wenn man „normalerweise“ Dokumente auf dem Server erzeugen will, so etwas wie Rechnungen, Bestellbestätigungen usw. muß man eine PDF-Lib nehmen. Mit deren Hilfe baut ein Programmierer das Dokument aus Einzelteilen zusammen, sprich, jede einzelne Linie, jeden Text und jede Schriftart. Dafür braucht er ewig lang und das bedeutet, es wird teuer.

Da Webseiten von Webentwicklern und Webdesignern gestaltet werden, wäre es doch viel besser, wenn diese Leute die Vorlagen für die Rechnungen in HTML, CSS und Javascript bauen könnten und man nur noch „die Werte“ einsetzt, oder? Genau das geht mit wkhtmltopdf und einigen anderen derartigen Programmen.

WKHTMLTOPDF

wkhtmltopdf hat den Vorteil, daß es in einer Statischen Form benutzt werden kann, ohne das man einen X11-VirtualFramebuffer braucht. Das macht den Einsatz auch auf gemieteten Webaccounts einfach.

Webdesigner können sich via HTML und CSS derart in dem Dokument austoben, wie es ein Programmierer mit der Lib fast nicht könnte und die Sache hat einen Vorteil: Man kann sich die erzeugte HTML Datei im Browser ansehen und bekommt so eine Vorschau ohne das man das PDF wirklich erzeugen und anzeigen müßte. Natürlich gibt es Stolpersteine und kleine Abweichungen vom Wunschzustand, das bleibt nicht aus. So kann man z.B. recht schlecht Seitenzahlen einfügen, außer man erzeugt die Seiten einzeln, bevor Sie ins PDF umgewandelt werden.

Beispiel:

wkhtmltopdf –print-media-type datei1.html datei2.html datei.pdf

–print-media-type gibt hier an, daß die print.css benutzt werden soll. So kann man in der Vorlage CSS fürs Web und für das PDF unterbringen. Dies erlaubt auch den einfachen Einsatz des Templates auf der Seite selbst 😉

Da wkhtmltopdf auch Javascript unterstützt, können auch dynamische PDFs erzeugt werden, die abhängig vom benutzergewählten Inhalt andere Darstellungen haben, ohne das man das großartig in der Webanwendung, die das PDF erzeugt, implementieren müßte. Das hat zur Folge, daß man bei einer geschickten Wahl des Dokumentenaufbaus, nichts an dem z.b. PHP Script machen muß, um neue Inhalte einzubauen.

Im konkreten Fall hatten wir eine SPA (Single Page Application ) gebaut, die dynamisch neue Formularinhalte hinzufügt, und damit hochgradig veränderliche Inhalte verarbeiten mußte. Das ist im PHP dann meistens die Hölle, das wieder zu sortieren, aber in unserem Fall konnten wir leicht die strukturelle Form in ein mehrseitiges PDF-Dokument überführen, ohne das das PHP Script wegen der Komplexität fehleranfällig wird oder etwas über die einzelnen Formularfelder wissen müßte, was über Basisinformationen wie Namen und Typen hinausgeht. Es lohnt sich immer eine gute Idee mal in der Praxis auszuprobieren 😉

Ghostscript

Meine Werbeagentur und ich haben diese Art der PDF Erzeugung neulich für ein Projekt benutzt, bei dem auch noch Bedingungen, für die es bereits fertige PDFs gab, in das finale Dokument eingefügt werden mußten. Dies kann man leicht mit Ghostscript erledigen, indem die einzelnen PDFs zu einem einzigen PDF verbunden werden:

gs -q -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=final.pdf datei.pdf bedingung1.pdf bedingung2.pdf

Natürlich könnte man die Bedingungen auch gleich als HTML Dokument in das vorher erzeugte PDF einbringen, aber in der Realität kommen solche Dokumente aus irgendwelchen Rechtsabteilungen und die können kein HTML oder CSS, sondern gerade einmal Word. Daher ist es meistens nötig, das in zwei Schritten zu realisieren.

Synapse 1.51+ mit schwerem Empfangsfehler

Marix Synapse Implementierung hat einen schweren Bug in den Versionen 1.51 und 1.52, der dazu führt, daß keine Nachrichten mit bestimmten Eigenschaften ( Verschlüsselung ) beim Empfänger ankommen.

Synapse 1.51+ mit schwerem Empfangsfehler

Kurios macht diesen Fehler, daß er nur bei Externen Nachrichten auftaucht. Schickt man intern Nachrichten von einem Account zum Anderen, kommen die Nachrichten trotzdem an.

Um festzustellen, daß Ihr betroffen seid, reicht es im Log des Homeservers nach diesen Fehlermeldungen zu suchen:

2022-02-09 00:00:41,933 - synapse.federation.transport.server.federation - 114 - ERROR - PUT-34443 - 'dict' object has no attribute 'edu_type'
Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/twisted/internet/defer.py", line 1661, in _inlineCallbacks
    result = current_context.run(gen.send, result)
StopIteration: 0

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/twisted/internet/defer.py", line 1661, in _inlineCallbacks
    result = current_context.run(gen.send, result)
StopIteration: {'ed25519:a_iiuD': FetchKeyResult(verify_key=<nacl.signing.VerifyKey object at 0x7fc8a5af6d90>, valid_until_ts=1644443665400)}

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/synapse/federation/transport/server/federation.py", line 101, in on_PUT
    device_list_updates = [
  File "/usr/lib/python3.9/site-packages/synapse/federation/transport/server/federation.py", line 104, in <listcomp>
    if edu.edu_type in DEVICE_UPDATE_EDUS
AttributeError: 'dict' object has no attribute 'edu_type'

Die Folge, Nachrichten mit diesem Attribute kommen nicht an, weil es einen GEHEIMEN! Defaulteintrag zu einem nicht standardmäßig konfigurierten Logger gibt! Ja, richtig gelesen: Weil die Nachrichten geloggt werden sollen, kommen Sie nicht an, weil das Pythonscript vorher crasht.

Die Lösung für das Problem

Die Lösung ist sehr einfach, was man auch in diesem Commit nachlesen kann:

Öffnet die Datei log_config.yaml und ändert Sie so:

loggers:
       synapse:
            level: ERROR
            handlers: file

       synapse.8631_debug:
            level: ERROR

Danach Synapse neustarten und das war es schon. Wenn Ihr betroffen ward und kein scheues Rehlein, sondern ein Matrix Hengst seid, bricht dann leider ein wahrer Sturm über Euren Server ein, weil alle anderen Server Euch gern die verpassten Nachrichten zustellen wollen. Kurzfristige Loadwarnungen sind zu tolerieren 😉

 

Vollversagen bei OpenSSH: Is a directory

Nehmt es mir nicht übel, aber anders als ein komplettes Versagen auf ganzer Linie kann man den Fall bei OpenSSH nicht bezeichnen, zumal es um einen kleinen, aber sinnvollen Bugfix geht.

Vollversagen bei OpenSSH: „Is a directory“

Damit Ihr versteht um was es geht, müssen wir zurückreisen ins Jahr 2010. Damals wurde ein Bugreport im Bugtracker von OpenSSH veröffentlicht: https://bugzilla.mindrot.org/show_bug.cgi?id=1768

Von dem wußte ich aber nichts, als ich 2015 über das Problem gestolpert bin. Das Problem sieht wie folgt aus:

scp testdatei user@servername:/topdir/subdir/

Wenn es „subdir“ als Directory gibt, dann wird testdatei dahin kopiert und alles ist gut. Wenn es „subdir“ aber nicht gibt, dann würde man ja wohl erwarten, eine Fehlermeldung zu bekommen, aus der genau das hervorgeht, oder? Tja, wie soll ich sagen, ähm…nein!

Actual results:

scp: /usr/doesnotexist/: Is a directory

Wow.. oder? Das komplette Gegenteil von dem was man annehmen würde 🙂 Diese Aussage bekommt jeder, der sich OpenSSH selbst aus den original Sourcen kompiliert, denn, obwohl der Fehler schon 2010 gemeldet wurde und 2015 Jakub Jelen von Red Hat einen kompletten Patch geschrieben hat und das seit Fedora 20 und RHEL8 an alle „Nutzer“ in einem „Feldtest“ ausgerollt wurde, sieht sich das Projekt hinter OpenSSH nicht dazu in der Lage.

Eingeführt wurde der Bug übrigens um das Jahr 2005 herum. Damit sind es streng genommen schon 15 Jahre, die der Bug da vor sich hin dümpelt. Da ich Fedora nutze, habe  ich das Problem nicht mehr und viele andere Distros haben den RH Patch übernommen, aber traurig ist das schon, oder?

Leider mußte ich gerade feststellen, daß die neue Fehlermeldung auch nicht gerade viel besser als die alte ist:

scp: /tmp/ugdjkfh/: Not a directory

Das stimmt zwar inhaltlich, gibt aber den wahren Ursprung meiner Meinung nach nur unzureichend wieder 😉 Daher war ich mal so frei, Jakub darauf hinzuweisen, zumal ich den Bug da auch bei RH reportet hatte, vielleicht bekommt man nach 10 Jahren dann doch nochmal ein „Does not exist“ 😀 Wobei man als Server ja unterscheiden können muß zwischen „directory gibts nicht“ und „Du User, darfst da nicht reinschreiben“ unterscheiden wenn so eine Anfrage kommt, sonst könnte ein User niedriger Privilegierung die Verzeichnisstruktur einfach durchtesten. Ok, er könnte das viel einfach, wenn er eingeloggt wäre, aber ggf. hat der Account nur SFTP Zugang, ohne Interaktiven Shellzugang zu haben. Wäre ja denkbar.

Wie man sieht, doch nicht ganz trivial so eine saubere Fehlermeldung 😉

Wenn Ihr jemanden im OpenSSH Team kennt, könnt Ihr Ihn ja mal auf dieses Problem ansprechen. Übrigens erinnert mich das ganz stark an meinen Bugreport an ProFTP, weil deren 20 Jahre alte CHROOT Anweisung den Fall, daß da einer das Ziel per Symlink umgeleitet hat, nicht abgedeckt hatte. Das wurde auch Jahrelang geblockt bis es dann doch wer geschafft hatte, die Entwickler umzustimmen. Leider durfte ich mit dem 20 Jahre Bug-Jubiläumsvortrag nicht auf dem CCC sprechen. Dabei wollte ich nur son 15 Minuten Vortragsfenster im Nebenraum haben. Ich hatte denen sogar einen Patch für das Problem geschickt. Das wäre bestimmt lustig geworden, wenn die ProFTP Devs sich auf der Vortragsliste gesehen hätten 😀 Vielleicht packe ich Euch den Vortrag als PDF mal auf die Seite.