Project

General

Profile

web_clients_local_brokers.md

last version - Sergey Ivanovskiy, 06/09/2026 03:01 PM

Download (15.9 KB)

 
1
# Web Clients — Local Brokers, Host Order, and `map.clients-to-backends`
2

    
3
This note explains how per-broker web-client port ranges are described in the
4
directory, how the **host order / host index** is derived from the directory
5
(no `hosts.txt` file is involved), and how `ClientsToPortsGenerator` produces the
6
`map.clients-to-backends` file consumed by a reverse proxy.
7

    
8
It is written against the broker example below and the code in
9
`com.goldencode.p2j.main.{BrokerDiscovery, Broker, ClientsToPortsGenerator,
10
HostsManager, WebClientsManager}`.
11

    
12
## 0. Two modes
13

    
14
`ClientsToPortsGenerator.generate(String host)` picks one of two modes based on
15
whether the directory's `brokers` container has any broker nodes:
16

    
17
| Mode | Condition | Hosts | Host index |
18
|------|-----------|-------|------------|
19
| **Broker mode** | `brokers` container is **non-empty** | one entry per ports-restricted broker | the broker's **position in the directory order** (loopback = 1, brokers = 2, 3, …) |
20
| **Local mode**  | `brokers` container is **empty** | a single host | always **1** |
21

    
22
In **local mode** the single host is the `host` argument passed to `generate`
23
(the actual representer of the local server, e.g. its LAN address
24
`192.168.100.3`), or `localhost` when no host is supplied. **No `hosts.txt` input
25
is required in either mode** — the host order comes entirely from the directory
26
(broker mode) or from the single host argument (local mode).
27

    
28
> **Host order is directory-based.** The host index is *not* stored in a file and
29
> is *not* an attribute on any node. In broker mode it is the enumeration order
30
> of the broker nodes under the `brokers` container; the FWD server's loopback is
31
> index `1` and brokers are `2, 3, …`. Because the generator and the running
32
> server both derive the index from that same ordered list, their indices agree
33
> by construction.
34

    
35
## 1. The directory data (broker mode)
36

    
37
A broker is a node under the `brokers` container (which itself lives under
38
`/server/<serverId>/`, e.g. `/server/default/`):
39

    
40
```xml
41
<node class="container" name="brokers">
42
  <node class="broker" name="broker1">
43
    <node-attribute name="account" value="sbi"/>
44
    <node-attribute name="broker_account" value="broker1_process"/>
45
    <node-attribute name="host" value="192.168.100.28"/>
46
    <node class="container" name="portsRange">
47
      <node class="string" name="namePrefix">
48
        <node-attribute name="value" value="client"/>
49
      </node>
50
      <node class="integer" name="from">
51
        <node-attribute name="value" value="7449"/>
52
      </node>
53
      <node class="integer" name="to">
54
        <node-attribute name="value" value="7459"/>
55
      </node>
56
    </node>
57
  </node>
58
</node>
59
```
60

    
61
The `broker` object-class (see `dir_schema.xml`) is **non-leaf** and carries:
62

    
63
| Attribute        | Mandatory | Multiple | Meaning |
64
|------------------|-----------|----------|---------|
65
| `host`           | yes       | no       | The host name / IP address on which this broker's client (agent) runs. This is the broker's **identity for host ordering**. |
66
| `broker_account` | yes       | no       | The FWD process account the broker client must authenticate as to register with the server. |
67
| `account`        | no        | yes      | The list of FWD user accounts served by this broker. |
68

    
69
The child `portsRange` container holds the **web-client port range** for this
70
broker:
71

    
72
| Node                  | Meaning |
73
|-----------------------|---------|
74
| `portsRange/from`     | First web-client port (inclusive). |
75
| `portsRange/to`       | Last web-client port (inclusive). |
76
| `portsRange/namePrefix` | **Not read per broker** — see the caveat in §6. |
77

    
78
In the example, `broker1` runs on `192.168.100.28` and owns the web-client port
79
range `7449..7459` (11 ports).
80

    
81
### How the directory data is read
82

    
83
`BrokerDiscovery.readBrokerConfigs(ds)`:
84

    
85
1. Locates the `brokers` container via `Utils.findDirectoryNodePath(ds,
86
   "brokers", "container", false)`.
87
2. Enumerates the broker child nodes with `ds.enumerateNodes(path)` and, **in
88
   that enumeration order**, builds a `LinkedHashMap<String, Broker>` (broker
89
   name → `Broker`). **This ordered map is what defines the host order** — see
