Web Client Setup¶
- Web Client Setup
This chapter documents how to configure and setup the FWD Server to allow access to the FWD Clients via a web browser (a different client than the Swing Applet). The Web Clients were implemented to allow access to a ChUI/GUI FWD Client running remotely on another machine (note that at the time of this writing only ChUI clients - using the Console Driver - can be accessed via a browser). Once all setup is performed, when accessing the remote FWD Client via the web browser, the user will have the same experience as if the user has connected to the remote machine and started the FWD Client there, manually: this means that the user will have the same OS security permissions and will be allowed to start processes, read files, access a shell to that machine, etc. But, an inherent limitation for the web clients is that they can start only processes which remain active in the same console window as the application client.
TBD: Add discussion for Spawner Setup and Configuration, Remote Launchers and Reverse Proxy.
The "Remote Launchers" and "Reverse Proxy" topics referenced above are now documented in the "Remote Web Client Spawning via Brokers":#remote-brokers section below.
System Requirements¶
The FWD Web Client should be able to run on any web browser which supports HTML5, Web Sockets and AJAX requests. At the time of this writing, the web client is expected to work properly with the latest versions of the Google Chrome, Mozilla Firefox, Internet Explorer and Safari browsers; from these, testing showed that the most reliable (in terms of performance and stability) is Google Chrome.
Considering how the dedicated web server (ran by each remote FWD Client, see the Under the Hood section, for more details) computes its dedicated port, it is best for the machine on which the remote FWD Client is launched to be directly accessible from the machine used by the user to start the Web Client (i.e. no intermediate NAT-enabled router is required to access the FWD application server). If this is not set then the embedded web server (upon which the web client depends) will not be started at server startup. No error messages will be written to the log. The core idea is that there is no support for insecure web protocols.
The last requirement is that an user allowed to connect to the FWD application server via a web browser needs to have access to valid OS credentials on the remote machine on which the remote FWD Client is started (which at the time of this writing is the same machine on which the FWD application server is running). Without valid credentials, the user will not be allowed to pass the initial login page.
Setup¶
TBD: Needs updates to document the full range of current configuration and options.
To access web clients, it is required for the FWD server to have the spawner tool configured properly - as the spawner is not used only for launching web clients, details about its setup can be found in the Application Server Installation chapter of this book. This chapter assumes the client spawner tool is setup properly.
In addition to the spawner, the FWD server itself must be configured with a secure port. This is defined in the bootstrap configuration or is passed as a bootstrap configuration override on the server driver command line. The values that must be set include net:connection:secure=true and net:server:secure_port=<port>. For more details on this, please see Part 2: Bootstrap Configuration.
The remaining configuration is all related to the FWD server directory, which instructs how the terminal will be displayed in the web browser. The directory node is named chuiWeb and can reside either in /server/default/ or the /server/<server-id>/ section, with the later having priority over the default configuration; also, if a chuiWeb/<node>/ node is found in both per-server and default configuration, the per-server value will be used. The following table describe all the possible configurations (all nodes are children of the chuiWeb/ node), their default values and meaning.
| chuiWeb/ Node | Type | Default Value | Mandatory | Description |
|---|---|---|---|---|
enabled |
boolean |
false |
yes | Flag identifying if this application server is configured for launching web clients. |
port |
integer |
0 |
no | An explicit port to start the FWD Client's embedded web server, or 0 to let the OS assign one. This applies when no ports range is configured: a non-zero value is used as the explicit embedded web server port, while a value of 0 means the new web client's port is assigned by the OS. |
fontsize |
integer |
12 |
no | The font's point size used for all text. |
rows |
integer |
24 |
no | The number of rows of text on the terminal screen. |
columns |
integer |
80 |
no | The number of columns of text on the terminal screen. |
webSocketTimeout |
integer |
-1 |
no | Web socket timeout (in milliseconds). This is a timeout after which the web socket is closed. |
watchdogTimeout |
integer |
-1 |
no | Watchdog timeout (in milliseconds). After the web socket is closed, the watchdog will wait at least that certain amount of time and, if the web socket was connected again, it will decide to terminate the FWD client, as the remote user is no longer connected to it. A value of -1 will mean the remote FWD client will be terminated as soon as the web socket is closed. |
Considering the table above, the minimal chuiWeb/ node looks like:
<node class="container" name="chuiWeb">
<node class="boolean" name="enabled">
<node-attribute name="value" value="TRUE"/>
</node>
</node>
while the full node looks like:
<node class="container" name="chuiWeb">
<node class="boolean" name="enabled">
<node-attribute name="value" value="TRUE" />
</node>
<node class="string" name="background">
<node-attribute name="value" value="0x000000" />
</node>
<node class="string" name="foreground">
<node-attribute name="value" value="0xFFA500" />
</node>
<node class="string" name="selection">
<node-attribute name="value" value="0x0000FF" />
</node>
<node class="string" name="fontname">
<node-attribute name="value" value="monospaced" />
</node>
<node class="integer" name="fontsize">
<node-attribute name="value" value="12" />
</node>
<node class="integer" name="rows">
<node-attribute name="value" value="24" />
</node>
<node class="integer" name="columns">
<node-attribute name="value" value="80" />
</node>
<node class="integer" name="webSocketTimeout">
<node-attribute name="value" value="-1" />
</node>
<node class="integer" name="watchdogTimeout">
<node-attribute name="value" value="300000" />
</node>
<node class="integer" name="port">
<node-attribute name="value" value="9443" />
</node>
<node class="string" name="host">
<node-attribute name="value" value="localhost" />
</node>
</node>
Under The Hood¶
This section documents the implementation details of the web client.
Web Client Modes¶
The following shows the different modes of the web client.
TBD: More discussion, include ChUI as well.

