soajs.controller 4.3.1 → 4.3.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.
@@ -6,9 +6,14 @@ on:
6
6
  pull_request:
7
7
  branches: [ master, develop ]
8
8
 
9
+ env:
10
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
11
+
9
12
  jobs:
10
13
  test:
11
14
  runs-on: ubuntu-latest
15
+ env:
16
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
12
17
 
13
18
  services:
14
19
  mongodb:
@@ -18,10 +23,10 @@ jobs:
18
23
 
19
24
  steps:
20
25
  - name: Checkout code
21
- uses: actions/checkout@v4
26
+ uses: actions/checkout@v4.2.2
22
27
 
23
28
  - name: Setup Node.js
24
- uses: actions/setup-node@v4
29
+ uses: actions/setup-node@v4.4.0
25
30
  with:
26
31
  node-version: '24'
27
32
 
@@ -6,9 +6,14 @@ on:
6
6
  - release
7
7
  - 'release/**'
8
8
 
9
+ env:
10
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
11
+
9
12
  jobs:
10
13
  test:
11
14
  runs-on: ubuntu-latest
15
+ env:
16
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
12
17
 
13
18
  services:
14
19
  mongodb:
@@ -18,10 +23,10 @@ jobs:
18
23
 
19
24
  steps:
20
25
  - name: Checkout code
21
- uses: actions/checkout@v4
26
+ uses: actions/checkout@v4.2.2
22
27
 
23
28
  - name: Setup Node.js
24
- uses: actions/setup-node@v4
29
+ uses: actions/setup-node@v4.4.0
25
30
  with:
26
31
  node-version: '24'
27
32
 
@@ -40,10 +45,12 @@ jobs:
40
45
  publish-docker:
41
46
  runs-on: ubuntu-latest
42
47
  needs: test
48
+ env:
49
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
43
50
 
44
51
  steps:
45
52
  - name: Checkout code
46
- uses: actions/checkout@v4
53
+ uses: actions/checkout@v4.2.2
47
54
 
48
55
  - name: Extract version from package.json
49
56
  id: version
@@ -85,7 +92,7 @@ jobs:
85
92
  run: git push origin ${{ steps.version.outputs.PACKAGE_VERSION }} || true
86
93
 
87
94
  - name: Create GitHub Release
88
- uses: softprops/action-gh-release@v1
95
+ uses: softprops/action-gh-release@v2
89
96
  with:
90
97
  tag_name: ${{ steps.version.outputs.PACKAGE_VERSION }}
91
98
  name: Release ${{ steps.version.outputs.PACKAGE_VERSION }}
@@ -12,6 +12,121 @@ const get = (p, o) => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o);
12
12
 
13
13
  const { httpRequestLight, proxyRequestMonitor } = require("../../lib/request.js");
14
14
 
