Die Web-App rechnet im Browser statt auf dem Server
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
diff --git a/pom.xml b/pom.xml
index 02478cb..601b9e9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,6 +19,7 @@
<enforced.maven-version>3.9.0</enforced.maven-version>
+ <build-helper-plugin.version>3.6.0</build-helper-plugin.version>
<compiler-plugin.version>3.15.0</compiler-plugin.version>
<dependency-plugin.version>3.11.0</dependency-plugin.version>
<enforcer-plugin.version>3.6.3</enforcer-plugin.version>
@@ -33,12 +34,12 @@
<assertj.version>3.27.7</assertj.version>
<java-diff-utils.version>4.17</java-diff-utils.version>
<jboss-logging.version>3.6.3.Final</jboss-logging.version>
+ <jzlib.version>1.1.5</jzlib.version>
<jspecify.version>1.0.0</jspecify.version>
<junit.version>5.13.4</junit.version>
<pdfbox.version>3.0.8</pdfbox.version>
<picocli.version>4.7.7</picocli.version>
<slf4j.version>2.0.18</slf4j.version>
- <tika.version>3.3.1</tika.version>
</properties>
<dependencyManagement>
@@ -86,13 +87,6 @@
<version>${slf4j.version}</version>
</dependency>
- <!-- Document type detection -->
- <dependency>
- <groupId>org.apache.tika</groupId>
- <artifactId>tika-core</artifactId>
- <version>${tika.version}</version>
- </dependency>
-
<!-- PDF text extraction -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
@@ -267,4 +261,144 @@
</build>
+ <profiles>
+
+ <!--
+ Browserfassung: übersetzt dieselbe Pipeline mit GraalVM Web Image nach WebAssembly.
+ Ausdrücklich anzufordern (`-Pwasm`); der Standard-Build bleibt unberührt und braucht
+ kein GraalVM.
+
+ Voraussetzung: Oracle GraalVM 25.1+ (Web Image ist dort enthalten, in der CE nicht):
+
+ JAVA_HOME=/pfad/zu/oracle-graalvm ./mvnw -Pwasm package
+
+ Ergebnis: target/web/ — aendggner.js, aendggner.js.wasm und die statischen Seiten.
+ -->
+ <profile>
+ <id>wasm</id>
+
+ <properties>
+ <native-image.executable>${java.home}/bin/native-image</native-image.executable>
+ <wasm.output.directory>${project.build.directory}/web</wasm.output.directory>
+ <!--
+ Die Option „release 21“ beschränkte den Modulpfad auf die dokumentierte JDK-API; die
+ Web-Image-API des GraalVM wäre damit unsichtbar. Deshalb hier source/target statt
+ release — übersetzt wird ohnehin nur mit dem GraalVM, gegen dessen eigene Klassen.
+ -->
+ <maven.compiler.release/>
+ <maven.compiler.source>21</maven.compiler.source>
+ <maven.compiler.target>21</maven.compiler.target>
+ </properties>
+
+ <dependencies>
+ <!--
+ Reines Java-zlib. Web Image bindet die nativen zlib-Funktionen des JDK nicht an
+ (GR-65205); ohne Inflate ist kein PDF lesbar. Siehe
+ src/wasm/java/.../InflaterErsatz.java.
+ -->
+ <dependency>
+ <groupId>org.jruby</groupId>
+ <artifactId>jzlib</artifactId>
+ <version>${jzlib.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+
+ <!-- Der Wasm-Quellbaum kommt nur in diesem Profil hinzu: Er braucht die
+ Web-Image-API und svm.jar, die es nur im GraalVM gibt. -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <version>${build-helper-plugin.version}</version>
+ <executions>
+ <execution>
+ <id>wasm-quellen</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>src/wasm/java</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgs>
+ <arg>-Aproject=${project.groupId}/${project.artifactId}</arg>
+ <arg>--add-modules</arg>
+ <arg>org.graalvm.webimage.api</arg>
+ </compilerArgs>
+ <annotationProcessorPaths>
+ <path>
+ <groupId>info.picocli</groupId>
+ <artifactId>picocli-codegen</artifactId>
+ <version>${picocli.version}</version>
+ </path>
+ </annotationProcessorPaths>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>statische-seiten</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${wasm.output.directory}</outputDirectory>
+ <resources>
+ <resource>
+ <directory>src/main/resources/eu/mulk/aendggner/web</directory>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>web-image</id>
+ <phase>package</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ <configuration>
+ <executable>${native-image.executable}</executable>
+ <arguments>
+ <argument>--tool:svm-wasm</argument>
+ <!-- Der Textzwischenschritt (aendggner.js.wat) wird sonst mehrere hundert
+ Megabyte groß; auf die Größe des .wasm hat das keinen Einfluss. -->
+ <argument>-H:WasmComments=NONE</argument>
+ <argument>-classpath</argument>
+ <classpath/>
+ <argument>-o</argument>
+ <argument>${wasm.output.directory}/aendggner</argument>
+ <argument>eu.mulk.aendggner.wasm.BrowserMain</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ </build>
+ </profile>
+
+ </profiles>
+
</project>