From 2b1841e17533543d1cb15220ca86a2a37c853daf Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Sat, 15 Aug 2026 10:51:11 +0200 Subject: Die Web-App rechnet im Browser statt auf dem Server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bisher lief die Weboberfläche auf einem JDK-HttpServer: Uploads landeten als temporäre Dateien auf dem Server, die Synopse entstand dort. Das kostete Betrieb (systemd, Reverse Proxy, Rate-Limiting, Timeouts) und verlangte ein Datenschutzversprechen, das nur zusicherbar, nicht nachprüfbar war — gerade Entwurfstexte verließen den Rechner der Nutzer:innen. Neu übersetzt `./mvnw -Pwasm package` dieselbe Pipeline mit GraalVM Web Image (`native-image --tool:svm-wasm`) nach WebAssembly, PDFBox eingeschlossen. Ausgeliefert werden nur noch statische Dateien; gerechnet wird im Browser. Die erzeugte Synopse ist byteweise identisch mit der der Befehlszeile (SHA-256 verglichen für IfSG 48/27/21 und BayJG 151/3/54). Die Befehlszeile bleibt unberührt: `./mvnw package` erzeugt unverändert das JAR, alle Optionen und Meldungstexte sind gleich, das Wasm-Profil ist rein additiv und verlangt Oracle GraalVM 25.1+ (die CE hat kein Web Image). Portabilitätsschnitt (nützt beiden Fassungen): * `Quelle` (Name + Bytes) ersetzt `Path` in der Pipeline; nur die Befehlszeile kennt noch ein Dateisystem. Der Name trägt genau den bisherigen `getFileName()`-Text, damit Warnungen und Quellenzeile wortgleich bleiben. * `DateiTyp` erkennt PDF/XML/Klartext an den Signaturbytes. Tika entfällt — eine schwergewichtige Abhängigkeit samt ServiceLoader- und XML-Konfiguration weniger, was der Wasm-Übersetzung unmittelbar zugutekommt. Vier Eigenheiten von Web Image, die der Quelltext jeweils an Ort und Stelle vermerkt: * `java.util.zip.Inflater` ist nicht angebunden (GR-65205), ohne Inflate ist kein PDF lesbar. `InflaterErsatz` substituiert ihn durch jzlib. * Typisierte JS-Felder lassen sich nicht nach `byte[]` umsetzen („byteArrayHub is not defined“); der Dateiinhalt wandert als Base64. * JULs Standardformatter ruft `StackWalker`, den es dort nicht gibt. * Im Worker fehlt `document.currentScript`, worauf die Laufzeit das Wasm-Modul neben `worker.js` sucht; die VM wird deshalb mit ausdrücklichem Pfad ein zweites Mal gestartet. Die Reachability-Metadaten stammen aus einem Lauf des Tracing-Agents über die Pipeline; die PDFBox- und FontBox-Ressourcen sind als Globs ergänzt, sonst scheitern PDFs an „Could not find referenced cmap stream Identity-H“. Entfallen: WebMain, UploadHandler, StaticHandler, Multipart und die systemd-Unit. Die nginx-Vorlage liefert jetzt statische Dateien aus, und die Datenschutzseite sagt, was nun stimmt: Die Dateien verlassen den Rechner nicht. Co-Authored-By: Claude Opus 5 Change-Id: I38faf2ac0f764d601f080d4276babe4747773683 --- deploy/nginx-aendggner.conf | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) (limited to 'deploy/nginx-aendggner.conf') diff --git a/deploy/nginx-aendggner.conf b/deploy/nginx-aendggner.conf index f4667fb..46eacb5 100644 --- a/deploy/nginx-aendggner.conf +++ b/deploy/nginx-aendggner.conf @@ -1,9 +1,8 @@ -# Reverse-Proxy-Vorlage für den ÄndGgner-Webserver. Übernimmt TLS-Terminierung und -# Rate-Limiting; der Java-Prozess selbst bindet nur an 127.0.0.1 (siehe aendggner-web.service). +# Vorlage für die Auslieferung der ÄndGgner-Browserfassung. Es gibt keinen +# Anwendungsprozess mehr: Der Server liefert nur statische Dateien aus +# (Inhalt von target/web/), gerechnet wird im Browser. # -# Vor dem Einsatz anpassen: server_name, ssl_certificate(_key) (z. B. via certbot). - -limit_req_zone $binary_remote_addr zone=aendggner:10m rate=10r/m; +# Vor dem Einsatz anpassen: server_name, ssl_certificate(_key) (z. B. via certbot), root. server { listen 80; @@ -20,20 +19,29 @@ server { ssl_certificate /etc/letsencrypt/live/aendggner.example.org/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/aendggner.example.org/privkey.pem; - # Muss zum Upload-Limit von UploadHandler (MAX_BODY_BYTES, aktuell 40 MB) passen. - client_max_body_size 40m; + # Inhalt von target/web/ — ohne aendggner.js.wat, das nur ein Zwischenschritt + # des Übersetzers ist. + root /var/www/aendggner; + index index.html; - location / { - limit_req zone=aendggner burst=5 nodelay; + # Ohne diesen Typ lehnt der Browser die Streaming-Instanziierung des Moduls ab. + types { + application/wasm wasm; + } - proxy_pass http://127.0.0.1:8080; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + gzip on; + gzip_types application/wasm application/javascript text/css text/html; + gzip_min_length 1024; - # Etwas großzügiger als der 30s-Timeout der Anwendung, damit deren eigene - # 504-Antwort den Nutzer erreicht statt eines nginx-eigenen Timeouts. - proxy_read_timeout 35s; + # Das Wasm-Modul ist einige Megabyte groß und ändert sich nur mit einer neuen + # Fassung; vorkomprimierte .gz/.br daneben zu legen lohnt sich (ngx_brotli bzw. + # gzip_static). + location ~ \.(wasm|js|css)$ { + expires 7d; + add_header Cache-Control "public"; + } + + location / { + try_files $uri $uri/ =404; } } -- cgit v1.2.1