feat(core): FILIUS' web server and web browser after Java, measured against Java

The servers share ServerAnwendung's frame (server.ts); the web server serves
/webserver with virtual hosts, the browser loads a page and its images over
one connection to port 80 (web.ts); HTTP in Java's text form (http.ts).
WebRun records eleven stories on the new web-lan network, with the server's
files; webJava.test.ts plays them card by card. A program's close is a
request, as Java's schliessen. Not taken from Java: /../ serving any file of
the host; an unreadable request closing the connection without a word.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 16:53:00 +00:00
co-authored by Claude Opus 5.5
parent de3a1d4dfe
commit 4cd5749350
33 changed files with 53601 additions and 216 deletions
+37
View File
@@ -580,3 +580,40 @@ Gemessen (01.10.2026):
dazwischen; so misst es `otherPort`.
* Der Client sendet nur ein SYN (wie bei `TcpRun`, `closedPort`).
## Webserver und Browser (F3.2a)
`WebRun` installiert auf dem Netz von `web-lan` (Browser 192.168.0.20,
DNS-Server .10, Webserver .30, ein Rechner ohne Webserver .31) Filius'
Webserver, eingeschaltet auf Port 80, mit den Dateien, die Filius ihm gibt
(`index.html` und ihr Bild), und eigenen (Seite mit Bildern, Text, Namen mit
Leerzeichen und Umlaut, virtuelle Hosts in `/www.conf/vhosts`, eine Datei
außerhalb von `/webserver`). Eine Adresse wird eingetippt wie ins Fenster und
wie dort zur URL (`erzeugeURL`). Es schreibt die Frames jeder Karte, Javas
Tabellen, die Fensterzeilen des Servers, was der Browser seinem Fenster
meldet (Seite mit Status, Inhaltsart und Text, oder „update“ für ein Bild),
und alle Dateien des Servers am Ende. Abläufe: `page`, `byName`,
`notFound`, `vhost`, `noServer`, `unresolved`, `post`, `encoded`,
`traversal`, `caseInsensitive`, `raw` (der einfache Client spricht mit dem
Server).
Gemessen (01.10.2026):
* Der Browser verbindet sich immer mit Port 80; die Adresszeile nimmt keinen
Port (Javas Muster für Hosts). Ein Schrägstrich am Ende fällt weg
(`String.split`), `/vhost1/` ist dann ein Ordner und 404.
* Je Seite eine Verbindung: Seite, dann jedes `<img>` nacheinander (das rohe
`src`, auch `IMG SRC='…'` und ohne Anführungszeichen, relativ ohne
führenden Schrägstrich), dann schließen. Ein Bild kommt als Javas Base64
mit Zeilenumbruch nach 76 Zeichen.
* Pfade gehen kodiert (`UriUtils.encodePath`: `%20`, `%C3%BC`), die Anfrage
ohne Abfrage (`getPfad`), mit dem Host, wie er eingetippt wurde.
* `GET /../geheim.txt` liefert `/geheim.txt`: Javas Dateisystem löst `..`
auf, der Server prüft nicht, ob es unter `/webserver` bleibt.
* Gesucht wird ohne Rücksicht auf Groß- und Kleinschreibung (Name und Host).
* „HALLO“ oder „GET /“ ohne Version: `HTTPNachricht` wirft, der Thread des
Servers schließt die Verbindung und meldet `null`.
* Beim Wechsel der Seite geht die DNS-Anfrage der neuen Seite vor dem FIN der
alten: `schliessen()` bittet den Socket-Thread nur.
* Die Fehlerseiten macht der Browser selbst (im Messaufbau Javas Ersatztext,
weil die Vorlage fehlt); der Vergleich sieht nur den Status.
+14 -1
View File
@@ -21,7 +21,7 @@ if [ ! -f "$upstream/target/filius.jar" ]; then
fi
classpath="$upstream/target/filius.jar:$upstream/target/lib/*"
mkdir -p "$here/.build"
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java"
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java" "$here/src/WebRun.java"
# FILIUS writes its settings below $HOME; keep them out of the real one.
mkdir -p "$here/.build/home"
# Removed first: a crashed run must not leave the previous result standing.
@@ -206,6 +206,19 @@ for run in exchange:12000 closedPort:20000 noRoute:8000 notConnected:3000 server
test -s "$output"
done
# The web server and the web browser as Java FILIUS runs them, on web-lan's
# network (WebRun); frames, the server's window lines, the browser's pages and
# images, and the server's files.
for run in page:8000 byName:12000 notFound:6000 vhost:14000 noServer:14000 unresolved:6000 post:8000 \
encoded:10000 traversal:6000 caseInsensitive:8000 raw:14000; do
story="${run%%:*}"
output="$root/packages/core/test/scenarios/web-java-$story.java.json"
rm -f "$output"
HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" WebRun \
"$root/packages/core/test/scenarios/web-lan.scenario.json" "$story" "${run##*:}" "$output" 2>&1 | grep -v 'logback\|^$' || true
test -s "$output"
done
# FILIUS' own examples as Java FILIUS runs them, opened and started as FILIUS
# does (ExampleRun): the acceptance of F2 (packages/fls/test/examplesJava.test.ts).
for run in internetwork_komplett:routing:12000 einfaches_rechnernetz_komplett:dhcp:20000 dns_server:dns:12000 \
+237
View File
@@ -0,0 +1,237 @@
// Runs a story of FILIUS' web server (WebServer) and web browser
// (WebBrowser) on a core scenario's network in Java FILIUS (ScenarioNetwork)
// and records what really happens: every frame each card saw, every line the
// server wrote into its window, and everything the browser told its window
// (a page: its status, content type and text; or "update", when an image
// came or failed).
//
// WebRun <scenario.json> <story> <milliseconds> <out.json>
//
// The stories use the network of web-lan (browser on 192.168.0.20, DNS
// server .10, web server .30, a host without one .31). The web server runs
// switched on, on port 80, with the files FILIUS gives it (index.html and
// its image) and those below (files), its virtual hosts in /www.conf/vhosts.
// An address is typed as into the browser's window and made into a URL as
// the window does (GUIApplicationWebBrowserWindow.erzeugeURL).
// page 192.168.0.30
// byName www.filius.de/seite.html (images found, missing, in a
// folder, absolute), then a link without host: /text.txt
// notFound http://www.filius.de/nichts.html
// vhost seite.filius.de, andere.filius.de, www.filius.de/vhost1/
// noServer leer.filius.de (no web server there)
// unresolved unbekannt.filius.de (no address)
// post a form's POST to www.filius.de/seite.html
// encoded www.filius.de/mein bild.html, www.filius.de/grüße.html
// traversal www.filius.de/../geheim.txt
// caseInsensitive WWW.Filius.de/SEITE.HTML
// raw FILIUS' generic client on .20 talks to the server: a
// request, then "HALLO"; connected anew, "GET /" without
// a version
//
// files: every file of the server's file system at the end, path, type and
// content, for the core to give its server the same.
import filius.rahmenprogramm.EingabenUeberpruefung;
import filius.software.clientserver.ClientBaustein;
import filius.software.system.Datei;
import filius.software.system.Dateisystem;
import filius.software.www.HTTPNachricht;
import filius.software.www.WebBrowser;
import filius.software.www.WebServer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
public class WebRun {
static final String BROWSER = "00000000-0000-0000-0000-000000000201";
static final String SERVER = "00000000-0000-0000-0000-000000000203";
static ScenarioNetwork network;
static WebBrowser browser;
static final List<Object[]> events = new ArrayList<>();
static synchronized void event(String who, String what) {
events.add(new Object[] { System.currentTimeMillis() - network.start, who, what });
}
static void sleepUntil(long milliseconds) throws InterruptedException {
long rest = network.start + milliseconds - System.currentTimeMillis();
if (rest > 0) Thread.sleep(rest);
}
/** GUIApplicationWebBrowserWindow.erzeugeURL. */
static URL url(String ressource) throws Exception {
String[] parts = ressource.split("/");
String host = null, path = "";
if (parts.length > 0 && !parts[0].equalsIgnoreCase("http:")) {
if (EingabenUeberpruefung.isGueltig(parts[0], EingabenUeberpruefung.musterDomain)
|| EingabenUeberpruefung.isGueltig(parts[0], EingabenUeberpruefung.musterIpAdresse)) {
host = parts[0];
for (int i = 1; i < parts.length; i++) path = path + "/" + parts[i];
}
}
if (parts.length > 2 && parts[0].equalsIgnoreCase("http:")) {
if (EingabenUeberpruefung.isGueltig(parts[2], EingabenUeberpruefung.musterDomain)
|| EingabenUeberpruefung.isGueltig(parts[2], EingabenUeberpruefung.musterIpAdresse)) {
host = parts[2];
}
for (int i = 3; i < parts.length; i++) path = path + "/" + parts[i];
}
if (host == null) return null;
if (path.equals("")) path = "/";
return new URL("http", host, path);
}
static void open(String typed, String post) throws Exception {
event("browser", "call holeWebseite " + typed + (post == null ? "" : " POST " + post));
URL url = url(typed);
if (url == null) {
event("browser", "no url");
} else if (post == null) {
browser.holeWebseite(url);
} else {
browser.holeWebseite(url, post);
}
}
static void file(Dateisystem files, String directory, String name, String type, String content) {
files.speicherDatei(directory, new Datei(name, type, content));
}
public static void main(String[] args) throws Exception {
network = ScenarioNetwork.read(args[0]);
String story = args[1];
long end = Long.parseLong(args[2]);
network.system(SERVER).installApp("filius.software.www.WebServer", false);
network.system(BROWSER).installApp("filius.software.www.WebBrowser", false);
WebServer server = (WebServer) network.system(SERVER).holeSoftware("filius.software.www.WebServer");
browser = (WebBrowser) network.system(BROWSER).holeSoftware("filius.software.www.WebBrowser");
if (story.equals("raw")) {
network.system(BROWSER).installApp("filius.software.clientserver.ClientBaustein", false);
network.system(BROWSER).holeSoftware("filius.software.clientserver.ClientBaustein")
.hinzuBeobachter((observable, data) -> event("client", String.valueOf(data)));
}
Dateisystem files = network.system(SERVER).getDateisystem();
String image = files.holeDatei("/webserver/splashscreen-mini.png").getDateiInhalt();
file(files, "/webserver", "seite.html", "html",
"<html><head><title>Seite</title></head><body><h1>Seite</h1>\n<img src=\"bild.png\"><IMG SRC='fehlt.png'>\n"
+ "<img alt=\"x\" src=unter/bild.png><img src=\"/bild.png\"></body></html>");
file(files, "/webserver", "bild.png", "png", image);
files.erstelleVerzeichnis("/webserver", "unter");
file(files, "/webserver/unter", "bild.png", "png", image);
file(files, "/webserver", "text.txt", "txt", "Nur Text\nzwei Zeilen");
file(files, "/webserver", "mein bild.html", "html", "<html><body>Leerzeichen</body></html>");
file(files, "/webserver", "grüße.html", "html", "<html><body>Umlaute</body></html>");
file(files, "/", "geheim.txt", "txt", "geheim");
files.erstelleVerzeichnis("/", "www.conf");
file(files, "/www.conf", "vhosts", "", "seite.filius.de\nvhost1\nandere.filius.de\nvhost2\n");
files.erstelleVerzeichnis("/webserver", "vhost1");
file(files, "/webserver/vhost1", "index.html", "html", "<html><body>Eins</body></html>");
files.erstelleVerzeichnis("/webserver", "vhost2");
file(files, "/webserver/vhost2", "index.html", "html", "<html><body>Zwei</body></html>");
server.setAktiv(true);
server.hinzuBeobachter((observable, data) -> event("server", String.valueOf(data)));
browser.hinzuBeobachter((observable, data) -> {
if (data instanceof HTTPNachricht) {
HTTPNachricht page = (HTTPNachricht) data;
event("browser", "page " + page.getStatusCode() + " " + page.getContentType() + "\n" + page.getDaten());
} else {
event("browser", data == null ? "update" : String.valueOf(data));
}
});
network.start();
sleepUntil(500);
switch (story) {
case "page":
open("192.168.0.30", null);
break;
case "byName":
open("www.filius.de/seite.html", null);
sleepUntil(6000);
open("http:///text.txt", null);
break;
case "notFound":
open("http://www.filius.de/nichts.html", null);
break;
case "vhost":
open("seite.filius.de", null);
sleepUntil(5000);
open("andere.filius.de", null);
sleepUntil(9500);
open("www.filius.de/vhost1/", null);
break;
case "noServer":
open("leer.filius.de", null);
break;
case "unresolved":
open("unbekannt.filius.de", null);
break;
case "post":
open("www.filius.de/seite.html", "name=Filius&x=1");
break;
case "encoded":
open("www.filius.de/mein bild.html", null);
sleepUntil(5000);
open("www.filius.de/grüße.html", null);
break;
case "traversal":
open("www.filius.de/../geheim.txt", null);
break;
case "caseInsensitive":
open("WWW.Filius.de/SEITE.HTML", null);
break;
case "raw": {
ClientBaustein client = (ClientBaustein) network.system(BROWSER).holeSoftware("filius.software.clientserver.ClientBaustein");
client.setZielIPAdresse("192.168.0.30");
client.setZielPort(80);
event("client", "call verbinden");
client.verbinden();
sleepUntil(2500);
event("client", "call senden GET /text.txt");
client.senden("GET /text.txt HTTP/1.1\nHost: 192.168.0.30\n");
sleepUntil(4500);
event("client", "call senden HALLO");
client.senden("HALLO");
sleepUntil(7000);
event("client", "call verbinden");
client.verbinden();
sleepUntil(9000);
event("client", "call senden GET /");
client.senden("GET /");
break;
}
default:
throw new IllegalArgumentException("unknown story " + story);
}
sleepUntil(end);
ScenarioNetwork.Json out = new ScenarioNetwork.Json();
out.open('{');
network.writeFrames(out);
synchronized (WebRun.class) {
out.key("events").open('[');
for (Object[] each : events) out.open('[').number((Long) each[0]).string((String) each[1]).string((String) each[2]).close(']');
out.close(']');
}
out.key("files").open('[');
Enumeration<TreeNode> nodes = files.getRoot().preorderEnumeration();
while (nodes.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) nodes.nextElement();
if (!(node.getUserObject() instanceof Datei)) continue;
Datei datei = (Datei) node.getUserObject();
out.open('[').string(Dateisystem.absoluterPfad(node)).string(datei.getDateiTyp()).string(datei.getDateiInhalt()).close(']');
}
out.close(']');
out.close('}');
network.stop();
ScenarioNetwork.write(args[3], out);
System.exit(0);
}
}