Spawning a Web Client¶
TBD: Add descriptions for both embedded mode forms. Add spawner flow and description for ChUI.

This description is out of date. It needs to be updated to match the above diagram.
This section describes how the FWD Server and the remote FWD Client react, when the user accesses a Web Client:
- The process with the user opening a browser session with the FWD server (running an embedded Jetty instance on the same port as the administration console). The URL will look like
https://localhost:<port>/chui, where<port>is the port configured in theadminPortdirectory configuration, in the server section (default or per-server). Depending on your browser configuration and how the server's SSL certificate was generated, the browser might show you an “untrusted connection” message and ask you to accept the certificate before continuing, on the first connection to this host and port. - The FWD server sends down a login page, which looks like:

- The user inputs their operating system level user credentials (e.g. Linux userid and password) and submits the form.
- Using the spawner tool (see the Application Server Installation chapter for how to install the spawner), the FWD server thus launches a new FWD client (as a child process) and part of that "spawn" processing is an OS-level login. If the given credentials are authentic/correct, the child process is not only launched, but it has the user's context at the OS level. In other words, the process is run under the user's id and has full access to that user's files/configuration/rights.
- The FWD client has its own embedded web server. After successfully launching and initializing, it provides URL information back to the FWD server to allow a connection to the FWD client's web server.
- The FWD server sends a redirect response back to the user's initial browser session. This also includes an authorization token that allows the FWD client to ensure that only that browser session may access that web server.
- The user's browser session redirects to the FWD client's web server. This web server is ran using the server's certificate, so the browser might show you an “untrusted connection” message and ask you to accept the certificate before continuing, on the first connection to this host and port. And if the port is randomly assigned, this will mean each time a new port is reached, the “untrusted connection” message will be displayed. If you are encountering a
HTTP ERROR: 403 - Forbiddenpage after the redirect, note at the time of this writing there is a limitation for web clients: thechuiWeb/hostnode needs to be configured so that it mentions the server's host used to access the web client. - The FWD client's web server sends back the "main" page which is mostly Javascript.
- The loaded page establishes a bidirectional websocket to the FWD client.
- At this point the ChUI driver in the FWD client and the javascript code in the browser will communicate freely. The core idea here is that the page is not reloaded or refreshed, but instead the javascript code just handles the rendering of what looks like a terminal and it handles the forwarding of user input (keys in a ChUI environment).
- The FWD client processes as it normally would except that the ChUI driver delegates the final aspects of the UI to the browser side. Note that, depending on how the FWD application server is configured, the user might be required to enter distinct credentials, to access the application server (which may be different than the OS credentials, which are required to remotely launch the FWD Client).
- If the user exits the application normally or if it gets automatically disconnected (i.e. due to server shutdown), the browser will be redirected back to the initial login page.


Security¶
TBD: HTTPS only, how our certs work, spawner security, OS accounts for spawning (UNIX/Linux suid bit, Windows OS user auth), session redirect during auth/spawning, one-time auth token and web socket shift, all web resources are from server's jetty (initial auth) or client's jetty (actual web client usage)...
Remote Web Client Spawning via Brokers¶
Status: Draft for review — author: investigation of FWD com.goldencode.p2j.main broker / web-client subsystem.
Scope: How the FWD server, with the help of a broker client, spawns remote web (browser) GUI clients.
Reference configuration: hotel_gui deployment — server deploy/server/directory.xml, broker client deploy/client/broker1_client.xml.
NOTE: The diagrams in this section are provided as PNG images (rendered from Mermaid sources). The PNG files and their .mmd sources live in the images/ folder; attach the PNGs to this wiki page so the !...! image references below resolve.
1. Overview¶
The FWD "broker" mechanism lets the FWD server delegate the launching of per-user web client JVMs to one or more broker hosts. A broker is itself an FWD client process started in a special broker mode that does not run any application UI; instead it logs into the server, registers as a remote launcher, and then waits for spawn requests. When a browser requests the web GUI, the server picks a registered broker, asks it (over an authenticated RPC connection) to start a new web client JVM, and the spawned web client then serves the browser its own embedded HTML5/Canvas UI.
Four distinct runtime actors are involved:
| Actor | Process / class | Config file | Role |
|---|---|---|---|
| FWD Server | standard server (ServerDriver); hosts BrokerManager, WebClientsManager, WebDriverHandler / VirtualDesktopWebHandler |
deploy/server/directory.xml |
Directory/auth authority; serves the login landing page; selects a broker; allocates the client port; orchestrates the spawn. |
| FWD Client (Broker Client) | BrokerCore in broker mode (<mode broker="true"/>) |
deploy/client/broker1_client.xml |
Authenticates to the server as the broker process account; registers; receives spawn RPCs; launches web client JVMs on its host via the native spawn binary. |
| FWD Spawned Web Client | a fresh FWD client JVM (GuiWebDriver / ChuiWebDriver + embedded Jetty) launched by the broker |
runtime options injected by the broker/server | Runs the actual 4GL session for one user; exposes an embedded web server that the browser connects to. |
| FWD Browser Web Client | the end user's browser (p2j.js on HTML5 Canvas) |
— | Loads the landing page from the server, POSTs credentials, is redirected to the spawned web client's embedded server, then runs over a WebSocket. |
1.1 Deployment / component view¶

