Project

General

Profile

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:

  1. 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 the adminPort directory 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.
  2. The FWD server sends down a login page, which looks like:
  3. The user inputs their operating system level user credentials (e.g. Linux userid and password) and submits the form.
  4. 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.
  5. 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.
  6. 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.
  7. 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 - Forbidden page after the redirect, note at the time of this writing there is a limitation for web clients: the chuiWeb/host node needs to be configured so that it mentions the server's host used to access the web client.
  8. The FWD client's web server sends back the "main" page which is mostly Javascript.
  9. The loaded page establishes a bidirectional websocket to the FWD client.
  10. 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).
  11. 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).
  12. 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 (CN broker1 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)):

  1. initialize(cfg) reads broker1_client.xml (section 2.2) and marks the process as a client (cfg.setServer(false)).
  2. SecurityManager.createInstance(cfg) loads the broker1 keystore / srv-certs.store truststore.
  3. A retry loop (count × seconds) calls connect(sessMgr, cfg):
    1. sessMgr.connectDirect(...) opens a secure session to the server (TLS, port 3333). The TLS handshake presents the broker1 certificate; the server authenticates the session as process account broker1_process (program authentication).
    2. The broker obtains the server-exported BrokerServerServices proxy and calls registerBroker(...), passing dedicatedHost (web host), associatedUser (defaultOsUser), dedicatedMode, the agent port range (minAgentPort/maxAgentPort), and debugger/JMX hints.
  4. On the server, BrokerManager.registerBroker(...):
    1. Looks up the matching brokers/broker1 entry by resolved host address (see section 5.2) and verifies the authenticated account equals broker_account (broker1_process). A mismatch throws and the registration is rejected.
    2. Builds a BrokerParameters record (random uuid, authenticated userId, agentAddress = host, associatedUser, dedicatedMode, a BrokerClientServices remote proxy back to the broker, and state = OPEN).
    3. Registers the host with WebClientsManager.createResourceQueue(...), which pre-fills a BlockingQueue<WebAllocatedResources> with one entry per port in 7449..7459 (plus the agent debug/JMX ports). The host index = broker index + 1 (localhost is 1).
  5. registerBroker returns the uuid; 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
  1. Landing page — the browser does GET /gui (or /chui). VirtualDesktopWebHandler.handle()serveLandingPage() returns the login HTML.
  2. Login POST — the browser POSTs credentials. WebDriverHandler.handleStartClientRequest() extracts WebDriverRequestParameters (user, password, theme, device id, and any reverse-proxy X-Forwarded-Host/-Proto) and builds WebDriverSpawnParameters (driver type, SSO flag, options), then calls spawnWorker(...).
  3. Resource allocationWebClientsManager.allocateClient(proxyConfigs, host, uuid, osUser) polls one WebAllocatedResources (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 the uuid → resources mapping.
  4. Broker selection & spawn RPCspawnWorker invokes WebClientSpawner.spawn(...), which (because this is a remote/broker spawn) routes through BrokerManager.spawn(clientBuilder, dedicatedUser, brokers, args, allocator, requestParameters, uuid):
    1. schedule(...) picks a registered broker for the user — honoring account=fwd / dedicated-host constraints and choosing the lowest getSystemLoading().
    2. updateWebClientOptions() injects the allocated --port=7449 (and reverse-proxy host/proto/web-root if present) into the command.
    3. The server calls broker.getRemote().spawn(BrokerSpawnParameters) over the broker's RPC session. BrokerSpawnParameters carries the command line + environment; trusted-vs-password mode decides whether a one-time password is streamed over stdin.
  5. Process launch on the brokerBrokerCore.spawn(args) (running on the broker host):
    1. Resolves command placeholders against the broker's local config — PARAM_SPAWNERspawner path, PARAM_JVMjvmArgs, PARAM_CPclasspath, PARAM_HOST/PARAM_PORT → server endpoint, PARAM_REMOTE_AGENT_HOSTdedicatedHost, PARAM_DEDICATED_MODEdedicatedMode.
    2. Runs the native spawn binary via ProcessBuilder (as OS user fwd, per the trusted-spawner grant), optionally writing the one-time password to stdin, and waits up to the launch timeout.
  6. Spawned web client comes up — the new JVM authenticates to the server with its temporary credentials and runs WebClientSpawner.TemporaryClientTask.doWork():
    1. Imports the server key store into its config, creates a GuiWebDriver (or ChuiWebDriver), and calls EmbeddedWebServer.startupServer(...) which starts an embedded Jetty server on host:port (e.g. 192.168.100.28:7449). If a reverse proxy is configured, the URI is rewritten using X-Forwarded-Host/-Proto and the proxy web-root.
    2. It calls back to the server via RPC spawner.clientIsReady(uuid, uri). On the server, WebClientSpawner.clientIsReady() stores this as remoteUri.
  7. Session registration — when the spawned client's P2J session attaches, WebClientsManager.registerWebClientSession(webClientId, peerNode) cancels the cleanup timer and maps peerNode → WebAllocatedResources, so the port can be released when the session ends (freeClient() returns it to the queue).
  8. Redirect to the browser — back in spawnWorker, after exitCode == 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).
  9. 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. Because dedicatedMode=false, the advertised host is the real 192.168.100.28 rather than localhost, 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.

  1. Candidate set by accountBrokerManager.getBrokers(osUser)getBrokersForUser(user):
    • A broker whose account list contains the user (broker.hasAccount(user)) is an account-specific broker for that user.
    • A broker with no account attribute (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.OPEN are considered.
  2. Load-based schedulingBrokerManager.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.

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
  1. The browser hits the proxy, which forwards the login request to the FWD server adding X-Forwarded-Host and X-Forwarded-Proto headers.
  2. WebDriverHandler extracts these into WebDriverRequestParameters (forwardedHost, forwardedProto, plus externalUserIp) and passes them to WebClientSpawner.spawn(webClientsManager, {forwardedHost, forwardedProto, externalUserIp}).
  3. BrokerManager.updateWebClientOptions() injects the proxy values (PROXY_HOST, PROXY_PROTOCOL, WEB_ROOT) into the spawn command alongside the allocated port.
  4. On the spawned client, TemporaryClientTask.doWork() starts the embedded server on the internal host:port and then, if forwardedHost is set, rewrites the externally visible URI with getForwardedUri(forwardedProto, forwardedHost, webRoot) — i.e. URI.create(forwardedProto + "://" + forwardedHost + webRoot + "/").
  5. 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; handleStartClientRequestspawnWorker; build the redirect URL.
WebClientsManager server Per-host port queues (createResourceQueue), allocateClient/freeClient, registerWebClientSession.
WebClientSpawner server + spawned client Server side: orchestrate remote spawn, clientIsReadyremoteUri, 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=false and web host=192.168.100.28, the browser is redirected to the spawned client's embedded server at 192.168.100.28:<port> and connects there directly (the portsRange 7449–7459 on the broker host must be reachable from browsers / through the reverse proxy). With dedicatedMode=true the advertised host is forced to localhost, which only works when a reverse proxy fronts the clients (section 6).
  • Agent ports vs client ports. minAgentPort/maxAgentPort (9988–9999, from broker1_client.xml) are the JVM agent/debug/JMX ports for the spawned process on the broker host; the portsRange (7449–7459, from directory.xml) are the browser-facing embedded-web-server ports. They are tracked separately inside WebAllocatedResources.
  • 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 registration BrokerCore parses the single debug/JMX ports from jvmArgs (WebClientsManager.parseDebuggerPort/parseJMXPort) and reports the range as configured (isAgentRangeConfigured). WebClientsManager.createResourceQueue then builds the agent-port list from IntStream.rangeClosed(minAgentPort, maxAgentPort) and assigns each WebAllocatedResources a distinct debugAgentPort/jmxAgentPort (via pollFirst). At spawn time WebClientBuilderParameters.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 literal address=9988 in jvmArgs is 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.

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.

  • maxIdleTime used to set org.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 by maxIdleTime=90000.
  • webSocketTimeout used to set a timeout org.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 by webSocketTimeout=90000.
  • embWebServerMaxIdleTimeout used to set a timeout for idle http connection of the embedded web server by org.eclipse.jetty.server.HttpConfiguration.setIdleTimeout(embWebServerMaxIdleTimeout). If its directory value is not set, then the default value is infinity given by embWebServerMaxIdleTimeout=0.
  • watchdogTimeout is 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 by watchdogTimeout=120000.
  • pingPongInterval used 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 by pingPongInterval=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:

  1. A bounded, refreshable authorization cookie (TTL + refresh/delete endpoints).
  2. 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_SHUTTINGDOWN on quit() and then waits up to CLIENT_RESPONSE_TIMEOUT ms for the JS acknowledgement.
  • On receiving MSG_SHUTTINGDOWN, the JS client issues a request to DELETE_AUTH_HANDLER (/delete-cookie/) with a fetch timeout of ~80% of clientResponseTimeout, then acknowledges.
  • While a tab is active the JS client periodically calls REFRESH_AUTH_HANDLER (/refresh-cookie/) at half the AUTH_COOKIE_TTL (capped at 5 minutes) to keep the cookie alive; if the tab dies, the cookie self-expires within AUTH_COOKIE_TTL.

© 2004-2025 Golden Code Development Corporation. ALL RIGHTS RESERVED.

client_spawning_flow.png (191 KB) Greg Shah, 03/29/2019 04:24 PM

virtual_desktop_mode_os_account_authentication_prompt.png (6.07 KB) Greg Shah, 03/09/2020 05:02 AM

web_client_modes.png (182 KB) Greg Shah, 03/09/2020 05:04 AM

web_client_spawning_flow_external_embedded_mode.png (241 KB) Greg Shah, 03/09/2020 05:04 AM

web_client_spawning_flow_integrated_embedded_mode.png (231 KB) Greg Shah, 03/09/2020 05:04 AM

web_client_spawning_flow_virtual_desktop_mode.png (172 KB) Greg Shah, 03/09/2020 05:04 AM

broker_reverse_proxy.png (95.8 KB) Sergey Ivanovskiy, 06/25/2026 07:04 AM

broker_scheduling.png (121 KB) Sergey Ivanovskiy, 06/25/2026 07:04 AM

broker_spawn_sequence.png (90.5 KB) Sergey Ivanovskiy, 06/25/2026 07:04 AM

broker_registration.png (146 KB) Sergey Ivanovskiy, 06/25/2026 07:04 AM

broker_identity_trust.png (127 KB) Sergey Ivanovskiy, 06/25/2026 07:04 AM

broker_deployment.png (85.8 KB) Sergey Ivanovskiy, 06/25/2026 07:04 AM

broker_reverse_proxy.mmd (865 Bytes) Sergey Ivanovskiy, 06/25/2026 07:10 AM

broker_scheduling.mmd (547 Bytes) Sergey Ivanovskiy, 06/25/2026 07:10 AM

broker_spawn_sequence.mmd (1.48 KB) Sergey Ivanovskiy, 06/25/2026 07:10 AM

broker_registration.mmd (1011 Bytes) Sergey Ivanovskiy, 06/25/2026 07:10 AM

broker_identity_trust.mmd (813 Bytes) Sergey Ivanovskiy, 06/25/2026 07:10 AM

broker_deployment.mmd (1.15 KB) Sergey Ivanovskiy, 06/25/2026 07:10 AM