@artilleryio/int-core 2.25.0 → 2.26.0-480c63a
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.
- package/lib/engine_http.js +129 -6
- package/lib/engine_socketio.js +4 -0
- package/lib/runner.js +22 -1
- package/package.json +6 -6
package/lib/engine_http.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
const async = require('async');
|
|
6
6
|
const _ = require('lodash');
|
|
7
|
-
const request = require('got');
|
|
8
7
|
const tough = require('tough-cookie');
|
|
9
8
|
const debug = require('debug')('http');
|
|
10
9
|
const debugRequests = require('debug')('http:request');
|
|
@@ -30,11 +29,50 @@ const crypto = require('node:crypto');
|
|
|
30
29
|
|
|
31
30
|
module.exports = HttpEngine;
|
|
32
31
|
|
|
32
|
+
const GOT_OPTION_NAMES = [
|
|
33
|
+
'url',
|
|
34
|
+
'searchParams',
|
|
35
|
+
'method',
|
|
36
|
+
'headers',
|
|
37
|
+
'body',
|
|
38
|
+
'json',
|
|
39
|
+
'form',
|
|
40
|
+
'allowGetBody',
|
|
41
|
+
'timeout',
|
|
42
|
+
'retry',
|
|
43
|
+
'encoding',
|
|
44
|
+
'cookieJar',
|
|
45
|
+
'followRedirect',
|
|
46
|
+
'maxRedirects',
|
|
47
|
+
'decompress',
|
|
48
|
+
'http2',
|
|
49
|
+
'agent',
|
|
50
|
+
'username',
|
|
51
|
+
'password',
|
|
52
|
+
'https',
|
|
53
|
+
'throwHttpErrors'
|
|
54
|
+
];
|
|
55
|
+
|
|
33
56
|
const DEFAULT_AGENT_OPTIONS = {
|
|
34
57
|
keepAlive: true,
|
|
35
58
|
keepAliveMsec: 1000
|
|
36
59
|
};
|
|
37
60
|
|
|
61
|
+
// agentkeepalive defaults `timeout` (active socket inactivity) to a hardcoded
|
|
62
|
+
// floor of 8000ms (Math.max(freeSocketTimeout * 2, 8000)). When a request's
|
|
63
|
+
// origin doesn't send response bytes within 8s, the socket is destroyed with
|
|
64
|
+
// `error.code = 'ERR_SOCKET_TIMEOUT'` regardless of the user's configured
|
|
65
|
+
// `http.timeout`. Mirror `http.timeout` (or top-level `timeout`) into the agent
|
|
66
|
+
// so the YAML config becomes the actual binding constraint.
|
|
67
|
+
function deriveAgentTimeoutMs(scriptConfig) {
|
|
68
|
+
const timeoutSec =
|
|
69
|
+
scriptConfig?.timeout || scriptConfig?.http?.timeout;
|
|
70
|
+
if (typeof timeoutSec !== 'number') return undefined;
|
|
71
|
+
// Never go below the existing 8s floor — preserves current behaviour for
|
|
72
|
+
// users who explicitly configure a smaller http.timeout.
|
|
73
|
+
return Math.max(8000, timeoutSec * 1000);
|
|
74
|
+
}
|
|
75
|
+
|
|
38
76
|
function createAgents(proxies, opts) {
|
|
39
77
|
const agentOpts = Object.assign({}, DEFAULT_AGENT_OPTIONS, opts);
|
|
40
78
|
|
|
@@ -106,6 +144,10 @@ function HttpEngine(script) {
|
|
|
106
144
|
maxSockets: this.maxSockets,
|
|
107
145
|
maxFreeSockets: this.maxSockets
|
|
108
146
|
});
|
|
147
|
+
const agentTimeoutMs = deriveAgentTimeoutMs(script.config);
|
|
148
|
+
if (agentTimeoutMs !== undefined) {
|
|
149
|
+
agentOpts.timeout = agentTimeoutMs;
|
|
150
|
+
}
|
|
109
151
|
|
|
110
152
|
const agents = createAgents(
|
|
111
153
|
{
|
|
@@ -126,6 +168,66 @@ function HttpEngine(script) {
|
|
|
126
168
|
}
|
|
127
169
|
}
|
|
128
170
|
|
|
171
|
+
HttpEngine.prototype.init = async function () {
|
|
172
|
+
this.request = (await import('got')).default;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
HttpEngine.prototype._isDistributedTracingEnabled = function (config) {
|
|
176
|
+
const dtConfig = config.http?.distributedTracing;
|
|
177
|
+
if (!dtConfig) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Handle both boolean and object forms
|
|
182
|
+
if (typeof dtConfig === 'boolean') {
|
|
183
|
+
return dtConfig;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (typeof dtConfig === 'object' && dtConfig.enabled !== undefined) {
|
|
187
|
+
return dtConfig.enabled;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Default to true if distributedTracing is set but enabled is not specified
|
|
191
|
+
return true;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
HttpEngine.prototype._generateTraceparent = function (config) {
|
|
195
|
+
// W3C Trace Context format: version-trace-id-parent-id-trace-flags
|
|
196
|
+
const version = '00';
|
|
197
|
+
|
|
198
|
+
// Get configuration
|
|
199
|
+
const dtConfig = config.http?.distributedTracing;
|
|
200
|
+
let sampled = true; // Default to sampled
|
|
201
|
+
let traceIdPrefix = 'a9'; // Default prefix
|
|
202
|
+
|
|
203
|
+
if (typeof dtConfig === 'object') {
|
|
204
|
+
if (dtConfig.sampled !== undefined) {
|
|
205
|
+
sampled = dtConfig.sampled;
|
|
206
|
+
}
|
|
207
|
+
if (dtConfig.traceIdPrefix !== undefined) {
|
|
208
|
+
traceIdPrefix = dtConfig.traceIdPrefix;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Validate and normalize prefix (must be valid hex, max 8 chars)
|
|
213
|
+
traceIdPrefix = traceIdPrefix.toLowerCase().replace(/[^0-9a-f]/g, '').slice(0, 8);
|
|
214
|
+
if (traceIdPrefix.length === 0) {
|
|
215
|
+
traceIdPrefix = 'a9'; // Fallback to default if invalid
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Generate trace-id with prefix (32 hex chars total)
|
|
219
|
+
const remainingBytes = Math.ceil((32 - traceIdPrefix.length) / 2);
|
|
220
|
+
const randomPart = crypto.randomBytes(remainingBytes).toString('hex');
|
|
221
|
+
const traceId = (traceIdPrefix + randomPart).slice(0, 32);
|
|
222
|
+
|
|
223
|
+
// Generate 8-byte parent-id (16 hex chars)
|
|
224
|
+
const parentId = crypto.randomBytes(8).toString('hex');
|
|
225
|
+
|
|
226
|
+
const traceFlags = sampled ? '01' : '00';
|
|
227
|
+
|
|
228
|
+
return `${version}-${traceId}-${parentId}-${traceFlags}`;
|
|
229
|
+
};
|
|
230
|
+
|
|
129
231
|
HttpEngine.prototype.createScenario = function (scenarioSpec, ee) {
|
|
130
232
|
ensurePropertyIsAList(scenarioSpec, 'beforeRequest');
|
|
131
233
|
ensurePropertyIsAList(scenarioSpec, 'afterResponse');
|
|
@@ -274,7 +376,7 @@ HttpEngine.prototype.step = function step(requestSpec, ee, opts) {
|
|
|
274
376
|
const requestParams = _.extend(_.clone(params), {
|
|
275
377
|
url: maybePrependBase(params.url || params.uri, config), // *NOT* templating here
|
|
276
378
|
method: method,
|
|
277
|
-
timeout: timeout
|
|
379
|
+
timeout: timeout,
|
|
278
380
|
uuid: crypto.randomUUID()
|
|
279
381
|
});
|
|
280
382
|
|
|
@@ -683,10 +785,21 @@ HttpEngine.prototype.step = function step(requestSpec, ee, opts) {
|
|
|
683
785
|
);
|
|
684
786
|
}
|
|
685
787
|
|
|
686
|
-
requestParams.retry = 0; // disable retries - ignored when using streams
|
|
788
|
+
requestParams.retry = { limit: 0 }; // disable retries - ignored when using streams
|
|
789
|
+
// Convert scalar seconds to Got v14 timeout object right before request
|
|
790
|
+
const gotOptions = _.pick(requestParams, GOT_OPTION_NAMES);
|
|
791
|
+
gotOptions.timeout = { response: requestParams.timeout * 1000 };
|
|
792
|
+
|
|
793
|
+
// Add W3C Trace Context headers if distributed tracing is enabled
|
|
794
|
+
if (self._isDistributedTracingEnabled(config)) {
|
|
795
|
+
const traceparent = self._generateTraceparent(config);
|
|
796
|
+
gotOptions.headers = gotOptions.headers || {};
|
|
797
|
+
gotOptions.headers.traceparent = traceparent;
|
|
798
|
+
}
|
|
687
799
|
|
|
688
800
|
let totalDownloaded = 0;
|
|
689
|
-
|
|
801
|
+
self
|
|
802
|
+
.request(gotOptions)
|
|
690
803
|
.on('request', (req) => {
|
|
691
804
|
ee.emit('trace:http:request', requestParams, requestParams.uuid);
|
|
692
805
|
|
|
@@ -909,6 +1022,10 @@ HttpEngine.prototype.setInitialContext = function (initialContext) {
|
|
|
909
1022
|
maxSockets: 1,
|
|
910
1023
|
maxFreeSockets: 1
|
|
911
1024
|
});
|
|
1025
|
+
const agentTimeoutMs = deriveAgentTimeoutMs(this.config);
|
|
1026
|
+
if (agentTimeoutMs !== undefined) {
|
|
1027
|
+
agentOpts.timeout = agentTimeoutMs;
|
|
1028
|
+
}
|
|
912
1029
|
|
|
913
1030
|
const agents = createAgents(
|
|
914
1031
|
{
|
|
@@ -937,10 +1054,16 @@ HttpEngine.prototype.compile = function compile(tasks, _scenarioSpec, ee) {
|
|
|
937
1054
|
context = await promisify(task)(context);
|
|
938
1055
|
} catch (taskErr) {
|
|
939
1056
|
ee.emit('error', taskErr.code || taskErr.message);
|
|
940
|
-
|
|
1057
|
+
if (callback) {
|
|
1058
|
+
return callback(taskErr, context);
|
|
1059
|
+
}
|
|
1060
|
+
throw taskErr;
|
|
941
1061
|
}
|
|
942
1062
|
}
|
|
943
|
-
|
|
1063
|
+
if (callback) {
|
|
1064
|
+
return callback(null, context);
|
|
1065
|
+
}
|
|
1066
|
+
return context;
|
|
944
1067
|
};
|
|
945
1068
|
};
|
|
946
1069
|
|
package/lib/engine_socketio.js
CHANGED
|
@@ -23,6 +23,10 @@ function SocketIoEngine(script) {
|
|
|
23
23
|
this.httpDelegate = new EngineHttp(script);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
SocketIoEngine.prototype.init = async function () {
|
|
27
|
+
await this.httpDelegate.init();
|
|
28
|
+
};
|
|
29
|
+
|
|
26
30
|
SocketIoEngine.prototype.createScenario = function (scenarioSpec, ee) {
|
|
27
31
|
// Adds scenario overridden configuration into the static config
|
|
28
32
|
this.socketioOpts = { ...this.socketioOpts, ...scenarioSpec.socketio };
|
package/lib/runner.js
CHANGED
|
@@ -163,6 +163,16 @@ async function runner(script, payload, options, callback) {
|
|
|
163
163
|
warnings
|
|
164
164
|
);
|
|
165
165
|
|
|
166
|
+
for (const e of runnerEngines) {
|
|
167
|
+
if (
|
|
168
|
+
e &&
|
|
169
|
+
typeof e.init === 'function' &&
|
|
170
|
+
e.init.constructor.name === 'AsyncFunction'
|
|
171
|
+
) {
|
|
172
|
+
await e.init();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
166
176
|
const promise = new Promise((resolve, _reject) => {
|
|
167
177
|
ee.run = (contextVars) => {
|
|
168
178
|
const runState = {
|
|
@@ -506,12 +516,23 @@ function $randomString(length = 10) {
|
|
|
506
516
|
return s;
|
|
507
517
|
}
|
|
508
518
|
|
|
509
|
-
function handleScriptHook(hook, script, hookEvents, contextVars = {}) {
|
|
519
|
+
async function handleScriptHook(hook, script, hookEvents, contextVars = {}) {
|
|
510
520
|
if (!script[hook]) {
|
|
511
521
|
return {};
|
|
512
522
|
}
|
|
513
523
|
|
|
514
524
|
const { loadedEngines: engines } = loadEngines(script, hookEvents);
|
|
525
|
+
|
|
526
|
+
for (const e of engines) {
|
|
527
|
+
if (
|
|
528
|
+
e &&
|
|
529
|
+
typeof e.init === 'function' &&
|
|
530
|
+
e.init.constructor.name === 'AsyncFunction'
|
|
531
|
+
) {
|
|
532
|
+
await e.init();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
515
536
|
const ee = new EventEmitter();
|
|
516
537
|
|
|
517
538
|
return new Promise((resolve, reject) => {
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@artilleryio/int-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.26.0-480c63a",
|
|
4
4
|
"main": "./index.js",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@artilleryio/int-commons": "2.
|
|
7
|
+
"@artilleryio/int-commons": "2.22.0-480c63a",
|
|
8
8
|
"@artilleryio/sketches-js": "^2.1.1",
|
|
9
9
|
"agentkeepalive": "^4.6.0",
|
|
10
10
|
"arrivals": "^2.1.2",
|
|
11
11
|
"async": "^2.6.4",
|
|
12
12
|
"chalk": "^2.4.2",
|
|
13
|
-
"cheerio": "^1.
|
|
13
|
+
"cheerio": "^1.2.0",
|
|
14
14
|
"cookie-parser": "^1.4.7",
|
|
15
15
|
"csv-parse": "^4.16.3",
|
|
16
16
|
"debug": "^4.4.3",
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
"fast-deep-equal": "^3.1.3",
|
|
23
23
|
"filtrex": "^0.5.4",
|
|
24
24
|
"form-data": "^4.0.5",
|
|
25
|
-
"got": "^
|
|
25
|
+
"got": "^14.6.6",
|
|
26
26
|
"hpagent": "^0.1.1",
|
|
27
27
|
"https-proxy-agent": "^5.0.0",
|
|
28
|
-
"lodash": "^4.
|
|
28
|
+
"lodash": "^4.18.0",
|
|
29
29
|
"ms": "^2.1.3",
|
|
30
|
-
"protobufjs": "^7.5.
|
|
30
|
+
"protobufjs": "^7.5.5",
|
|
31
31
|
"socket.io-client": "^4.8.3",
|
|
32
32
|
"socketio-wildcard": "^2.0.0",
|
|
33
33
|
"tough-cookie": "^5.1.2",
|