2. Reference configuration¶
The two configuration files encode the identity, trust, and resource parameters that the whole mechanism depends on.
2.1 Server: directory.xml¶
The server side contributes four pieces of configuration that matter to the broker flow.
- The broker process account* —
accounts/processes/broker1_process:
<node class="process" name="broker1_process"> <node-attribute name="enabled" value="TRUE"/> <node-attribute name="description" value="broker1_process"/> <node-attribute name="server" value="FALSE"/> <node-attribute name="alias" value="broker1"/> </node>
This is the FWD process identity the broker client authenticates as. Its certificate alias is broker1.
- The broker's certificate and private key* — under
certificates:
certificates/peers/broker1— the X.509 certificate (CNbroker1 account) the server trusts as a peer.certificates/private-keys/broker1— the key entry + key password.
These correspond, on the client side, to the broker1-private-key.store keystore.
- Authorization* — two permission entries grant the broker its rights:
<!-- broker1_process may read the accounts container -->
<node class="container" name="000510">
<node class="strings" name="subjects">
<node-attribute name="values" value="broker1_process"/>
</node>
<node class="resource" name="resource-instance">
<node-attribute name="reference" value="accounts"/>
</node>
</node>
<!-- the broker identity 'broker1' is a trusted spawner that may spawn OS-user 'fwd' processes -->
<node class="container" name="trustedspawner">
<node class="container" name="000100">
<node class="resource" name="resource-instance">
<node-attribute name="reference" value="fwd"/>
</node>
<node class="trustedSpawnerRights" name="rights">
<node-attribute name="allow" value="true"/>
</node>
<node class="strings" name="subjects">
<node-attribute name="values" value="broker1"/>
</node>
</node>
</node>
The trustedspawner entry is the key authorization that allows the broker (broker1) to launch processes that run as OS user fwd. It is enforced via TrustedSpawnerResource / TrustedSpawnerRights.
- The broker definition* —
server/default/brokers/broker1:
<node class="container" name="brokers">
<node class="broker" name="broker1">
<node-attribute name="account" value="fwd"/>
<node-attribute name="broker_account" value="broker1_process"/>
<node-attribute name="host" value="192.168.100.28"/>
<node class="container" name="portsRange">
<node class="string" name="namePrefix"><node-attribute name="value" value="client"/></node>
<node class="integer" name="from"><node-attribute name="value" value="7449"/></node>
<node class="integer" name="to"><node-attribute name="value" value="7459"/></node>
</node>
</node>
</node>
This is parsed by BrokerDiscovery.readBrokerConfigs() into Broker objects.
| directory.xml attribute | Broker field (BrokerDiscovery.getBroker()) |
Meaning |
|---|---|---|
broker_account = broker1_process |
brokerAccount |
The process account the broker must authenticate as. Checked at registration. |
account = fwd |
accounts (List) |
The FWD/OS user(s) this broker serves. Used to choose a broker for a given user. |
host = 192.168.100.28 |
host |
The broker host; also the host the browser will ultimately reach. |
portsRange/from = 7449 |
from |
First TCP port for spawned web clients on this host. |
portsRange/to = 7459 |
to |
Last TCP port. portsRestricted = (to > from) && (from > 0). |
2.2 Broker client: broker1_client.xml¶
<node type="client">
<client><mode broker="true" /></client> <!-- BROKER MODE -->
<net>
<server host="192.168.100.28" />
<server port="3433" />
<server secure_port="3333" />
<connection secure="true" />
</net>
<security>
<certificate validate="true" />
<truststore filename="../server/srv-certs.store" />
<truststore alias="standard" />
<keystore filename="../server/broker1-private-key.store" />
<keystore processalias="broker1" /> <!-- authenticate as broker1 -->
<authentication type="program"/> <!-- certificate/program auth -->
</security>
<broker>
<retry count="10" />
<retry seconds="10" />
</broker>
<client>
<spawner value="/home/fwd/opt/hotel_gui/spawn" /> <!-- native spawn binary -->
<jvmArgs value="-Xmx1G ... -Xrunjdwp:...address=9988..." />
<classpath value=".../lib/p2j.jar" />
<minAgentPort value="9988" />
<maxAgentPort value="9999" />
<web host="192.168.100.28"/> <!-- dedicatedHost advertised -->
<web dedicatedMode="false"/> <!-- network-accessible clients -->
<web defaultOsUser="fwd"/> <!-- OS user spawned clients run as -->
</client>
</node>
This is read by BrokerCore.initialize(BootstrapConfig).
| broker1_client.xml | ConfigItem / BrokerCore field |
Role |
|---|---|---|
mode broker="true" |
broker mode | Run as a remote launcher, not a UI client. |
net/server/host, secure_port |
host, port |
Server endpoint (TLS, 3333). |
security/keystore processalias="broker1" |
keystore alias | Authenticate as process broker1_process. |
authentication type="program" |
program auth | Certificate-based process authentication. |
broker/retry count/seconds |
count / seconds |
Reconnect retry loop (10× every 10 s). |
client/spawner |
spawner |
Path to the native spawn binary. |
client/jvmArgs, classpath |
jvmargs, classpath |
JVM args + classpath used for spawned clients. |
client/minAgentPort/maxAgentPort |
minAgentPort/maxAgentPort (9988–9999) |
Agent (debug/JMX) port range on the broker host. |
client/web/host |
dedicatedHost |
Host advertised to the browser for the spawned client. |
client/web/dedicatedMode = false |
dedicatedMode |
false → clients are reachable over the network at host (not forced to localhost). |
client/web/defaultOsUser = fwd |
associatedUser |
OS user the spawned client JVM runs as. |
2.3 Identity & trust chain (how the two files line up)¶