15
+ // Monitor buffer for batch uploads
16
+ let monitorBuffer = [];
17
+ let monitorFlushTimer = null;
18
+ let monitorFlushInProgress = false;
19
+ let lastReqForFlush = null;
20
+
21
+ /**
22
+ * Flush the monitor buffer to soamonitor
23
+ */
24
+ function flushMonitorBuffer() {
25
+ if (monitorFlushInProgress || monitorBuffer.length === 0 || !lastReqForFlush) {
26
+ return;
27
+ }
28
+
29
+ monitorFlushInProgress = true;
30
+
31
+ // Clear timer if exists
32
+ if (monitorFlushTimer) {
33
+ clearTimeout(monitorFlushTimer);
34
+ monitorFlushTimer = null;
35
+ }
36
+
37
+ // Take current buffer and reset
38
+ let itemsToSend = monitorBuffer;
39
+ let req = lastReqForFlush;
40
+ monitorBuffer = [];
41
+
42
+ let soamonitor = "soamonitor";
43
+ let soamonitorVersion = "1";
44
+ let port = get(["registry", "services", "soamonitor", "port"], req.soajs) || "4050";
45
+ let api = "/monitor/items"; // Batch endpoint
46
+
47
+ req.soajs.awareness.getHost(soamonitor, soamonitorVersion, function (host) {
48
+ monitorFlushInProgress = false;
49
+
50
+ if (!host) {
51
+ req.soajs.log.debug('Monitor buffer flush: Unable to find host for ' + soamonitor);
52
+ // Put items back in buffer for retry
53
+ monitorBuffer = itemsToSend.concat(monitorBuffer);
54
+ return;
55
+ }
56
+
57
+ // Convert body/response to strings
58
+ itemsToSend.forEach(doc => {
59
+ if (doc.body) {
60
+ doc.body = doc.body.toString();
61
+ }
62
+ if (doc.response) {
63
+ doc.response = doc.response.toString();
64
+ }
65
+ });
66
+
67
+ let uri = "http://" + host + ":" + port + api;
68
+ let options = {
69
+ "method": "POST",
70
+ "uri": uri,
71
+ "data": { "items": itemsToSend },
72
+ "json": true
73
+ };
74
+
75
+ httpRequestLight(options)
76
+ .then((body) => {
77
+ if (body && !body.result) {
78
+ req.soajs.log.debug('Monitor buffer flush response:', body);
79
+ } else {
80
+ req.soajs.log.debug('Monitor buffer flushed: ' + itemsToSend.length + ' items');
81
+ }
82
+ })
83
+ .catch((error) => {
84
+ req.soajs.log.debug('Monitor buffer flush failed: ' + error.message);
85
+ });
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Add item to monitor buffer and trigger flush if needed
91
+ * @param {Object} req - Request object
92
+ * @param {Object} doc - Monitor document to buffer
93
+ * @param {Object} monitor - Monitor configuration
94
+ */
95
+ function bufferMonitorItem(req, doc, monitor) {
96
+ monitorBuffer.push(doc);
97
+ lastReqForFlush = req;
98
+
99
+ let bufferConfig = monitor.buffer || {};
100
+ let bufferTime = bufferConfig.time;
101
+ let bufferLimit = bufferConfig.limit;
102
+
103
+ // Check count threshold
104
+ if (bufferLimit && monitorBuffer.length >= bufferLimit) {
105
+ flushMonitorBuffer();
106
+ return;
107
+ }
108
+
109
+ // Start timer if not already running and time threshold is set
110
+ if (bufferTime && !monitorFlushTimer) {
111
+ monitorFlushTimer = setTimeout(() => {
112
+ monitorFlushTimer = null;
113
+ flushMonitorBuffer();
114
+ }, bufferTime);
115
+ }
116
+ }
117
+
118
+ // Graceful shutdown - flush remaining buffer
119
+ process.on('SIGTERM', () => {
120
+ if (monitorBuffer.length > 0 && !monitorFlushInProgress) {
121
+ // Best-effort cleanup
122
+ monitorBuffer = [];
123
+ if (monitorFlushTimer) {
124
+ clearTimeout(monitorFlushTimer);
125
+ monitorFlushTimer = null;
126
+ }
127
+ }
128
+ });
129
+
15
130
  module.exports = (configuration) => {
16
131
 
17
132
  let core = configuration.core;
@@ -26,12 +141,21 @@ module.exports = (configuration) => {
26
141
  */
27
142
  return (req, res) => {
28
143
  preRedirect(req, res, core, function (obj) {
144
+ let monitor = get(["registry", "custom", "gateway", "value", "gotoService", "monitor"], req.soajs);
29
145
  let log_monitor_triggered = false;
30
146
  let log_monitor = (doc) => {
31
147
  if (log_monitor_triggered) {
32
148
  return;
33
149
  }
34
150
  log_monitor_triggered = true;
151
+
152
+ // Check if buffering is enabled
153
+ if (monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit)) {
154
+ bufferMonitorItem(req, doc, monitor);
155
+ return;
156
+ }
157
+
158
+ // Original immediate send logic (unchanged)
35
159
  let soamonitor = "soamonitor";
36
160
  let soamonitorVersion = "1";
37
161
  let port = get(["registry", "services", "soamonitor", "port"], req.soajs);
@@ -72,8 +196,6 @@ module.exports = (configuration) => {
72
196
  }
73
197
  });
74
198
  };
75
-
76
- let monitor = get(["registry", "custom", "gateway", "value", "gotoService", "monitor"], req.soajs);
77
199
  let monitoObj = {
78
200
  "time": {}
79
201
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "soajs.controller",
3
3
  "description": "soajs multi tenant API gateway",
4
- "version": "4.3.1",
4
+ "version": "4.3.2",
5
5
  "author": {
6
6
  "name": "soajs team",
7
7
  "email": "team@soajs.org"
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * @license
5
+ * Copyright SOAJS All Rights Reserved.
6
+ *
7
+ * Use of this source code is governed by an Apache license that can be
8
+ * found in the LICENSE file at the root of this repository
9
+ */
10
+
11
+ const assert = require('assert');
12
+
13
+ describe("Unit test for: mw - gotoService monitor buffer", function () {
14
+
15
+ // We need to test the buffer module functions directly
16
+ // Since they are module-level in redirectToService.js, we'll test the logic patterns
17
+
18
+ describe("Buffer configuration parsing", function () {
19
+
20
+ it("should recognize buffer config with time only", function (done) {
21
+ let monitor = {
22
+ "buffer": {
23
+ "time": 5000
24
+ }
25
+ };
26
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
27
+ assert.strictEqual(hasBuffer, true);
28
+ done();
29
+ });
30
+
31
+ it("should recognize buffer config with limit only", function (done) {
32
+ let monitor = {
33
+ "buffer": {
34
+ "limit": 100
35
+ }
36
+ };
37
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
38
+ assert.strictEqual(hasBuffer, true);
39
+ done();
40
+ });
41
+
42
+ it("should recognize buffer config with both time and limit", function (done) {
43
+ let monitor = {
44
+ "buffer": {
45
+ "time": 5000,
46
+ "limit": 100
47
+ }
48
+ };
49
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
50
+ assert.strictEqual(hasBuffer, true);
51
+ done();
52
+ });
53
+
54
+ it("should not recognize buffer config when buffer is not set", function (done) {
55
+ let monitor = {
56
+ "req_info": true
57
+ };
58
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
59
+ assert.strictEqual(!!hasBuffer, false);
60
+ done();
61
+ });
62
+
63
+ it("should not recognize buffer config when buffer is empty", function (done) {
64
+ let monitor = {
65
+ "buffer": {}
66
+ };
67
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
68
+ assert.strictEqual(!!hasBuffer, false);
69
+ done();
70
+ });
71
+
72
+ it("should not recognize buffer when monitor is null", function (done) {
73
+ let monitor = null;
74
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
75
+ assert.strictEqual(!!hasBuffer, false);
76
+ done();
77
+ });
78
+ });
79
+
80
+ describe("Buffer limit threshold logic", function () {
81
+
82
+ it("should trigger flush when buffer reaches limit", function (done) {
83
+ let monitorBuffer = [];
84
+ let bufferLimit = 3;
85
+
86
+ // Simulate adding items
87
+ for (let i = 0; i < 3; i++) {
88
+ monitorBuffer.push({ name: "test", api: "/test" + i });
89
+ }
90
+
91
+ let shouldFlush = bufferLimit && monitorBuffer.length >= bufferLimit;
92
+ assert.strictEqual(shouldFlush, true);
93
+ done();
94
+ });
95
+
96
+ it("should not trigger flush when buffer is below limit", function (done) {
97
+ let monitorBuffer = [];
98
+ let bufferLimit = 10;
99
+
100
+ // Simulate adding items
101
+ for (let i = 0; i < 5; i++) {
102
+ monitorBuffer.push({ name: "test", api: "/test" + i });
103
+ }
104
+
105
+ let shouldFlush = bufferLimit && monitorBuffer.length >= bufferLimit;
106
+ assert.strictEqual(shouldFlush, false);
107
+ done();
108
+ });
109
+
110
+ it("should not trigger flush when no limit is set", function (done) {
111
+ let monitorBuffer = [];
112
+ let bufferLimit = null;
113
+
114
+ // Simulate adding items
115
+ for (let i = 0; i < 100; i++) {
116
+ monitorBuffer.push({ name: "test", api: "/test" + i });
117
+ }
118
+
119
+ let shouldFlush = bufferLimit && monitorBuffer.length >= bufferLimit;
120
+ assert.strictEqual(shouldFlush, false);
121
+ done();
122
+ });
123
+ });
124
+
125
+ describe("Buffer time threshold logic", function () {
126
+
127
+ it("should start timer when time threshold is set and no timer exists", function (done) {
128
+ let monitorFlushTimer = null;
129
+ let bufferTime = 5000;
130
+
131
+ let shouldStartTimer = bufferTime && !monitorFlushTimer;
132
+ assert.strictEqual(shouldStartTimer, true);
133
+ done();
134
+ });
135
+
136
+ it("should not start timer when timer already exists", function (done) {
137
+ let monitorFlushTimer = setTimeout(() => {}, 5000);
138
+ let bufferTime = 5000;
139
+
140
+ let shouldStartTimer = bufferTime && !monitorFlushTimer;
141
+ assert.strictEqual(shouldStartTimer, false);
142
+
143
+ clearTimeout(monitorFlushTimer);
144
+ done();
145
+ });
146
+
147
+ it("should not start timer when no time threshold is set", function (done) {
148
+ let monitorFlushTimer = null;
149
+ let bufferTime = null;
150
+
151
+ let shouldStartTimer = bufferTime && !monitorFlushTimer;
152
+ assert.strictEqual(!!shouldStartTimer, false);
153
+ done();
154
+ });
155
+ });
156
+
157
+ describe("Buffer flush conditions", function () {
158
+
159
+ it("should not flush when buffer is empty", function (done) {
160
+ let monitorBuffer = [];
161
+ let monitorFlushInProgress = false;
162
+
163
+ let shouldFlush = !(monitorFlushInProgress || monitorBuffer.length === 0);
164
+ assert.strictEqual(shouldFlush, false);
165
+ done();
166
+ });
167
+
168
+ it("should not flush when flush is already in progress", function (done) {
169
+ let monitorBuffer = [{ name: "test" }];
170
+ let monitorFlushInProgress = true;
171
+
172
+ let shouldFlush = !(monitorFlushInProgress || monitorBuffer.length === 0);
173
+ assert.strictEqual(shouldFlush, false);
174
+ done();
175
+ });
176
+
177
+ it("should flush when buffer has items and no flush in progress", function (done) {
178
+ let monitorBuffer = [{ name: "test" }];
179
+ let monitorFlushInProgress = false;
180
+
181
+ let shouldFlush = !(monitorFlushInProgress || monitorBuffer.length === 0);
182
+ assert.strictEqual(shouldFlush, true);
183
+ done();
184
+ });
185
+ });
186
+
187
+ describe("Monitor document transformation", function () {
188
+
189
+ it("should convert body to string before sending", function (done) {
190
+ let doc = {
191
+ name: "test",
192
+ body: Buffer.from("test body")
193
+ };
194
+
195
+ if (doc.body) {
196
+ doc.body = doc.body.toString();
197
+ }
198
+
199
+ assert.strictEqual(typeof doc.body, "string");
200
+ assert.strictEqual(doc.body, "test body");
201
+ done();
202
+ });
203
+
204
+ it("should convert response to string before sending", function (done) {
205
+ let doc = {
206
+ name: "test",
207
+ response: Buffer.from('{"result": true}')
208
+ };
209
+
210
+ if (doc.response) {
211
+ doc.response = doc.response.toString();
212
+ }
213
+
214
+ assert.strictEqual(typeof doc.response, "string");
215
+ assert.strictEqual(doc.response, '{"result": true}');
216
+ done();
217
+ });
218
+
219
+ it("should handle doc without body or response", function (done) {
220
+ let doc = {
221
+ name: "test",
222
+ api: "/test"
223
+ };
224
+
225
+ if (doc.body) {
226
+ doc.body = doc.body.toString();
227
+ }
228
+ if (doc.response) {
229
+ doc.response = doc.response.toString();
230
+ }
231
+
232
+ assert.strictEqual(doc.body, undefined);
233
+ assert.strictEqual(doc.response, undefined);
234
+ done();
235
+ });
236
+ });
237
+
238
+ describe("Backward compatibility", function () {
239
+
240
+ it("should send immediately when no buffer config (backward compatible)", function (done) {
241
+ let monitor = {
242
+ "req_info": true,
243
+ "req_response": true
244
+ };
245
+
246
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
247
+ let shouldSendImmediately = !hasBuffer;
248
+
249
+ assert.strictEqual(shouldSendImmediately, true);
250
+ done();
251
+ });
252
+
253
+ it("should use buffer when buffer config is present", function (done) {
254
+ let monitor = {
255
+ "req_info": true,
256
+ "req_response": true,
257
+ "buffer": {
258
+ "time": 5000,
259
+ "limit": 100
260
+ }
261
+ };
262
+
263
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
264
+ let shouldSendImmediately = !hasBuffer;
265
+
266
+ assert.strictEqual(shouldSendImmediately, false);
267
+ done();
268
+ });
269
+ });
270
+
271
+ describe("Buffer retry logic", function () {
272
+
273
+ it("should put items back in buffer when host not found", function (done) {
274
+ let monitorBuffer = [];
275
+ let itemsToSend = [{ name: "test1" }, { name: "test2" }];
276
+ let host = null;
277
+
278
+ // Simulate host not found - items should go back to buffer
279
+ if (!host) {
280
+ monitorBuffer = itemsToSend.concat(monitorBuffer);
281
+ }
282
+
283
+ assert.strictEqual(monitorBuffer.length, 2);
284
+ assert.strictEqual(monitorBuffer[0].name, "test1");
285
+ done();
286
+ });
287
+
288
+ it("should preserve order when putting items back", function (done) {
289
+ let monitorBuffer = [{ name: "new1" }];
290
+ let itemsToSend = [{ name: "old1" }, { name: "old2" }];
291
+ let host = null;
292
+
293
+ // Simulate host not found - items should go back to buffer
294
+ if (!host) {
295
+ monitorBuffer = itemsToSend.concat(monitorBuffer);
296
+ }
297
+
298
+ assert.strictEqual(monitorBuffer.length, 3);
299
+ assert.strictEqual(monitorBuffer[0].name, "old1");
300
+ assert.strictEqual(monitorBuffer[1].name, "old2");
301
+ assert.strictEqual(monitorBuffer[2].name, "new1");
302
+ done();
303
+ });
304
+ });
305
+
306
+ });