90
   §2.
91
3. For each broker node, `getBroker(...)` reads the `account` / `broker_account`
92
   / `host` attributes, then reads the two port-range values **relative to the
93
   broker node** using `ConfigItem.RELATIVE_TO_PARENT`:
94

    
95
   ```java
96
   int from = ConfigItem.<Integer>createConfigItemOfType(
97
                 ConfigItem.CLIENT_PORT_RANGE_FROM, Type.RELATIVE_TO_PARENT)
98
             .setContainerNodeSearchPath("brokers/broker1/")   // "brokers/<name>/"
99
             .read(0);
100
   // ... same for CLIENT_PORT_RANGE_TO ("portsRange/to")
101
   ```
102

    
103
   `CLIENT_PORT_RANGE_FROM`/`_TO` have node name `portsRange/from` /
104
   `portsRange/to`, so the resolved directory path is
105
   `brokers/broker1/portsRange/from` and `…/to`.
106

    
107
4. The resulting `Broker` reports `isPortsRestricted()` as
108
   `(to > from) && (from > 0)`. For `broker1` that is
109
   `(7459 > 7449) && (7449 > 0)` → **true**. A broker whose range is absent or
110
   invalid is **not** ports-restricted: it still occupies a host index (§2) but
111
   produces no lines in the map.
112

    
113
## 2. Host order — derived from the broker enumeration order
114

    
115
There is no host-index file and no stored index attribute. In broker mode the
116
host index is the **position of a broker in the ordered broker list**:
117

    
118
* Index **1** is reserved for the FWD server's own loopback host
119
  (`localhost` / `127.0.0.1`). The server is always "host 1".
120
* **Every** broker node consumes the next index in the order it appears under the
121
  `brokers` container — the first broker → `2`, the second → `3`, and so on. This
122
  is true even for a broker that is not ports-restricted: it still takes its slot
123
  so that the numbering stays aligned with the runtime, which registers one host
124
  per broker regardless of its port configuration (§7).