3. Broker startup & registration¶
When the broker client process starts (BrokerCore.start(cfg)):
initialize(cfg)readsbroker1_client.xml(section 2.2) and marks the process as a client (cfg.setServer(false)).SecurityManager.createInstance(cfg)loads thebroker1keystore /srv-certs.storetruststore.- A retry loop (
count×seconds) callsconnect(sessMgr, cfg):sessMgr.connectDirect(...)opens a secure session to the server (TLS, port 3333). The TLS handshake presents thebroker1certificate; the server authenticates the session as process accountbroker1_process(program authentication).- The broker obtains the server-exported
BrokerServerServicesproxy and callsregisterBroker(...), passingdedicatedHost(web host),associatedUser(defaultOsUser),dedicatedMode, the agent port range (minAgentPort/maxAgentPort), and debugger/JMX hints.
- On the server,
BrokerManager.registerBroker(...):- Looks up the matching
brokers/broker1entry by resolved host address (see section 5.2) and verifies the authenticated account equalsbroker_account(broker1_process). A mismatch throws and the registration is rejected. - Builds a
BrokerParametersrecord (randomuuid, authenticateduserId,agentAddress= host,associatedUser,dedicatedMode, aBrokerClientServicesremote proxy back to the broker, andstate = OPEN). - Registers the host with
WebClientsManager.createResourceQueue(...), which pre-fills aBlockingQueue<WebAllocatedResources>with one entry per port in7449..7459(plus the agent debug/JMX ports). The host index = brokerindex + 1(localhost is 1).
- Looks up the matching
registerBrokerreturns theuuid; the broker keeps the session open and waits for spawn RPCs.

4. Spawning a web client for a browser¶
4.1 End-to-end sequence¶
- Landing page — the browser does
GET /gui(or/chui).VirtualDesktopWebHandler.handle()→serveLandingPage()returns the login HTML. - Login POST — the browser POSTs credentials.
WebDriverHandler.handleStartClientRequest()extractsWebDriverRequestParameters(user, password, theme, device id, and any reverse-proxyX-Forwarded-Host/-Proto) and buildsWebDriverSpawnParameters(driver type, SSO flag, options), then callsspawnWorker(...). - Resource allocation —
WebClientsManager.allocateClient(proxyConfigs, host, uuid, osUser)polls oneWebAllocatedResources(a free port, e.g.7449, plus agent ports) from the broker host's pre-filled queue, tests that the port is actually free, and records theuuid → resourcesmapping. - Broker selection & spawn RPC —
spawnWorkerinvokesWebClientSpawner.spawn(...), which (because this is a remote/broker spawn) routes throughBrokerManager.spawn(clientBuilder, dedicatedUser, brokers, args, allocator, requestParameters, uuid):schedule(...)picks a registered broker for the user — honoringaccount=fwd/ dedicated-host constraints and choosing the lowestgetSystemLoading().updateWebClientOptions()injects the allocated--port=7449(and reverse-proxy host/proto/web-root if present) into the command.- The server calls
broker.getRemote().spawn(BrokerSpawnParameters)over the broker's RPC session.BrokerSpawnParameterscarries the command line + environment; trusted-vs-password mode decides whether a one-time password is streamed over stdin.
- Process launch on the broker —
BrokerCore.spawn(args)(running on the broker host):- Resolves command placeholders against the broker's local config —
PARAM_SPAWNER→spawnerpath,PARAM_JVM→jvmArgs,PARAM_CP→classpath,PARAM_HOST/PARAM_PORT→ server endpoint,PARAM_REMOTE_AGENT_HOST→dedicatedHost,PARAM_DEDICATED_MODE→dedicatedMode. - Runs the native
spawnbinary viaProcessBuilder(as OS userfwd, per the trusted-spawner grant), optionally writing the one-time password to stdin, and waits up to the launch timeout.
- Resolves command placeholders against the broker's local config —
- Spawned web client comes up — the new JVM authenticates to the server with its temporary credentials and runs
WebClientSpawner.TemporaryClientTask.doWork():- Imports the server key store into its config, creates a
GuiWebDriver(orChuiWebDriver), and callsEmbeddedWebServer.startupServer(...)which starts an embedded Jetty server onhost:port(e.g.192.168.100.28:7449). If a reverse proxy is configured, the URI is rewritten usingX-Forwarded-Host/-Protoand the proxy web-root. - It calls back to the server via RPC
spawner.clientIsReady(uuid, uri). On the server,WebClientSpawner.clientIsReady()stores this asremoteUri.
- Imports the server key store into its config, creates a
- Session registration — when the spawned client's P2J session attaches,
WebClientsManager.registerWebClientSession(webClientId, peerNode)cancels the cleanup timer and mapspeerNode → WebAllocatedResources, so the port can be released when the session ends (freeClient()returns it to the queue). - Redirect to the browser — back in
spawnWorker, afterexitCode == 0,result.uri = spawner.getRemoteUri()appends the authorization token (index.html?token=...). The server returns this URL to the browser (as a JSON/body redirect). - Browser connects to the spawned client — the browser navigates to the spawned client's embedded server URI (
192.168.100.28:7449, directly or via the reverse proxy) and opens the WebSocket; from then on the canvas UI is driven by the spawned web client. BecausededicatedMode=false, the advertised host is the real192.168.100.28rather thanlocalhost, so a remote browser can reach it.
4.2 Spawn sequence diagram¶

