From 2b1841e17533543d1cb15220ca86a2a37c853daf Mon Sep 17 00:00:00 2001
From: Matthias Andreas Benkard <code@mail.matthias.benkard.de>
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 <noreply@anthropic.com>
Change-Id: I38faf2ac0f764d601f080d4276babe4747773683
---
 README.adoc | 67 +++++++++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 48 insertions(+), 19 deletions(-)

(limited to 'README.adoc')

diff --git a/README.adoc b/README.adoc
index a29b7ac..24083c7 100644
--- a/README.adoc
+++ b/README.adoc
@@ -272,30 +272,59 @@ schlimmer als keine.
 [[web]]
 == Web-App
 
-Neben der CLI gibt es einen eigenständigen Webserver
-(`eu.mulk.aendggner.web.WebMain`), der dieselbe Pipeline
-(`eu.mulk.aendggner.Pipeline`) über ein einfaches Upload-Formular
-zugänglich macht: Stammgesetz- und Änderungsgesetz-Datei(en) hochladen,
-Synopse erhalten. Er nutzt ausschließlich JDK-Bordmittel
-(`com.sun.net.httpserver.HttpServer`) — keine zusätzliche
-Web-Framework-Abhängigkeit — und hält keine Rechtsdaten Dritter
-serverseitig vor; hochgeladene Dateien werden nur für die Dauer der
-Anfrage als temporäre Dateien gehalten und danach sofort gelöscht.
-
-Lokal starten nach <<building,dem Bauen>> der JAR:
+Neben der CLI gibt es eine Browserfassung, die dieselbe Pipeline
+(`eu.mulk.aendggner.Pipeline`) über ein Upload-Formular zugänglich macht:
+Stammgesetz- und Änderungsgesetz-Datei(en) wählen, Synopse erhalten.
+
+Sie braucht keinen Server: Die vollständige Verarbeitung — PDF-Textgewinnung
+mit PDFBox eingeschlossen — läuft als WebAssembly-Modul im Browser, übersetzt
+mit GraalVM Web Image aus demselben Java-Quelltext. Ausgeliefert werden nur
+statische Dateien; die gewählten Dokumente verlassen den Rechner der
+Nutzer:innen nicht.
+
+Bauen (verlangt Oracle GraalVM 25.1 oder neuer — Web Image ist dort enthalten,
+in der Community Edition nicht):
 
 [source,shell script]
 ----
-java -cp "target/aendggner-0.1.0-SNAPSHOT.jar:target/libs/*" \
-  eu.mulk.aendggner.web.WebMain
+JAVA_HOME=/pfad/zu/oracle-graalvm ./mvnw -Pwasm package
+----
+
+Ergebnis ist `target/web/` mit `index.html`, `app.js`, `worker.js`,
+`style.css`, `aendggner.js` und `aendggner.js.wasm` (rund 20 MB, komprimiert
+etwa 6 MB). Die daneben liegende `aendggner.js.wat` ist ein Zwischenschritt des
+Übersetzers und gehört nicht auf den Server.
+
+Lokal ansehen — `file://` genügt nicht, Browser laden Wasm-Module und Worker
+nur über HTTP:
+
+[source,shell script]
 ----
+python3 -m http.server --directory target/web 8000
+----
+
+Der öffentliche Betrieb braucht nur einen Webserver für statische Dateien;
+eine `nginx`-Vorlage liegt unter `deploy/nginx-aendggner.conf`. Die
+Befehlszeilenfassung bleibt davon unberührt und ist weiterhin der Weg für
+Massenläufe.
+
+=== Warum WebAssembly und nicht ein Java-Server
+
+Der Kern ist reines Java ohne Dateisystem- oder Netzzugriff; nur vier Stellen
+berührten die Plattform (PDFBox, MIME-Erkennung, XML-Parser, Dateizugriff).
+Sie sind hinter `eu.mulk.aendggner.Quelle` (Name + Bytes) und
+`eu.mulk.aendggner.DateiTyp` (Signaturbytes statt Tika) gebündelt, sodass
+Befehlszeile und Browser dieselbe Pipeline speisen.
+
+Zwei Eigenheiten von Web Image sind dabei zu beachten und im Quelltext
+vermerkt:
+
+* Die nativen zlib-Bindungen des JDK fehlen (`java.util.zip.Inflater`), ohne
+  die kein PDF lesbar ist. `src/wasm/java/.../InflaterErsatz.java` ersetzt sie
+  durch die reine Java-Umsetzung von jzlib.
+* Typisierte Felder lassen sich derzeit nicht nach `byte[]` umsetzen; der
+  Dateiinhalt wandert deshalb als Base64-Text über die JS-Grenze.
 
-Der Server bindet standardmäßig nur an `127.0.0.1:8080`; Adresse und
-Port lassen sich über die Umgebungsvariablen `AENDGGNER_WEB_BIND` und
-`AENDGGNER_WEB_PORT` ändern. Für den öffentlichen Betrieb gehört davor
-ein TLS-terminierender Reverse Proxy mit Rate-Limiting — eine
-`nginx`-Vorlage sowie eine `systemd`-Unit liegen unter `deploy/`
-(`deploy/nginx-aendggner.conf`, `deploy/aendggner-web.service`).
 
 Vor dem produktiven, öffentlichen Betrieb sind zwingend zu erledigen:
 
-- 
cgit v1.2.1

