feat(editor): F4.6d the web browser and web server on a computer's desktop

The oracle BrowserWindowRun uses Java's browser and web server windows as a
person does; the room keeps a browser window per computer and sends a
program window's view, the start names the person's language. The editor
shows the page in a sandboxed frame, links and forms go to the room, the web
server has its button, virtual hosts and lines. Better than Java: relative
images and links count from the page's folder, a mark stays in the page, the
error page from Java's template, the virtual hosts' file with complete rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 16:09:05 +00:00
co-authored by Claude Opus 5.5
parent f0e7c73ec7
commit aa54e5400e
31 changed files with 2773 additions and 55 deletions
+40
View File
@@ -1292,3 +1292,43 @@ Gemessen (03.10.2026):
Darum misst `GetterDefaults` jetzt auch `#priority` der gespeicherten
Thread-Klassen (alle 5), die vorher unaufgelöst als 0 gelesen wurden.
### Webbrowser und Webserver (F4.6d)
`BrowserWindowRun` öffnet Javas `GUIApplicationWebBrowserWindow` und
`GUIApplicationWebServerWindow` auf je einem `GUIDesktopPanel` im Netz von
web-lan (Browser 192.168.0.20, Webserver .30 mit eigenen Seiten) und benutzt
sie wie eine Person: eine Adresse in die Adresszeile und Enter, ein Link
(das Ereignis, das Swings `HTMLEditorKit` daraus macht: das href gegen die
Basis des Dokuments), ein Formularknopf (der echte Knopf, den Swing für das
Formular zeichnet, mit `doClick`), am Server Startknopf, Kästchen und
Tabelle der virtuellen Hosts, dazu Stopp und Start des Systems. Nach jedem
Schritt schreibt es Adresszeile, Titel, die Seiten, die das Programm dem
Fenster gab, und die Anfragen des Servers, am Server Knopf, Kästchen,
Tabelle, Datei und Zeilen (`packages/sim/test/browserWindow.java.json`).
Gemessen (03.10.2026):
* Am Anfang zeigt die Adresszeile „http://“, der Titel den Programmnamen,
die Seite Javas Logo. Jeder Abruf schreibt „http://“ + Host + Pfad ohne
Query in Zeile und Titel; kam die Seite, wird ihr Titel der Titel des
Fensters — als HTML („Links &amp;amp; mehr“). Keine Adresse: die Zeile
zurück auf „http://“, der Titel bleibt, nichts lädt.
* Ein Query geht nie hinaus (`HTTPNachricht.getPfad` schneidet ihn ab):
weder getippt noch aus einem Link noch aus einem GET-Formular. Ein
POST-Formular schickt seine Felder in Reihenfolge samt gedrücktem Knopf,
form-codiert (`name=Fil+ius&c=1&s=y&t=%C3%BC%26%3D&ok=Gut`).
* Relative Links und Bilder zählen vom Wurzelordner des Servers: die Basis
des Dokuments ist der Bildzwischenspeicher, nicht die Seite (ein Bild in
`/ordner/drei.html` holt `bild.png` aus der Wurzel). Ein `#anker` lädt die
Startseite des Hosts.
* Bei einem Status außer 200 zeigt Java immer `sw_webbrowser_msg2`
(„Fehler“): `einlesenTextdatei` löst den schon aufgelösten Pfad der
Vorlage `tmpl/http_fehler_*` ein zweites Mal auf und findet sie nie. Ohne
Server: `sw_webbrowser_msg1`. Text und Bilder zeigt das Fenster als HTML.
* Der Webserver: „Starten“/„Anhalten“, das Kästchen schaltet die Tabelle
ein (ohne Datei). Jede geänderte Zelle schreibt `/www.conf/vhosts` mit
allen fünf Zeilen, leere als Leerzeilen; neu gelesen überspringt
`StringTokenizer` die Leerzeilen, und die Zelle einer unvollständigen
Zeile rückt in die davor. Liegt die Datei da, ist das Kästchen nach dem
nächsten Start wieder an, auch abgeschaltet.
+11 -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" "$here/src/WebRun.java" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java" "$here/src/TerminalRun.java" "$here/src/FileCases.java" "$here/src/GnutellaRun.java" "$here/src/NewDeviceRun.java" "$here/src/SheetRun.java" "$here/src/TerminalWindowRun.java" "$here/src/InstallRun.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" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java" "$here/src/TerminalRun.java" "$here/src/FileCases.java" "$here/src/GnutellaRun.java" "$here/src/NewDeviceRun.java" "$here/src/SheetRun.java" "$here/src/TerminalWindowRun.java" "$here/src/InstallRun.java" "$here/src/BrowserWindowRun.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.
@@ -368,6 +368,16 @@ rm -f "$output"
HOME="$here/.build/home" xvfb-run -a java -Duser.language=de -Duser.country=DE -cp "$classpath:$here/.build" TerminalWindowRun "$output" 2>&1 | grep -v 'logback\|^$' || true
test -s "$output"
# FILIUS' web browser and web server windows used as a person does — an
# address typed, a link clicked, a form's button pressed, the server's
# button, check box and table — on web-lan's network (BrowserWindowRun): the
# room's browser window and the server's (packages/sim/test/browserWindow.test.ts).
output="$root/packages/sim/test/browserWindow.java.json"
rm -f "$output"
HOME="$here/.build/home" xvfb-run -a java -Duser.language=de -Duser.country=DE -cp "$classpath:$here/.build" BrowserWindowRun \
"$root/packages/core/test/scenarios/web-lan.scenario.json" "$output" 2>&1 | grep -v 'logback\|^$\|^\s*at \|AWT-EventQueue' || true
test -s "$output"
# 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)
# and the classics of F3 (web, email, gnutella; packages/fls/test/classicsJava.test.ts).
+338
View File
@@ -0,0 +1,338 @@
// Uses FILIUS' own web browser and web server windows
// (GUIApplicationWebBrowserWindow, GUIApplicationWebServerWindow on their
// GUIDesktopPanels) on web-lan's network as a person does: an address typed
// into the browser's address line and Enter pressed, a link clicked (the
// event Swing's HTMLEditorKit makes of it: the href against the document's
// base), a form's button pressed (the real button Swing draws for it), and
// on the server's window the start button, the virtual hosts' check box and
// table. After each step it writes what the windows show: the browser's
// address line and title, the page its program handed the window; the
// server's button, check box, table and log, and the requests it got. The
// reference for the editor's browser and web server windows
// (packages/sim/test/browserWindow.test.ts).
//
// Usage: java -cp filius.jar:lib/*:classes BrowserWindowRun <scenario.json> <output json>
import filius.gui.anwendungssicht.GUIApplicationWebBrowserWindow;
import filius.gui.anwendungssicht.GUIApplicationWebServerWindow;
import filius.gui.anwendungssicht.GUIApplicationWindow;
import filius.gui.anwendungssicht.GUIDesktopPanel;
import filius.software.system.Betriebssystem;
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.awt.Component;
import java.awt.Container;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.html.HTMLDocument;
public class BrowserWindowRun {
static final String BROWSER = "00000000-0000-0000-0000-000000000201";
static final String SERVER = "00000000-0000-0000-0000-000000000203";
static final List<Object> steps = new ArrayList<>();
/** What the browser's program handed its window since the last step. */
static final List<String> pages = new ArrayList<>();
/** The requests the server got since the last step (its ">>" lines). */
static final List<String> requests = new ArrayList<>();
static Object field(Class<?> owner, Object object, String name) throws Exception {
Field field = owner.getDeclaredField(name);
field.setAccessible(true);
return field.get(object);
}
static String title(GUIApplicationWindow window) throws Exception {
return ((JLabel) field(GUIApplicationWindow.class, window, "titleLabel")).getText();
}
static void browserStep(GUIApplicationWebBrowserWindow window, String step) throws Exception {
Map<String, Object> result = new LinkedHashMap<>();
result.put("step", step);
result.put("address", ((JTextField) field(GUIApplicationWebBrowserWindow.class, window, "urlFeld")).getText());
result.put("title", title(window));
synchronized (pages) {
result.put("pages", new ArrayList<>(pages));
pages.clear();
}
synchronized (requests) {
result.put("requests", new ArrayList<>(requests));
requests.clear();
}
steps.add(result);
}
static void serverStep(GUIApplicationWebServerWindow window, WebServer server, Dateisystem files, String step) throws Exception {
Map<String, Object> result = new LinkedHashMap<>();
result.put("step", step);
result.put("title", title(window));
result.put("button", ((JButton) field(GUIApplicationWebServerWindow.class, window, "buttonStart")).getText());
result.put("virtualHosts", ((JCheckBox) field(GUIApplicationWebServerWindow.class, window, "showVHosts")).isSelected());
result.put("serverVirtualHosts", server.isUseVHost());
List<Object> rows = new ArrayList<>();
for (String[] row : server.getVHostArray()) rows.add(Arrays.asList(row[0], row[1]));
result.put("table", rows);
Datei vhosts = files.holeDatei("/www.conf/vhosts");
result.put("vhostsFile", vhosts == null ? null : vhosts.getDateiInhalt());
result.put("log", ((JTextArea) field(GUIApplicationWebServerWindow.class, window, "logArea")).getText());
steps.add(result);
}
static void type(GUIApplicationWebBrowserWindow window, String typed) throws Exception {
JTextField address = (JTextField) field(GUIApplicationWebBrowserWindow.class, window, "urlFeld");
SwingUtilities.invokeAndWait(() -> {
address.setText(typed);
KeyEvent event = new KeyEvent(address, KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0, KeyEvent.VK_ENTER, KeyEvent.CHAR_UNDEFINED);
for (KeyListener listener : address.getKeyListeners()) listener.keyPressed(event);
});
settle();
}
/** Waits until the browser's program handed its window a page and its images came (or 25 seconds passed: nothing loads). */
static void settle() throws Exception {
long until = System.currentTimeMillis() + 25000;
int known = -1;
long quiet = 0;
while (System.currentTimeMillis() < until) {
Thread.sleep(100);
int count;
boolean page;
synchronized (pages) {
count = pages.size();
page = pages.stream().anyMatch(each -> !each.equals("update"));
}
synchronized (requests) {
count += requests.size();
}
if (count != known) {
known = count;
quiet = System.currentTimeMillis();
} else if (page && System.currentTimeMillis() - quiet > 2500) {
return;
}
}
}
/** A click on a link: HTMLEditorKit.LinkController's event, the href against the document's base. */
static void follow(GUIApplicationWebBrowserWindow window, String href) throws Exception {
JEditorPane pane = (JEditorPane) field(GUIApplicationWebBrowserWindow.class, window, "anzeigeFeld");
SwingUtilities.invokeAndWait(() -> {
try {
URL base = ((HTMLDocument) pane.getDocument()).getBase();
HyperlinkEvent event = new HyperlinkEvent(pane, HyperlinkEvent.EventType.ACTIVATED, new URL(base, href), href);
for (HyperlinkListener listener : pane.getHyperlinkListeners()) listener.hyperlinkUpdate(event);
} catch (Exception error) {
throw new RuntimeException(error);
}
});
settle();
}
static void reload(GUIApplicationWebBrowserWindow window) throws Exception {
type(window, "www.filius.de/links.html");
synchronized (pages) {
pages.clear();
}
synchronized (requests) {
requests.clear();
}
}
static AbstractButton find(Container container, String text) {
for (Component component : container.getComponents()) {
if (component instanceof AbstractButton && text.equals(((AbstractButton) component).getText())) return (AbstractButton) component;
if (component instanceof Container) {
AbstractButton found = find((Container) component, text);
if (found != null) return found;
}
}
return null;
}
/** A press on the button Swing drew for a form's submit input. */
static void press(GUIApplicationWebBrowserWindow window, String label) throws Exception {
JEditorPane pane = (JEditorPane) field(GUIApplicationWebBrowserWindow.class, window, "anzeigeFeld");
SwingUtilities.invokeAndWait(() -> {
pane.revalidate();
pane.doLayout();
});
Thread.sleep(500);
AbstractButton[] button = new AbstractButton[1];
SwingUtilities.invokeAndWait(() -> button[0] = find(pane, label));
if (button[0] == null) throw new IllegalStateException("no button " + label);
SwingUtilities.invokeAndWait(() -> button[0].doClick());
settle();
}
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 {
try {
run(args);
} catch (Throwable error) {
error.printStackTrace();
System.exit(1);
}
System.exit(0);
}
static void run(String[] args) throws Exception {
ScenarioNetwork network = ScenarioNetwork.read(args[0]);
Betriebssystem serverSystem = (Betriebssystem) network.nodes.get(SERVER).getSystemSoftware();
Betriebssystem browserSystem = (Betriebssystem) network.nodes.get(BROWSER).getSystemSoftware();
serverSystem.installApp("filius.software.www.WebServer", false);
browserSystem.installApp("filius.software.www.WebBrowser", false);
WebServer server = (WebServer) serverSystem.holeSoftware("filius.software.www.WebServer");
WebBrowser browser = (WebBrowser) browserSystem.holeSoftware("filius.software.www.WebBrowser");
Dateisystem files = serverSystem.getDateisystem();
String image = files.holeDatei("/webserver/splashscreen-mini.png").getDateiInhalt();
file(files, "/webserver", "links.html", "html", "<html><head><title>Links &amp; mehr</title></head><body>\n"
+ "<a href=\"ziel.html\">rel</a> <a href=\"/text.txt\">abs</a> <a href=\"http://192.168.0.30/\">host</a>\n"
+ "<a href=\"ordner/drei.html\">folder</a> <a href=\"ziel.html?a=1&amp;b=2\">query</a> <a href=\"#oben\">anchor</a>\n"
+ "<form action=\"ziel.html\" method=\"get\"><input name=\"q\" value=\"a b\"><input type=\"submit\" name=\"los\" value=\"Los\"></form>\n"
+ "<form action=\"ziel.html\" method=\"post\"><input name=\"name\" value=\"Fil ius\"><input type=\"checkbox\" name=\"c\" value=\"1\" checked>"
+ "<input type=\"checkbox\" name=\"d\" value=\"1\"><select name=\"s\"><option>x</option><option selected value=\"y\">Y</option></select>"
+ "<textarea name=\"t\">&uuml;&amp;=</textarea><input type=\"submit\" name=\"ok\" value=\"Gut\"></form>\n"
+ "</body></html>");
file(files, "/webserver", "ziel.html", "html", "<html><head><title>Ziel</title></head><body>Ziel</body></html>");
file(files, "/webserver", "ohne.html", "html", "<html><body>Ohne Titel</body></html>");
file(files, "/webserver", "text.txt", "txt", "Nur Text\nzwei Zeilen");
file(files, "/webserver", "bild.png", "png", image);
files.erstelleVerzeichnis("/webserver", "ordner");
file(files, "/webserver/ordner", "drei.html", "html", "<html><head><title>Drei</title></head><body><a href=\"vier.html\">vier</a><img src=\"bild.png\"></body></html>");
file(files, "/webserver/ordner", "vier.html", "html", "<html><body>Vier im Ordner</body></html>");
file(files, "/webserver", "vier.html", "html", "<html><body>Vier oben</body></html>");
server.hinzuBeobachter((observable, data) -> {
String text = String.valueOf(data);
if (text.startsWith(">>")) synchronized (requests) { requests.add(text.substring(2)); }
});
browser.hinzuBeobachter((observable, data) -> {
synchronized (pages) {
if (data instanceof HTTPNachricht) {
HTTPNachricht page = (HTTPNachricht) data;
pages.add(page.getStatusCode() + " " + page.getContentType() + "\n" + page.getDaten());
} else {
pages.add("update");
}
}
});
network.start();
Thread.sleep(500);
JFrame frame = new JFrame();
GUIDesktopPanel serverDesktop = new GUIDesktopPanel(serverSystem);
GUIDesktopPanel browserDesktop = new GUIDesktopPanel(browserSystem);
frame.getContentPane().setLayout(new java.awt.FlowLayout());
frame.getContentPane().add(serverDesktop);
frame.getContentPane().add(browserDesktop);
GUIApplicationWebServerWindow serverWindow = (GUIApplicationWebServerWindow) serverDesktop.starteAnwendung("filius.software.www.WebServer");
GUIApplicationWebBrowserWindow window = (GUIApplicationWebBrowserWindow) browserDesktop.starteAnwendung("filius.software.www.WebBrowser");
SwingUtilities.invokeAndWait(() -> {
frame.pack();
frame.setVisible(true);
});
Thread.sleep(500);
serverStep(serverWindow, server, files, "server:open");
browserStep(window, "open");
type(window, "192.168.0.30");
browserStep(window, "type:192.168.0.30 (server off)");
// The links page loaded anew before each link and button, without a step of its own.
SwingUtilities.invokeAndWait(() -> {
try {
((JButton) field(GUIApplicationWebServerWindow.class, serverWindow, "buttonStart")).doClick();
} catch (Exception error) {
throw new RuntimeException(error);
}
});
Thread.sleep(500);
serverStep(serverWindow, server, files, "server:start");
for (String typed : new String[] { "192.168.0.30", "www.filius.de/ohne.html", "kein host!", "http://www.filius.de/nichts.html",
"leer.filius.de", "www.filius.de/text.txt", "www.filius.de/links.html?x=1", "www.filius.de/links.html" }) {
type(window, typed);
browserStep(window, "type:" + typed);
}
serverStep(serverWindow, server, files, "server:after pages");
for (String href : new String[] { "ziel.html", "/text.txt", "http://192.168.0.30/", "ordner/drei.html", "vier.html" }) {
follow(window, href);
browserStep(window, "follow:" + href);
reload(window);
}
for (String href : new String[] { "ziel.html?a=1&b=2", "#oben" }) {
follow(window, href);
browserStep(window, "follow:" + href);
reload(window);
}
press(window, "Los");
browserStep(window, "press:Los");
reload(window);
press(window, "Gut");
browserStep(window, "press:Gut");
// The virtual hosts: switched on, rows written as the table's editor does (changeVHostTable), a stop and a start.
JCheckBox box = (JCheckBox) field(GUIApplicationWebServerWindow.class, serverWindow, "showVHosts");
SwingUtilities.invokeAndWait(box::doClick);
Thread.sleep(300);
serverStep(serverWindow, server, files, "server:virtual hosts on");
String[][] cells = { { "0", "0", "seite.filius.de" }, { "0", "1", "vhost1" }, { "1", "0", "nurhost.filius.de" }, { "2", "1", "nurordner" },
{ "3", "0", "andere.filius.de" }, { "3", "1", "vhost2" } };
for (String[] cell : cells) server.changeVHostTable(Integer.parseInt(cell[0]), Integer.parseInt(cell[1]), cell[2]);
serverStep(serverWindow, server, files, "server:rows written");
serverSystem.beenden();
serverSystem.starten();
Thread.sleep(500);
serverStep(serverWindow, server, files, "server:restart");
SwingUtilities.invokeAndWait(box::doClick);
Thread.sleep(300);
serverStep(serverWindow, server, files, "server:virtual hosts off");
serverSystem.beenden();
serverSystem.starten();
Thread.sleep(500);
serverStep(serverWindow, server, files, "server:restart after off");
SwingUtilities.invokeAndWait(() -> {
try {
((JButton) field(GUIApplicationWebServerWindow.class, serverWindow, "buttonStart")).doClick();
} catch (Exception error) {
throw new RuntimeException(error);
}
});
Thread.sleep(1000);
serverStep(serverWindow, server, files, "server:stop");
Map<String, Object> output = new LinkedHashMap<>();
output.put("steps", steps);
try (FileOutputStream out = new FileOutputStream(args[1])) {
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));
}
network.stop();
System.out.println("browser window steps: " + steps.size());
}
}