5. Multi-broker deployments¶
A single FWD server can drive several brokers — for horizontal scale-out (spawning web clients across many hosts) and/or to dedicate certain hosts to certain users. The reference config has only broker1, but the mechanism generalises.
5.1 Declaring more than one broker¶
Add additional <broker> nodes under server/default/brokers in directory.xml, each with its own broker_account, certificate/private-key, host, and portsRange. Each broker host runs its own broker-client process with its own broker*_client.xml (its own keystore / processalias). For example a second broker:
<node class="container" name="brokers">
<node class="broker" name="broker1">
<node-attribute name="broker_account" value="broker1_process"/>
<node-attribute name="account" value="fwd"/>
<node-attribute name="host" value="192.168.100.28"/>
<node class="container" name="portsRange"> ... 7449..7459 ... </node>
</node>
<node class="broker" name="broker2">
<node-attribute name="broker_account" value="broker2_process"/>
<!-- no 'account' attribute => GENERIC broker (serves any user) -->
<node-attribute name="host" value="192.168.100.29"/>
<node class="container" name="portsRange"> ... </node>
</node>
</node>
Each broker needs its matching process account, peers/private-keys certificate entries, and (if it must spawn under a given OS user) a trustedspawner grant — exactly as documented for broker1 in section 2.1.
5.2 Host indexing and ordering¶
At registration each broker host is added to WebClientsManager with index broker.getIndex() + 1, where getIndex() is the broker's 1-based position in the brokers container (so localhost is index 1, the first declared broker is index 2, and so on). BrokerManager also guards against two brokers resolving to the same host. The web-client host index used by the browser side is therefore derived from this broker declaration order, not from any external host list.
IMPORTANT: A <broker> node must never be configured with host set to the FWD server's own loopback address (localhost or 127.0.0.1). Host index 1 is permanently reserved for the FWD server's loopback host. A broker declared on the loopback would be folded into that reserved index-1 slot at runtime, while the reverse-proxy map generator (ClientsToPortsGenerator) would still assign it its declaration-order index (2, 3, …); the generated client-<index>-<port> proxy names would then not match the runtime web-root, and that broker's clients could not be routed through the reverse proxy. Each broker must be declared on a distinct, non-loopback host.
Host matching is performed on resolved addresses, not on the literal strings. When the server reads the brokers container at startup it resolves each broker's host attribute to its canonical address set (every address returned for the name), and at registerBroker it resolves the registering broker's reported dedicatedHost (client/web/host) the same way; a broker is matched when those address sets intersect (share at least one resolved address). Both resolutions are performed on the same FWD server host, so a given machine resolves to the same addresses on both sides regardless of how it is named — a literal host name, FQDN, short name, or IP all resolve consistently. The host attribute in directory.xml and the broker's own client/web/host therefore need not be written as the identical string; they only need to name the same machine as seen from the server. (This assumes the server's name resolution is stable between startup and broker registration, which holds because both run on that one server host.)
WARNING: The order of <broker> nodes in directory.xml is significant for host indexing. Keep it stable across deployments; reordering brokers shifts their indices.
5.3 Selecting a broker for a spawn request¶
Selection is two-stage — first a candidate set by account, then load-based scheduling within it.
- Candidate set by account —
BrokerManager.getBrokers(osUser)→getBrokersForUser(user):- A broker whose
accountlist contains the user (broker.hasAccount(user)) is an account-specific broker for that user. - A broker with no
accountattribute (broker.noAccounts()) is a generic broker. - The server first tries account-specific brokers for the user; if none are registered/OPEN it falls back to generic brokers. (Account-specific spawns are attempted once; generic spawns may be retried across brokers.)
- Only brokers currently in
BrokerState.OPENare considered.
- A broker whose
- Load-based scheduling —
BrokerManager.schedule(brokers, dedicatedUser, clientAddress):- Validates the requesting client's IP (
InetAddress.getByName). - Computes the user's dedicated host set via
getHostsForUser(dedicatedUser, ...). If the user is pinned to specific hosts, only brokers on those hosts are eligible; if the client's own address is among them it is preferred; otherwise any OPEN broker host is eligible (anyHost). - Among eligible brokers it picks the one reporting the lowest
getRemote().getSystemLoading()(a live RPC to each broker). Throws if none are available.
- Validates the requesting client's IP (

