geonix 1.32.1 → 1.32.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/Request.js +49 -44
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geonix",
3
- "version": "1.32.1",
3
+ "version": "1.32.2",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "bin": {
package/src/Request.js CHANGED
@@ -73,11 +73,16 @@ export async function Request(service, method, args, context, options) {
73
73
  return directRequest(identifier, method, args, context, options, service);
74
74
  }
75
75
 
76
+ let _httpInstanceRoundRobin = 0;
76
77
  let _httpRpcRoundRobin = 0;
77
78
 
79
+ const DEFAULT_HTTP_TIMEOUT = 300_000;
80
+
78
81
  /**
79
82
  * Sends a request directly to a resolved registry identifier without performing a registry
80
- * lookup. Prefers HTTP RPC for single-instance services; falls back to NATS otherwise.
83
+ * lookup. Prefers HTTP RPC; for multi-instance services round-robins across instances that
84
+ * have advertised addresses. Falls back to NATS when no instance has addresses or the HTTP
85
+ * attempt fails.
81
86
  *
82
87
  * @param {string} identifier - Registry identifier in the form `"Name@version"` or an instance ID.
83
88
  * @param {string} method - Method name to invoke on the service.
@@ -92,53 +97,53 @@ export async function directRequest(identifier, method, args, context, options,
92
97
  const originator = rpcContext.getStore()?.originator;
93
98
  const requestBegin = Date.now();
94
99
 
95
- // try HTTP RPC first if the service has a single known instance with addresses
96
- // (multiple instances use NATS so queue-group distribution is preserved)
100
+ // HTTP path: round-robin across instances that have advertised addresses; within the
101
+ // chosen instance, round-robin across addresses (loopback-prefer kept by isLoopbackAddress).
102
+ // NATS handles the fallback when no usable instance exists or the HTTP attempt fails.
97
103
  const entries = registry.getEntriesForIdentifier(identifier);
98
- if (entries.length === 1) {
99
- const addresses = entries[0].a || [];
100
- if (addresses.length > 0) {
101
- const loopback = addresses.filter(isLoopbackAddress);
102
- const remote = addresses.filter((a) => !isLoopbackAddress(a));
103
- const pool = loopback.length > 0 ? loopback : remote;
104
- const address = pool[_httpRpcRoundRobin++ % pool.length];
105
-
106
- const url = `http://${address}/!!_gx/rpc/${hash(entries[0].i)}`;
107
- const rpcPayload = { m: method, a: args, c: context, o: originator };
108
- const fetchBody = _payloadKey
109
- ? encryptPayload(Buffer.from(JSON.stringify(rpcPayload)))
110
- : JSON.stringify(rpcPayload);
111
- const contentType = _payloadKey ? "application/octet-stream" : "application/json";
112
-
113
- let httpResponse;
114
- try {
115
- const res = await fetchWithTimeout(
116
- url,
117
- {
118
- method: "POST",
119
- headers: { "content-type": contentType },
120
- body: fetchBody,
121
- },
122
- options?.httpTimeout ?? 5000,
123
- );
124
- if (res.ok) {
125
- httpResponse = _payloadKey
126
- ? JSON.parse(decryptPayload(Buffer.from(await res.arrayBuffer())))
127
- : await res.json();
128
- }
129
- } catch (e) {
130
- logger.debug("directRequest: HTTP RPC failed, falling back to NATS", address, e.message);
104
+ const usable = entries.filter((e) => e.a?.length > 0);
105
+ if (usable.length > 0) {
106
+ const instance = usable[_httpInstanceRoundRobin++ % usable.length];
107
+ const loopback = instance.a.filter(isLoopbackAddress);
108
+ const remote = instance.a.filter((a) => !isLoopbackAddress(a));
109
+ const pool = loopback.length > 0 ? loopback : remote;
110
+ const address = pool[_httpRpcRoundRobin++ % pool.length];
111
+
112
+ const url = `http://${address}/!!_gx/rpc/${hash(instance.i)}`;
113
+ const rpcPayload = { m: method, a: args, c: context, o: originator };
114
+ const fetchBody = _payloadKey
115
+ ? encryptPayload(Buffer.from(JSON.stringify(rpcPayload)))
116
+ : JSON.stringify(rpcPayload);
117
+ const contentType = _payloadKey ? "application/octet-stream" : "application/json";
118
+
119
+ let httpResponse;
120
+ try {
121
+ const res = await fetchWithTimeout(
122
+ url,
123
+ {
124
+ method: "POST",
125
+ headers: { "content-type": contentType },
126
+ body: fetchBody,
127
+ },
128
+ options?.httpTimeout ?? DEFAULT_HTTP_TIMEOUT,
129
+ );
130
+ if (res.ok) {
131
+ httpResponse = _payloadKey
132
+ ? JSON.parse(decryptPayload(Buffer.from(await res.arrayBuffer())))
133
+ : await res.json();
131
134
  }
135
+ } catch (e) {
136
+ logger.debug("directRequest: HTTP RPC failed, falling back to NATS", address, e.message);
137
+ }
132
138
 
133
- if (httpResponse) {
134
- if (httpResponse.e) {
135
- throw Error(`Request: remote error: ${httpResponse.e}`);
136
- }
137
- if (isStream(httpResponse.r)) {
138
- return JSON.parse(await streamToString(httpResponse.r));
139
- }
140
- return httpResponse.r;
139
+ if (httpResponse) {
140
+ if (httpResponse.e) {
141
+ throw Error(`Request: remote error: ${httpResponse.e}`);
142
+ }
143
+ if (isStream(httpResponse.r)) {
144
+ return JSON.parse(await streamToString(httpResponse.r));
141
145
  }
146
+ return httpResponse.r;
142
147
  }
143
148
  }
144
149