@artilleryio/int-core 2.25.0 → 2.26.0-93f0ca6

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.
@@ -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 * 1000,
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
- request(_.omit(requestParams, ['uuid']))
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
- return callback(taskErr, context); // calling back for now for existing client code
1057
+ if (callback) {
1058
+ return callback(taskErr, context);
1059
+ }
1060
+ throw taskErr;
941
1061
  }
942
1062
  }
943
- return callback(null, context);
1063
+ if (callback) {
1064
+ return callback(null, context);
1065
+ }
1066
+ return context;
944
1067
  };
945
1068
  };
946
1069
 
@@ -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.25.0",
3
+ "version": "2.26.0-93f0ca6",
4
4
  "main": "./index.js",
5
5
  "license": "MPL-2.0",
6
6
  "dependencies": {
7
- "@artilleryio/int-commons": "2.21.0",
7
+ "@artilleryio/int-commons": "2.22.0-93f0ca6",
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.1.2",
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": "^11.8.5",
25
+ "got": "^14.6.6",
26
26
  "hpagent": "^0.1.1",
27
27
  "https-proxy-agent": "^5.0.0",
28
- "lodash": "^4.17.21",
28
+ "lodash": "^4.18.0",
29
29
  "ms": "^2.1.3",
30
- "protobufjs": "^7.5.4",
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",