6. Reverse-proxy deployments¶
The browser ultimately connects to a spawned web client's embedded server (e.g. 192.168.100.28:7449). In production that host:port is usually fronted by a reverse proxy (nginx, Apache, a load balancer) so that browsers see a single public hostname over standard TLS, and the internal 7449..7459 ports are never exposed directly.
6.1 How the proxy headers flow through the spawn¶
- The browser hits the proxy, which forwards the login request to the FWD server adding
X-Forwarded-HostandX-Forwarded-Protoheaders. WebDriverHandlerextracts these intoWebDriverRequestParameters(forwardedHost,forwardedProto, plusexternalUserIp) and passes them toWebClientSpawner.spawn(webClientsManager, {forwardedHost, forwardedProto, externalUserIp}).BrokerManager.updateWebClientOptions()injects the proxy values (PROXY_HOST,PROXY_PROTOCOL,WEB_ROOT) into the spawn command alongside the allocated port.- On the spawned client,
TemporaryClientTask.doWork()starts the embedded server on the internalhost:portand then, ifforwardedHostis set, rewrites the externally visible URI withgetForwardedUri(forwardedProto, forwardedHost, webRoot)— i.e.URI.create(forwardedProto + "://" + forwardedHost + webRoot + "/"). - The rewritten URI is reported back to the server via
clientIsReady(uuid, uri);WebDriverHandler.getRemoteUri()appends the auth token, and the browser is redirected to the public proxy URL — never to the internal port.
6.2 Proxy path (web-root) configuration¶
WebDriverHandler.getServerProxyPath(forwardedHost) returns "/" + PROXY_PATH when a proxy custom context path (serverProxyPath) is configured in the directory webClient config (and an empty string otherwise). This proxyPath / web-root is used both to build login/logout page URLs (PLACEHOLDER_PROXY_PATH, web:url:serverBaseUrl) and to rewrite the spawned client's URI, so that a proxy mapping like https://public.example.com/fwd/ → internal 192.168.100.28:7449/ resolves consistently. (When REMOVE_PROXY_PATH_IN_WEB_PAGES is set, the proxy path is stripped from page URLs so the proxy can map serverProxyPath directly to the driver root; in that case the directory serverProxyPath and guiIdentifier/chuiIdentifier should match.)
6.3 Reverse-proxy flow¶

