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:
@@ -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.
|
||||
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user