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
+2 -15
View File
@@ -74,7 +74,7 @@
// Java FILIUS to the topology of the original up to those departures.
import {
base64Encode,
javaBase64,
defaultSwitchConfiguration,
dhcpServerConfiguration,
forwardsIPv4ByDefault,
@@ -534,20 +534,7 @@ function fileTrees(files: VirtualFileSystem, directory: string): FlsFileTree[] {
});
}
/**
* FILIUS's Base64.encodeBytes (iHarder Base64 2.x): a line break after every
* 76 characters — also after the last full line — then the padded rest.
*/
export function javaBase64(bytes: Uint8Array): string {
const full = bytes.length - (bytes.length % 3);
let text = '';
for (let offset = 0; offset < full; offset += 57) {
const end = Math.min(offset + 57, full);
text += base64Encode(bytes.subarray(offset, end));
if (end - offset === 57) text += '\n';
}
return text + base64Encode(bytes.subarray(full));
}
export { javaBase64 };
/** "image/png" → "png", "image/jpeg" → "jpg", as Java names them. */
function subtype(mediaType: string): string {
+12 -3
View File
@@ -5,10 +5,11 @@
// Only the cables count: a host's SSID connects nothing at run time, as in
// Java (Swift joins a wireless host to the switch of its SSID; not taken).
// A DNS server runs only when it is switched on (Java: DNSServer.aktiv); an
// echo server is there either way, switched on or off (it can be switched at
// run time).
// echo or web server is there either way, switched on or off (it can be
// switched at run time). The programs read the nodes' files (a web server its
// pages).
import { portSnapshot, topologySnapshot, type DnsServerConfiguration, type EchoServerSnapshot, type Id, type TopologySnapshot } from '@mdg-filius/core';
import { portSnapshot, topologySnapshot, type DnsServerConfiguration, type EchoServerSnapshot, type Id, type WebServerSnapshot, type TopologySnapshot } from '@mdg-filius/core';
import type { ImportedProject } from './importProject.js';
export function projectSnapshot(project: ImportedProject): TopologySnapshot {
@@ -20,6 +21,11 @@ export function projectSnapshot(project: ImportedProject): TopologySnapshot {
for (const [nodeId, configuration] of project.echoServerConfigurations) {
echoServers.set(nodeId, { ...configuration, isActive: project.activePrograms.get(nodeId)?.has('echoServer') === true });
}
const webServers = new Map<Id, WebServerSnapshot>();
for (const [nodeId, configuration] of project.webServerConfigurations) {
webServers.set(nodeId, { ...configuration, isActive: project.activePrograms.get(nodeId)?.has('webServer') === true });
}
const webBrowsers = new Set([...project.installedPrograms].filter(([, programs]) => programs.has('webBrowser')).map(([nodeId]) => nodeId));
return topologySnapshot({
nodes: project.graph.nodes.map((node) => ({
id: node.id,
@@ -40,5 +46,8 @@ export function projectSnapshot(project: ImportedProject): TopologySnapshot {
ipForwardingByNodeId: project.ipForwardingByNodeId,
echoServersByNodeId: echoServers,
simpleClientsByNodeId: project.simpleClientConfigurations,
webServersByNodeId: webServers,
webBrowserNodeIds: webBrowsers,
fileSystemsByNodeId: project.virtualFileSystems,
});
}
+24
View File
@@ -749,6 +749,30 @@ describe('importProject rules', () => {
expect([...snapshot.simpleClientsByNodeId]).toEqual([[off, { targetAddress: '10.0.0.1', targetPort: 7 }]]);
});
it('runs every web server, switched on or off, every web browser, on the nodes\' files (projectSnapshot)', () => {
const source = flsNode('Rechner').source;
const webServer = (isActive: boolean): FlsApplication => ({ key: 'web', javaClass: 'filius.software.www.WebServer', source, isActive, port: 80 });
const browser: FlsApplication = { key: 'browser', javaClass: 'filius.software.www.WebBrowser', source };
const project = importProject(
topology({
nodes: [
flsNode('Rechner', { interfaces: [nic('10.0.0.1')], system: { applications: [webServer(true)] } }),
flsNode('Rechner', { interfaces: [nic('10.0.0.2')], system: { applications: [webServer(false), browser] } }),
],
}),
counter(),
);
const [on, off] = project.graph.nodes.map((node) => node.id);
const snapshot = projectSnapshot(project);
expect([...snapshot.webServersByNodeId]).toEqual([
[on, { port: 80, documentRoot: '/webserver', virtualHosts: false, isActive: true }],
[off, { port: 80, documentRoot: '/webserver', virtualHosts: false, isActive: false }],
]);
expect([...snapshot.webBrowserNodeIds]).toEqual([off]);
expect(snapshot.fileSystemsByNodeId).toBe(project.virtualFileSystems);
});
it("keeps a node's IP forwarding where it differs from its kind's: a host that forwards, a router that does not", () => {
const system = (ipForwardingEnabled: boolean) => ({ manualRoutes: [], ipForwardingEnabled });
const project = importProject(