7. Key classes & responsibilities¶
| Class | Side | Responsibility |
|---|---|---|
BrokerDiscovery |
server | Parse brokers container of directory.xml into Broker objects (readBrokerConfigs, getBroker). |
Broker |
server | Holds one broker's config (brokerAccount, accounts, host, from/to, portsRestricted, index) and its registered BrokerParameters instances. |
BrokerManager |
server | Tracks brokers (activeBrokers, hostToBroker), handles registerBroker, selects a broker (schedule), drives spawn, injects port options (updateWebClientOptions). |
BrokerParameters |
server | Per-registration record: uuid, authenticated userId, agentAddress, associatedUser, dedicatedMode, state, remote BrokerClientServices proxy, session. |
BrokerState |
both | OPEN / CLOSE / INTERRUPTED. |
BrokerServerServices |
server (RPC) | Interface the broker calls: registerBroker(...), start(uuid). |
BrokerClientServices |
broker (RPC) | Interface the server calls on the broker: spawn(BrokerSpawnParameters), getSystemLoading(), getDedicatedHost(), ping(uuid). |
BrokerSpawnParameters / BrokerSpawnResult |
both | Spawn request (command, environment, password) / result (exitCode, exception). |
BrokerCore |
broker | Broker-mode lifecycle: initialize, connect/register, spawn (launch JVM via ProcessBuilder + native spawn). |
WebDriverHandler / VirtualDesktopWebHandler |
server | Serve landing page; handleStartClientRequest → spawnWorker; build the redirect URL. |
WebClientsManager |
server | Per-host port queues (createResourceQueue), allocateClient/freeClient, registerWebClientSession. |
WebClientSpawner |
server + spawned client | Server side: orchestrate remote spawn, clientIsReady → remoteUri, getRemoteUri. Spawned-client side: TemporaryClientTask.doWork starts the embedded web server. |
WebAllocatedResources |
server | One allocatable unit: host, port, debugAgentPort, jmxAgentPort, uuid, osUser, proxy fields; back-reference to its queue for release. |
8. Notes, assumptions & open questions¶
- Browser → spawned-client reachability. With
dedicatedMode=falseandweb host=192.168.100.28, the browser is redirected to the spawned client's embedded server at192.168.100.28:<port>and connects there directly (theportsRange 7449–7459on the broker host must be reachable from browsers / through the reverse proxy). WithdedicatedMode=truethe advertised host is forced tolocalhost, which only works when a reverse proxy fronts the clients (section 6). - Agent ports vs client ports.
minAgentPort/maxAgentPort(9988–9999, frombroker1_client.xml) are the JVM agent/debug/JMX ports for the spawned process on the broker host; theportsRange(7449–7459, fromdirectory.xml) are the browser-facing embedded-web-server ports. They are tracked separately insideWebAllocatedResources. - Per-spawn agent-port override (resolved). The fixed JDWP port in
jvmArgs(-Xrunjdwp:...address=9988) is not used as-is for concurrent clients — the configured agent port range overrides it per spawn. At registrationBrokerCoreparses the single debug/JMX ports fromjvmArgs(WebClientsManager.parseDebuggerPort/parseJMXPort) and reports the range as configured (isAgentRangeConfigured).WebClientsManager.createResourceQueuethen builds the agent-port list fromIntStream.rangeClosed(minAgentPort, maxAgentPort)and assigns eachWebAllocatedResourcesa distinctdebugAgentPort/jmxAgentPort(viapollFirst). At spawn timeWebClientBuilderParameters.updateOptions(allocatedResources)rewrites the spawned JVM's-Xrunjdwp:transport=...,address=<port>(and-Dcom.sun.management.jmxremote.port) to the allocated port. So each concurrently spawned client on the broker host gets its own debug/JMX port from the 9988–9999 range, and the literaladdress=9988injvmArgsis replaced rather than colliding.
Debugging spawned web clients¶
There are locally-spawned and broker-spawned web clients.
Locally-spawned web clients¶
These special directory settings under clientConfig node start the java debugger agent on the specified ports and apply the default debugger profile settings to spawned java clients.
| Configuration name | Class | Format/Example |
|---|---|---|
| minAgentPort | integer | <node class="integer" name="minAgentPort"><node-attribute name="value" value="8980"/></node> |
| maxAgentPort | integer | <node class="integer" name="maxAgentPort"><node-attribute name="value" value="8990"/></node> |
| enableSpawnerDebug | boolean | <node class="boolean" name="enableSpawnerDebug"><node-attribute name="value" value="TRUE"/></node> |
| jvmArgs | string | <node class="string" name="jvmArgs"><node-attribute name="value" value="*"/></node> |
jvmArgs value="*" is a standard JVM arguments string. For the debugging the following JVM arguments string is used commonly in FWD project
-Xmx512m -Djava.awt.headless=true -Duser.country=US -Duser.language=en -Xdebug -Xnoagent -Djava.compiler=NONE -Djava.net.preferIPv4Stack=true -Xrunjdwp:transport=dt_socket,address=8980,server=y,suspend=n
All these options can be adjusted for the target tasks.
If jvmArgs settings specifies the java debugger -Xrunjdwp:transport=dt_socket,address=8980,server=y,suspend=n, then for the locally-spawned java process the given port number is allocated 8980. So the next spawned process cannot use the same settings and these configuration nodes minAgentPort and maxAgentPort are used to specify the range of available ports for the java debugger. The web client runtime manages these ports so the next spawned java process can be started successfully in this debug mode.
This is an example of the directory settings used here
<node class="container" name="clientConfig">
............................................
<node class="boolean" name="enableSpawnerDebug">
<node-attribute name="value" value="TRUE"/>
</node>
<node class="integer" name="minAgentPort">
<node-attribute name="value" value="8980"/>
</node>
<node class="integer" name="maxAgentPort">
<node-attribute name="value" value="8999"/>
</node>
<node class="string" name="jvmArgs">
<node-attribute name="value" value="-Xmx512m
-Djava.awt.headless=true
-Duser.country=US
-Duser.language=en
-Xdebug
-Xnoagent
-Djava.compiler=NONE
-Djava.net.preferIPv4Stack=true
-Xrunjdwp:transport=dt_socket,address=8980,server=y,suspend=n"/>
</node>
<node class="string" name="cfgOverrides">
<node-attribute name="value" value="client:cmd-line-option:debugalert=true net:http_client:disable_ssl_certificate_validation=true security:provider:name=conscrypt"/>
</node>
...................................................
</node>
Broker-spawned web clients: when a client is launched on a remote broker host rather than locally, the equivalent debug/agent port management is driven from the broker client's minAgentPort/maxAgentPort (broker1_client.xml) — see the Per-spawn agent-port override note in "section 8":#remote-brokers of the broker documentation above for how the fixed -Xrunjdwp port is overridden per spawn from that range.
Networking Configuration Value Recommendations¶
This section will help the end-user with some suggestions regarding the recommended values for the parameters which can be defined in directory.xml. It will include a set of values for environments with fast and reliable network, and another set for the ones with slow networks. The description for each of the following parameters can be read in the bootstrap configuration, corresponding to the client:web entries.
| Configuration name | Default | Fast network | Slow network |
|---|---|---|---|
| webSocketTimeout | 90000 | 90000 | 180000 |
| watchdogTimeout | 120000 | 90000 | 180000 |
| pingPongInterval | 30000 | 15000 | 50000 |
| maxLostPings | 6 | 4 | 10 |
| delayBetweenPingTries | 1000 | 1000 | 3000 |
| delayBetweenTriesToConnect | 5000 | 2500 | 8000 |
| maxIdleTime | 90000 | 90000 | 180000 |
| embWebServerMaxIdleTimeout | 90000 | 90000 | 180000 |
| maxOutputBufferSize | 1048576 | 1048576 | 524288 |
| maxOutputAggregationSize | 8192 | 8192 | 4096 |
| maxResponseHeaderSize | 8192 | 8192 | 4096 |
| maxRequestHeaderSize | 8192 | 8192 | 4096 |
There are restrictions on values used for configuration parameters: the embedded web server parameters for the java web client webSocketTimeout, watchdogTimeout, embWebServerMaxIdleTimeout and maxIdleTime and the jscript client parameter pingPongInterval.
webSocketTimeout <= maxIdleTime <= embWebServerMaxIdleTimeout, webSocketTimeout + pingPongInterval <= watchdogTimeout and webSocketTimeout > pingPongInterval should be satisfied so that for watchdogTimeout the web client can reconnect to the java web client and at least one ping message comes within webSocketTimeout interval.
maxIdleTimeused to setorg.eclipse.jetty.websocket.servlet.WebSocketServletFactory.getPolicy().setIdleTimeout(maxIdleTime)for the web socket connection created by web socket servlet factory. If its directory value is not set, then the default value for the web client is 90000 ms given bymaxIdleTime=90000.webSocketTimeoutused to set a timeoutorg.eclipse.jetty.websocket.api.Session.setIdleTimeout(webSocketTimeout)for the given web socket session. If its directory value is not set, then the default value is 90000 ms given bywebSocketTimeout=90000.embWebServerMaxIdleTimeoutused to set a timeout for idle http connection of the embedded web server byorg.eclipse.jetty.server.HttpConfiguration.setIdleTimeout(embWebServerMaxIdleTimeout). If its directory value is not set, then the default value is infinity given by embWebServerMaxIdleTimeout=0.watchdogTimeoutis a timeout within which interval the java web client can establish the web socket connection with the jscript web client on any IO exceptions during network operations. If its directory value is not set, then the default value is 120000 ms given bywatchdogTimeout=120000.pingPongIntervalused for jscript web client that sends a ping pong message within this interval. If its directory value is not set, then the default value is 30000 ms given bypingPongInterval=30000.
The target default values are defined in com.goldencode.p2j.web.WebConfigurationConstants.
New Constants — Branch 11401a¶
This branch adds configuration, routing, and protocol constants supporting two related features in the web client driver:
- A bounded, refreshable authorization cookie (TTL + refresh/delete endpoints).
- A client response timeout that lets the Java web client wait — without blocking forever — for the JavaScript client to acknowledge shutdown so the auth cookie can be cleaned up.
1. com.goldencode.p2j.util.ConfigItem¶
Two new directory-backed configuration items, both ConfigItem<Integer> of Type.WEB_CLIENT.
CLIENT_RESPONSE_TIMEOUT¶
| Property | Value |
|---|---|
| Type | ConfigItem<Integer> |
| Config key | clientResponseTimeout |
| Directory path | client:web:clientResponseTimeout |
| Unit | milliseconds |
| Default | WebConfigurationConstants.CLIENT_RESPONSE_TIMEOUT (5000) |
Time in milliseconds the Java web client waits for a response from the JavaScript client before giving up, so the Java side is never blocked forever. Non-positive configured values are clamped to the default at every read site.
AUTH_COOKIE_TTL¶
| Property | Value |
|---|---|
| Type | ConfigItem<Integer> |
| Config key | authCookieTtl |
| Directory path | client:web:authCookieTtl |
| Unit | seconds |
| Default | WebConfigurationConstants.AUTH_COOKIE_TTL (1800) |
Lifetime in seconds of the web-client authorization cookie. While a tab is active the cookie is periodically refreshed; once the tab closes the cookie expires within this TTL, reclaiming orphaned cookies left behind by abnormally-closed tabs. Non-positive configured values are clamped to the default.
2. com.goldencode.p2j.web.WebConfigurationConstants¶
Default values for the two new config items above. Used as the fallback whenever the corresponding ConfigItem is unset or non-positive.
| Constant | Type | Value | Meaning |
|---|---|---|---|
CLIENT_RESPONSE_TIMEOUT |
int |
5000 |
Default timeout (ms) to wait for the JS client response. |
AUTH_COOKIE_TTL |
int |
1800 |
Default authorization-cookie lifetime (seconds = 30 minutes). |
3. com.goldencode.p2j.ui.client.driver.web.AuthHandler¶
Two new public route constants identifying dedicated HTTP request paths handled by AuthHandler. Both respond 204 No Content.
| Constant | Type | Value | Meaning |
|---|---|---|---|
DELETE_AUTH_HANDLER |
String |
"/delete-cookie/" |
Path whose handler deletes the (HttpOnly) authorization cookie. Invoked by the JS client on shutdown. |
REFRESH_AUTH_HANDLER |
String |
"/refresh-cookie/" |
Path whose handler re-issues the authorization cookie with a fresh max age, prolonging an active session. |
4. com.goldencode.p2j.ui.client.driver.web.WebClientMessageTypes¶
One new binary protocol message type.
| Constant | Type | Value | Meaning |
|---|---|---|---|
MSG_SHUTTINGDOWN |
byte |
(byte) 0xCF |
Sent by the Java web client to tell the JS client it is shutting down, giving the JS client a chance to clear the auth cookie before MSG_QUIT. |
JavaScript mirror — res/p2j.socket.js¶
The same value is mirrored in the JS message-type map so the client recognizes it:
| Constant | Value |
|---|---|
types.MSG_SHUTTINGDOWN |
0xCF |
Constant relationships¶
- The Java side sends
MSG_SHUTTINGDOWNonquit()and then waits up toCLIENT_RESPONSE_TIMEOUTms for the JS acknowledgement. - On receiving
MSG_SHUTTINGDOWN, the JS client issues a request toDELETE_AUTH_HANDLER(/delete-cookie/) with a fetch timeout of ~80% ofclientResponseTimeout, then acknowledges. - While a tab is active the JS client periodically calls
REFRESH_AUTH_HANDLER(/refresh-cookie/) at half theAUTH_COOKIE_TTL(capped at 5 minutes) to keep the cookie alive; if the tab dies, the cookie self-expires withinAUTH_COOKIE_TTL.
© 2004-2025 Golden Code Development Corporation. ALL RIGHTS RESERVED.