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
@@ -113,6 +113,7 @@ import { EMPTY_TOPOLOGY_SNAPSHOT, withSnapshot, type NodeSnapshot, type Topology
import { RuntimeSpeed } from './speed.js';
import { ApplicationThreads, waitUntil, type Steps, type Wait } from './threads.js';
import { EchoServer, SimpleClient } from './apps/echo.js';
import { WebBrowser, WebServer } from './apps/web.js';
import {
PRODUCTION_TRACE_RETENTION,
type HeaderField,
@@ -154,6 +155,8 @@ export type ScheduledEventKind =
/** The lookups waiting for an address wake up, after what else happens at that time. */
| { readonly kind: 'arpAnswered'; readonly nodeId: Id; readonly ipAddress: string; readonly macAddress: string }
| { readonly kind: 'tcpTimeout'; readonly sessionId: Id }
/** A program asked its socket to close (requestTcpClose): the socket's thread does it. */
| { readonly kind: 'tcpClose'; readonly socketId: Id }
/** A program's thread waited until this time (threads.ts). */
| { readonly kind: 'applicationWake'; readonly wakeId: number }
/** A DNS query waited five round trips for its answer (DNSQueryAgent). */
@@ -883,6 +886,8 @@ export class NetworkRuntimeEngine {
/** The programs, by node (initializePrograms). */
private readonly echoServersByNodeId = new Map<Id, EchoServer>();
private readonly simpleClientsByNodeId = new Map<Id, SimpleClient>();
private readonly webServersByNodeId = new Map<Id, WebServer>();
private readonly webBrowsersByNodeId = new Map<Id, WebBrowser>();
/** Accepted connections a program has taken (acceptTcpSteps). */
private readonly handedOutTcpSocketIds = new Set<Id>();
private nextApplicationWakeId = 1;
@@ -997,6 +1002,8 @@ export class NetworkRuntimeEngine {
this.handedOutTcpSocketIds.clear();
this.echoServersByNodeId.clear();
this.simpleClientsByNodeId.clear();
this.webServersByNodeId.clear();
this.webBrowsersByNodeId.clear();
this.arpLookups = [];
this.protocolThreads.clear();
this.dnsQueriesBySocketId.clear();
@@ -1051,6 +1058,16 @@ export class NetworkRuntimeEngine {
return this.simpleClientsByNodeId.get(nodeId);
}
/** The node's web server while the simulation runs, if it has one installed. */
webServer(nodeId: Id): WebServer | undefined {
return this.webServersByNodeId.get(nodeId);
}
/** The node's web browser while the simulation runs, if it has one installed. */
webBrowser(nodeId: Id): WebBrowser | undefined {
return this.webBrowsersByNodeId.get(nodeId);
}
/**
* Each node's programs start with the simulation (Betriebssystem.starten),
* from the snapshot; what they show starts empty.
@@ -1059,11 +1076,18 @@ export class NetworkRuntimeEngine {
const snapshot = this.stateValue.topologySnapshot;
this.echoServersByNodeId.clear();
this.simpleClientsByNodeId.clear();
this.webServersByNodeId.clear();
this.webBrowsersByNodeId.clear();
for (const node of snapshot.nodes) {
const echoServer = snapshot.echoServersByNodeId.get(node.id);
if (echoServer !== undefined) this.echoServersByNodeId.set(node.id, new EchoServer(this, node.id, echoServer));
const simpleClient = snapshot.simpleClientsByNodeId.get(node.id);
if (simpleClient !== undefined) this.simpleClientsByNodeId.set(node.id, new SimpleClient(this, node.id, simpleClient));
const webServer = snapshot.webServersByNodeId.get(node.id);
if (webServer !== undefined) {
this.webServersByNodeId.set(node.id, new WebServer(this, node.id, webServer, snapshot.fileSystemsByNodeId.get(node.id)));
}
if (snapshot.webBrowserNodeIds.has(node.id)) this.webBrowsersByNodeId.set(node.id, new WebBrowser(this, node.id));
}
}
@@ -1261,6 +1285,9 @@ export class NetworkRuntimeEngine {
case 'tcpTimeout':
this.handleTcpTimeout(kind.sessionId);
return;
case 'tcpClose':
this.closeTcpSocket(kind.socketId);
return;
case 'applicationWake': {
const wake = this.applicationWakes.get(kind.wakeId);
this.applicationWakes.delete(kind.wakeId);
@@ -2089,6 +2116,16 @@ export class NetworkRuntimeEngine {
}
}
/**
* TCPSocket.schliessen from a program: it only asks; the socket's own
* thread leaves its loop and closes (sends the FIN) after what the
* program does next at that time.
*/
requestTcpClose(socketId: Id): void {
if (this.stateValue.phase !== 'running') return;
this.schedule(this.stateValue.currentTimeMilliseconds, { kind: 'tcpClose', socketId });
}
/**
* Java's close: the FIN goes once, never again; the first wait counts from
* handing it down (the timeout transmitTcpSegment schedules).