125

    
126
So for the example (one broker, `broker1` on `192.168.100.28`):
127

    
128
```
129
index 1 : localhost / 127.0.0.1   (the FWD server)
130
index 2 : 192.168.100.28          (broker1, first broker node)
131
```
132

    
133
Add a `broker2` after `broker1` and it becomes index `3`, etc. The ordering is
134
the directory enumeration order — the same order `BrokerDiscovery` returns and
135
the same "well-defined host order" the comment in `BrokerManager.registerBroker`
136
refers to. **To renumber a host, reorder the broker nodes** in the directory.
137

    
138
Each broker declares a unique `host` (mandatory, single-valued), and
139
`BrokerManager` rejects a second broker that resolves to a host already claimed,
140
so the host → index mapping is one-to-one.
141

    
142
> **Design decision — count every broker.** The host index advances for *every*
143
> broker node, including brokers that are not ports-restricted, rather than only
144
> for the brokers that emit lines. This is deliberate, for consistency with the
145
> runtime: `BrokerManager.registerBroker` registers one host per broker
146
> (`HostsManager.addHost`) regardless of its port configuration, so a
147
> non-ports-restricted broker still consumes a host slot at runtime. Counting it
148
> in the generator too keeps the offline indices and the runtime indices in
149
> lockstep. The trade-off is that the position of *all* broker nodes affects the
150
> numbering — adding, removing, or reordering any broker shifts the indices of
151
> the brokers after it.
152

    
153
## 3. Mode selection in `generate(String host)`
154

    
155
```
156
generate(host):
157
  brokers = BrokerDiscovery.readBrokerConfigs(ds)
158
  if (!brokers.isEmpty()):
159
      → generateWithBrokerRanges(brokers, fileName)   # broker mode (directory-ordered)
160
  else:
161
      → local mode: single host = host or "localhost", at index 1
162
```
163

    
164
The switch is simply **whether any broker node exists**. (Note this differs from
165
an earlier draft that switched on "any broker is ports-restricted"; the mode is
166
now decided purely by the presence of broker nodes.)
167

    
168
## 4. Broker mode — `generateWithBrokerRanges`
169

    
170
The generator walks the brokers **in directory order**, advancing the host index
171
for every broker (starting at `1` for the server loopback, so the first broker
172
host is `2`), and emits lines only for ports-restricted brokers:
173

    
174
```
175
prefix    = resolvePrefix(...)            # see §6
176
hostIndex = 1                             # 1 == server loopback (localhost)
177

    
178
for (broker : brokers.values()) {         # directory enumeration order
179
   hostIndex++;                           # every broker takes a slot: 2, 3, ...
180
   if (!broker.isPortsRestricted()) continue;   # but only restricted ones emit lines
181

    
182
   for (port = broker.getFrom(); port <= broker.getTo(); port++) {
183
      name = getPortName(prefix, broker.getFrom(), hostIndex, port);
184
      write(name + "    " + broker.getHost() + ":" + port);
185
   }
186
}
187
```
188

    
189
The output file is `map.clients-to-backends` by default (the user may enter
190
another name at the prompt). Because the index comes from the broker order, there
191
is no `hosts.txt` lookup, creation, or write-back.
192

    
193
### Port name format
194

    
195
`getPortName` (shared by the generator and the runtime) is:
196

    
197
```
198
«prefix»-«hostIndex»-«port - from + 1»
199
```
200

    
201
i.e. `prefix`, the **host index**, and the **1-based offset of the port within
202
the range**, joined by hyphens.
203

    
204
## 5. Local mode — single host
205

    
206
When `brokers` is empty, the generator uses the **global**
207
`webClient/portsRange` (`from` / `to`, prompting and persisting them in the
208
directory if absent, exactly as before) and maps a **single host at index 1**:
209

    
210
```
211
localHost = (host == null || host.isEmpty()) ? "localhost" : host
212
for (port = from; port <= to; port++) {
213
   write(getPortName(prefix, from, 1, port) + "    " + localHost + ":" + port)
214
}
215
```
216

    
217
`host` is the `generate(...)` argument — supplied on the command line as the
218
optional second parameter (see §8). Use it to name the local server by the
219
address a reverse proxy must reach (e.g. `192.168.100.3`); omit it to fall back
220
to `localhost`.
221

    
222
## 6. Caveat — `namePrefix` under the broker node is ignored
223

    
224
`resolvePrefix(...)` reads `ConfigItem.PROXY_CLIENT_NAME_PREFIX`, which is a
225
`Type.WEB_CLIENT` item with node name `portsRange/namePrefix` — i.e. it resolves
226
to the **global** `webClient/portsRange/namePrefix`, *not* the
227
`portsRange/namePrefix` placed under the broker node.
228

    
229
Consequences:
230

    
231
* The `<node class="string" name="namePrefix" value="client"/>` inside
232
  `broker1/portsRange` in the example is **not consulted** by the generator.
233
* The prefix comes from `webClient/portsRange/namePrefix`; if that is empty the
234
  generator prompts for it (interactive) and defaults to `client` if nothing is
235
  entered.
236
* Only `portsRange/from` and `portsRange/to` are read per broker
237
  (`BrokerDiscovery` does not read a per-broker `namePrefix`).
238

    
239
If a per-broker prefix is intended, it would require reading
240
`brokers/<name>/portsRange/namePrefix` via a `RELATIVE_TO_PARENT` item in
241
`BrokerDiscovery`/`generateWithBrokerRanges`; today it is a single global value.
242

    
243
## 7. Worked examples
244

    
245
### Broker mode
246

    
247
Directory: a single ports-restricted broker `broker1` →
248
`host=192.168.100.28`, `from=7449`, `to=7459`, with `prefix=client`.
249

    
250
`broker1` is the first broker node, so its host order is `2` (index `1` is the
251
server loopback). The generator emits 11 lines:
252

    
253
```
254
client-2-1     192.168.100.28:7449
255
client-2-2     192.168.100.28:7450
256
client-2-3     192.168.100.28:7451
257
client-2-4     192.168.100.28:7452
258
client-2-5     192.168.100.28:7453
259
client-2-6     192.168.100.28:7454
260
client-2-7     192.168.100.28:7455
261
client-2-8     192.168.100.28:7456
262
client-2-9     192.168.100.28:7457
263
client-2-10    192.168.100.28:7458
264
client-2-11    192.168.100.28:7459
265
```
266

    
267
The left column is the proxy **route name**; the right column is the **backend**
268
the reverse proxy forwards that route to. If a `broker2` were added next, it
269
would produce `client-3-*` lines for its own host and range.
270

    
271
### Local mode
272

    
273
No brokers configured, global `webClient/portsRange` = `7449..7459`,
274
`prefix=client`, and the generator invoked with host `192.168.100.3`. The single
275
host is index `1`:
276

    
277
```
278
client-1-1     192.168.100.3:7449
279
client-1-2     192.168.100.3:7450
280
...
281
client-1-11    192.168.100.3:7459
282
```
283

    
284
Invoked with no host argument, the backend column would read `localhost:74xx`.
285

    
286
## 8. Command line
287

    
288
```
289
java com.goldencode.p2j.main.ClientsToPortsGenerator <directory.xml> [host]
290
```
291

    
292
* `<directory.xml>` — required; the directory the brokers / webClient config is
293
  read from.
294
* `[host]` — optional; the local-server host for **local mode** (defaults to
295
  `localhost`). **Ignored in broker mode**, where hosts come from the broker
296
  nodes. There is no `hosts.txt` argument.
297

    
298
## 9. The runtime side — the same order, no second source of truth
299

    
300
The generated `map.clients-to-backends` only routes correctly if the **host
301
index the proxy was configured with equals the host index the running server
302
uses**. Both sides derive that index from the **same directory data**, so they
303
agree by construction:
304

    
305
* `HostsManager` pins the server loopback at construction: `localhost → 1`,
306
  `127.0.0.1 → 1`, and the host counter starts at `1`.
307
* As brokers register (`BrokerManager.registerBroker` → `HostsManager.addHost`),
308
  each host is assigned `broker.getIndex() + 1` — i.e. its **directory position**
309
  (`BrokerDiscovery` sets `index = i + 1`), not a running registration-time
310
  counter. Keying the index off the directory position is what keeps the runtime
311
  aligned with the generator regardless of the order in which brokers actually
312
  register, or whether some brokers are skipped.
313
* `HostsManager.getPortName(prefix, from, host, port)` looks up that host's index
314
  directly by the `host` literal (`hosts.get(host)`) — **no DNS resolution at
315
  route-name time** — and calls the same `ClientsToPortsGenerator.getPortName(...)`.
316
  `WebClientsManager` uses this to build the proxy web-root for a web client.
317

    
318
```
319
 directory `brokers` order ──► host index ─┬─► generator: "client-2-7"  (map.clients-to-backends)
320
                                           └─► runtime  : "client-2-7"  (proxy web-root)
321
```
322

    
323
* **Local mode / single-host**: the host is index `1` on both sides; nothing to
324
  diverge.
325
* **Broker mode / multiple hosts**: the index is the broker's position in the
326
  directory order on both sides. Reordering the broker nodes (or the order in
327
  which brokers are expected to register) changes the index consistently for the
328
  generator and the server, so the proxy map and the runtime stay aligned without
329
  a flat file.
330

    
331
### Host literal form
332

    
333
A broker's `host` attribute is used verbatim as its identity. The running server
334
keys the host index by that exact literal: `HostsManager.addHost` stores the
335
index under the registered `host` string, and `getPortName` looks it up by the
336
same string (`hosts.get(host)`) — there is **no DNS resolution at route-name
337
time**. The host registered (`BrokerManager.registerBroker`) and the host used to
338
build the route name (`WebClientsManager`) both originate from the broker's
339
`host` config value, so they match by construction. Keep the `host` literal in a
340
single, consistent form across the directory and any proxy configuration derived
341
from these route names — a different spelling of the same address (e.g. a DNS
342
name vs. its IP) is treated as a distinct host because the lookup is now purely
343
string-keyed.
344

    
345
## 10. Summary
346

    
347
1. `generate(host)` runs **broker mode** when the `brokers` container has any
348
   broker nodes, **local mode** otherwise. Neither mode reads a `hosts.txt` file.
349
2. **Broker mode**: each broker node declares its `host`, `broker_account`, and a
350
   `portsRange/{from,to}`. The **host index is the broker's position in the
351
   directory order** — loopback `1`, brokers `2, 3, …`, counting every broker.
352
   Ports-restricted brokers emit `«prefix»-«hostIndex»-«offset»  «host»:«port»`
353
   lines into `map.clients-to-backends`.
354
3. **Local mode**: a single host (the `host` argument, or `localhost`) is mapped
355
   at index `1` using the global `webClient/portsRange`.
356
4. The running server (`HostsManager`) assigns indices from the same ordered
357
   broker registrations (loopback pinned to `1`), so the offline-generated proxy
358
   map and the runtime web-roots agree by construction.