@loadmill/executer 0.1.37 → 0.1.38
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/dist/errors.d.ts +5 -0
- package/dist/errors.js +38 -0
- package/dist/errors.js.map +1 -0
- package/dist/extraction-combiner.d.ts +4 -2
- package/dist/extraction-combiner.js +96 -46
- package/dist/extraction-combiner.js.map +1 -1
- package/dist/mill-version.js +1 -1
- package/dist/sequence.js +249 -112
- package/dist/sequence.js.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +29 -0
- package/dist/utils.js.map +1 -0
- package/dist/ws.d.ts +66 -0
- package/dist/ws.js +435 -0
- package/dist/ws.js.map +1 -0
- package/package.json +4 -4
- package/src/errors.ts +10 -0
- package/src/extraction-combiner.ts +15 -4
- package/src/mill-version.ts +1 -1
- package/src/sequence.ts +101 -33
- package/src/utils.ts +8 -0
- package/src/ws.ts +233 -0
package/dist/ws.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.js","sourceRoot":"","sources":["../src/ws.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loadmill/executer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.38",
|
|
4
4
|
"description": "Loadmill executer library",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"ts-watch": "rimraf dist && tsc -w -p tsconfig.json",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
},
|
|
15
15
|
"license": "Apache-2.0",
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@loadmill/core": "0.3.
|
|
18
|
-
"@loadmill/universal": "0.3.
|
|
17
|
+
"@loadmill/core": "0.3.37",
|
|
18
|
+
"@loadmill/universal": "0.3.30",
|
|
19
19
|
"@types/estree": "^0.0.50",
|
|
20
20
|
"acorn": "^8.4.1",
|
|
21
21
|
"agentkeepalive": "^4.1.3",
|
|
@@ -25,6 +25,6 @@
|
|
|
25
25
|
"randomstring": "^1.1.5",
|
|
26
26
|
"superagent": "^6.1.0",
|
|
27
27
|
"urijs": "^1.18.1",
|
|
28
|
-
"vm2": "^3.9.
|
|
28
|
+
"vm2": "^3.9.4"
|
|
29
29
|
}
|
|
30
30
|
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Histogram } from './failures';
|
|
2
|
+
|
|
3
|
+
export class RequestFailuresError extends Error {
|
|
4
|
+
constructor(message: string, public histogram: Histogram = { [message]: 1 }) {
|
|
5
|
+
super(message);
|
|
6
|
+
|
|
7
|
+
// Workaround suggested in: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
|
8
|
+
Object.setPrototypeOf(this, RequestFailuresError.prototype);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -5,12 +5,14 @@ import {
|
|
|
5
5
|
CheerioExtractor,
|
|
6
6
|
JsonPathExtractor,
|
|
7
7
|
ExpressionExtractor,
|
|
8
|
-
ParametrizedExtractor
|
|
8
|
+
ParametrizedExtractor,
|
|
9
|
+
WsExtractor,
|
|
9
10
|
} from '@loadmill/core/dist/parameters/extractors';
|
|
10
11
|
import { Parameters } from '@loadmill/core/dist/parameters';
|
|
11
12
|
import { Extraction } from '@loadmill/core/dist/request';
|
|
12
13
|
import log from '@loadmill/universal/dist/log';
|
|
13
14
|
import ednParser from 'jsedn';
|
|
15
|
+
import { WSExtractionData } from '@loadmill/core/dist/parameters/extractors/ws-extractor';
|
|
14
16
|
|
|
15
17
|
export class ExtractionCombiner {
|
|
16
18
|
headerExtractor: HeaderExtractor;
|
|
@@ -19,19 +21,19 @@ export class ExtractionCombiner {
|
|
|
19
21
|
ednPathExtractor: JsonPathExtractor;
|
|
20
22
|
expressionExtractor: ExpressionExtractor;
|
|
21
23
|
|
|
22
|
-
constructor(private contextParameters: Parameters, private res: any) {
|
|
24
|
+
constructor(private contextParameters: Parameters, private res: any, private wsExtractionData: WSExtractionData) {
|
|
23
25
|
this.headerExtractor = new HeaderExtractor(res, this.contextParameters);
|
|
24
26
|
this.expressionExtractor = new ExpressionExtractor(this.contextParameters);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
combine(extraction: Extraction) {
|
|
29
|
+
async combine(extraction: Extraction) {
|
|
28
30
|
let query: string | string[], extractor!: ParametrizedExtractor;
|
|
29
31
|
|
|
30
32
|
if (typeof extraction === 'string') {
|
|
31
33
|
query = extraction;
|
|
32
34
|
extractor = this.expressionExtractor;
|
|
33
35
|
} else {
|
|
34
|
-
const { header, jQuery, jsonPath, edn, regex } = extraction;
|
|
36
|
+
const { header, jQuery, jsonPath, edn, regex, ws } = extraction;
|
|
35
37
|
|
|
36
38
|
if (header != null) {
|
|
37
39
|
query = header;
|
|
@@ -88,6 +90,15 @@ export class ExtractionCombiner {
|
|
|
88
90
|
|
|
89
91
|
query = regex;
|
|
90
92
|
}
|
|
93
|
+
|
|
94
|
+
if (ws != null) {
|
|
95
|
+
extractor = new WsExtractor(
|
|
96
|
+
this.wsExtractionData,
|
|
97
|
+
this.contextParameters
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
query = ws;
|
|
101
|
+
}
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
return () => extractor.extract(query);
|
package/src/mill-version.ts
CHANGED
package/src/sequence.ts
CHANGED
|
@@ -10,7 +10,6 @@ import clamp from 'lodash/clamp';
|
|
|
10
10
|
import find from 'lodash/find';
|
|
11
11
|
import map from 'lodash/map';
|
|
12
12
|
import filter from 'lodash/filter';
|
|
13
|
-
import forEach from 'lodash/forEach';
|
|
14
13
|
|
|
15
14
|
import { ResolvedRequest } from './request-sequence-result';
|
|
16
15
|
import { Asserter } from './asserter';
|
|
@@ -52,8 +51,6 @@ const {
|
|
|
52
51
|
DEFAULT_REQUEST_DELAY,
|
|
53
52
|
} = confDefaults;
|
|
54
53
|
const {
|
|
55
|
-
MAX_API_REQUEST_BODY_LENGTH,
|
|
56
|
-
MAX_LOAD_REQUEST_BODY_LENGTH,
|
|
57
54
|
MAX_RESPONSE_BYTES,
|
|
58
55
|
MAX_RESPONSE_COLLECT,
|
|
59
56
|
MIN_REQUEST_DELAY,
|
|
@@ -71,10 +68,15 @@ import {
|
|
|
71
68
|
ALLOWED_RESPONSE_STATUSES,
|
|
72
69
|
LoopConf,
|
|
73
70
|
DEFAULT_REQUEST_TIMEOUT,
|
|
74
|
-
CachePenetrationModes
|
|
71
|
+
CachePenetrationModes,
|
|
72
|
+
RequestPostData
|
|
75
73
|
} from '@loadmill/core/dist/request';
|
|
76
74
|
|
|
77
75
|
import { testRunEventsEmitter } from './test-run-event-emitter';
|
|
76
|
+
import { WSRequest, WSRequestArguments, WSSequenceHandler } from './ws';
|
|
77
|
+
import { RequestFailuresError } from './errors';
|
|
78
|
+
import { getMaxRequestBodySize } from './utils';
|
|
79
|
+
import { WSExtractionData } from '@loadmill/core/dist/parameters/extractors/ws-extractor';
|
|
78
80
|
|
|
79
81
|
export const reqIdParamName = 'loadmill-request-id';
|
|
80
82
|
|
|
@@ -127,6 +129,7 @@ class SequenceExecutor {
|
|
|
127
129
|
resolvedRequests: ResolvedRequest[] = [];
|
|
128
130
|
keepaliveHTTPSAgent;
|
|
129
131
|
keepaliveHTTPAgent;
|
|
132
|
+
wsHandler: WSSequenceHandler;
|
|
130
133
|
|
|
131
134
|
constructor(
|
|
132
135
|
private httpAgent,
|
|
@@ -143,6 +146,7 @@ class SequenceExecutor {
|
|
|
143
146
|
freeSocketTimeout: SOCKET_TIMEOUT / 2
|
|
144
147
|
});
|
|
145
148
|
this.postScriptRunner = new PostScriptRunner();
|
|
149
|
+
this.wsHandler = new WSSequenceHandler();
|
|
146
150
|
}
|
|
147
151
|
|
|
148
152
|
startAndPass(requests: LoadmillRequest[]) {
|
|
@@ -213,9 +217,11 @@ class SequenceExecutor {
|
|
|
213
217
|
log.info('Unexpected error info:', unexpectedError);
|
|
214
218
|
}
|
|
215
219
|
|
|
220
|
+
this.wsHandler.closeAllConnections();
|
|
216
221
|
throw error;
|
|
217
222
|
}
|
|
218
223
|
}
|
|
224
|
+
this.wsHandler.closeAllConnections();
|
|
219
225
|
}
|
|
220
226
|
|
|
221
227
|
shouldStop(request: LoadmillRequest) {
|
|
@@ -309,12 +315,14 @@ class SequenceExecutor {
|
|
|
309
315
|
let failedAssertionsHistogram, resTime;
|
|
310
316
|
let loopIteration = 0;
|
|
311
317
|
const maxIterations = getLoopIterations(requestConf.loop);
|
|
318
|
+
this.wsHandler.clearMessages();
|
|
312
319
|
|
|
313
320
|
while (loopIteration < maxIterations) {
|
|
314
321
|
loopIteration++;
|
|
315
322
|
const request = this.prepareRequest(requestConf, index);
|
|
316
323
|
|
|
317
324
|
const res = await this.sendRequest(request, index);
|
|
325
|
+
|
|
318
326
|
// Setting now to avoid possible user overwrite:
|
|
319
327
|
resTime = Number(this.parameters.__responseTime);
|
|
320
328
|
|
|
@@ -325,7 +333,9 @@ class SequenceExecutor {
|
|
|
325
333
|
);
|
|
326
334
|
}
|
|
327
335
|
|
|
328
|
-
|
|
336
|
+
res.wsExtractionData = { messages: this.wsHandler.messages, timeLimit: timeout - resTime } as WSExtractionData;
|
|
337
|
+
|
|
338
|
+
failedAssertionsHistogram = await this.processSuccessfulResponse(
|
|
329
339
|
index,
|
|
330
340
|
requestConf,
|
|
331
341
|
res
|
|
@@ -501,8 +511,18 @@ class SequenceExecutor {
|
|
|
501
511
|
}
|
|
502
512
|
|
|
503
513
|
prepareRequest(requestConf: LoadmillRequest, reqIndex: number) {
|
|
514
|
+
if (this.isWSRequest(requestConf) && !envUtils.isBrowser()) {
|
|
515
|
+
return this.prepareWSRequest(requestConf, reqIndex);
|
|
516
|
+
}
|
|
517
|
+
return this.prepareHttpRequest(requestConf, reqIndex);
|
|
518
|
+
}
|
|
519
|
+
private isWSRequest({ url }: LoadmillRequest) {
|
|
520
|
+
const resolvedUrl = this.resolve(url, (e) => setParameterErrorHistogram(e, 'Failed to compute URL - '));
|
|
521
|
+
return resolvedUrl.startsWith('ws://') || resolvedUrl.startsWith('wss://');
|
|
522
|
+
}
|
|
523
|
+
private prepareHttpRequest(requestConf: LoadmillRequest, reqIndex: number) {
|
|
504
524
|
const urlObj = new URI(
|
|
505
|
-
resolveUrl(requestConf.url, this.parameters, (err) =>
|
|
525
|
+
resolveUrl(requestConf.url, this.parameters, (err: Error) =>
|
|
506
526
|
setParameterErrorHistogram(err, 'Failed to compute URL - ')
|
|
507
527
|
)
|
|
508
528
|
);
|
|
@@ -654,6 +674,66 @@ class SequenceExecutor {
|
|
|
654
674
|
return request;
|
|
655
675
|
}
|
|
656
676
|
|
|
677
|
+
prepareWSRequest(requestConf: LoadmillRequest, reqIndex: number) {
|
|
678
|
+
const wsRequestArgs = this.resolveAndSetWSReqData(requestConf, reqIndex);
|
|
679
|
+
|
|
680
|
+
return new WSRequest(
|
|
681
|
+
wsRequestArgs,
|
|
682
|
+
this.wsHandler,
|
|
683
|
+
(e: Error) => this.setSingleFailure(reqIndex, 'Websocket error ' + e.message),
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
resolveAndSetWSReqData = (
|
|
688
|
+
{ url, headers, postData, timeout = DEFAULT_REQUEST_TIMEOUT, expectedStatus = 'SUCCESS' }: LoadmillRequest,
|
|
689
|
+
reqIndex: number
|
|
690
|
+
): WSRequestArguments => {
|
|
691
|
+
const preparedUrl = this.prepareWsUrl(url, reqIndex);
|
|
692
|
+
const preparedHeaders = this.prepareWsHeaders(headers, reqIndex);
|
|
693
|
+
const preparedMessage = this.prepareWsMessage(postData, reqIndex);
|
|
694
|
+
return {
|
|
695
|
+
expectedStatus,
|
|
696
|
+
headers: preparedHeaders,
|
|
697
|
+
message: preparedMessage,
|
|
698
|
+
timeout,
|
|
699
|
+
url: preparedUrl,
|
|
700
|
+
};
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
prepareWsUrl = (url: string, reqIndex: number) => {
|
|
704
|
+
const resolvedUrl = this.resolve(url, (e: Error) => setParameterErrorHistogram(e, 'Failed to compute URL - '));
|
|
705
|
+
this.resolvedRequests[reqIndex].url = resolvedUrl;
|
|
706
|
+
return resolvedUrl;
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
prepareWsHeaders = (headers: LoadmillHeaders[] | undefined, reqIndex: number) => {
|
|
710
|
+
const resolvedHeadersObj: LoadmillHeaders = {};
|
|
711
|
+
const resolvedHeaders: LoadmillHeaders[] = [];
|
|
712
|
+
if (headers && !isEmpty(headers)) {
|
|
713
|
+
resolvedHeaders.push(...this.resolveHeaders(headers));
|
|
714
|
+
resolvedHeaders.forEach(({ name, value }) => resolvedHeadersObj[name] = value);
|
|
715
|
+
}
|
|
716
|
+
this.resolvedRequests[reqIndex].headers = resolvedHeaders;
|
|
717
|
+
return resolvedHeadersObj;
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
prepareWsMessage = (postData: RequestPostData | undefined, reqIndex: number) => {
|
|
721
|
+
let resolvedMessage: string = '';
|
|
722
|
+
if (postData) {
|
|
723
|
+
resolvedMessage = this.resolve(postData.text, (err: Error) =>
|
|
724
|
+
setParameterErrorHistogram(err, 'Failed to compute Websocket message - ')
|
|
725
|
+
);
|
|
726
|
+
if (resolvedMessage && resolvedMessage.length > getMaxRequestBodySize()) {
|
|
727
|
+
throw new RequestFailuresError('Websocket message size is too large');
|
|
728
|
+
}
|
|
729
|
+
this.resolvedRequests[reqIndex].postData = {
|
|
730
|
+
mimeType: postData.mimeType,
|
|
731
|
+
text: resolvedMessage,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
return resolvedMessage;
|
|
735
|
+
};
|
|
736
|
+
|
|
657
737
|
checkProgressEvent = (
|
|
658
738
|
requestIndex: number,
|
|
659
739
|
request,
|
|
@@ -715,9 +795,9 @@ class SequenceExecutor {
|
|
|
715
795
|
)
|
|
716
796
|
);
|
|
717
797
|
|
|
718
|
-
processSuccessfulResponse(reqIndex, requestConf: LoadmillRequest, res) {
|
|
798
|
+
async processSuccessfulResponse(reqIndex, requestConf: LoadmillRequest, res) {
|
|
719
799
|
// modifies parameters:
|
|
720
|
-
this.handleExtractions(requestConf, res);
|
|
800
|
+
await this.handleExtractions(requestConf, res);
|
|
721
801
|
|
|
722
802
|
if (!envUtils.isBrowser()) {
|
|
723
803
|
this.handlePostScript(requestConf, res);
|
|
@@ -745,33 +825,33 @@ class SequenceExecutor {
|
|
|
745
825
|
);
|
|
746
826
|
}
|
|
747
827
|
|
|
748
|
-
handleExtractions(requestConf: LoadmillRequest, res) {
|
|
749
|
-
requestConf.extract
|
|
750
|
-
this.extractInScope(res, extractions)
|
|
751
|
-
|
|
828
|
+
async handleExtractions(requestConf: LoadmillRequest, res) {
|
|
829
|
+
for (const extractions of (requestConf.extract || [])) {
|
|
830
|
+
await this.extractInScope(res, extractions);
|
|
831
|
+
}
|
|
752
832
|
}
|
|
753
833
|
|
|
754
|
-
extractInScope(res, extractions: Extractions) {
|
|
834
|
+
async extractInScope(res, extractions: Extractions) {
|
|
755
835
|
const contextParameters = Object.assign({}, this.parameters);
|
|
756
|
-
const extractionCombiner = new ExtractionCombiner(contextParameters, res);
|
|
836
|
+
const extractionCombiner = new ExtractionCombiner(contextParameters, res, res.wsExtractionData);
|
|
757
837
|
|
|
758
|
-
|
|
759
|
-
this.extract(name, extraction, extractionCombiner)
|
|
760
|
-
|
|
838
|
+
for (const [name, extraction] of Object.entries(extractions)) {
|
|
839
|
+
await this.extract(name, extraction, extractionCombiner);
|
|
840
|
+
}
|
|
761
841
|
}
|
|
762
842
|
|
|
763
|
-
extract(
|
|
843
|
+
async extract(
|
|
764
844
|
parameterName: string,
|
|
765
845
|
extraction: Extraction,
|
|
766
846
|
extractionCombiner: ExtractionCombiner
|
|
767
847
|
) {
|
|
768
848
|
log.trace('Parameter extraction start: ', { parameterName, extraction });
|
|
769
849
|
|
|
770
|
-
const combinedExtractor = extractionCombiner.combine(extraction);
|
|
850
|
+
const combinedExtractor = await extractionCombiner.combine(extraction);
|
|
771
851
|
let result;
|
|
772
852
|
|
|
773
853
|
try {
|
|
774
|
-
result = combinedExtractor();
|
|
854
|
+
result = await combinedExtractor();
|
|
775
855
|
} catch (error) {
|
|
776
856
|
const genericMessage = `Failed to extract value for parameter "${parameterName}"`;
|
|
777
857
|
log.debug(genericMessage, error);
|
|
@@ -893,9 +973,6 @@ function isSimpleRequest(headers) {
|
|
|
893
973
|
);
|
|
894
974
|
}
|
|
895
975
|
|
|
896
|
-
const getMaxRequestBodySize = () =>
|
|
897
|
-
envUtils.isBrowser() ? MAX_LOAD_REQUEST_BODY_LENGTH : MAX_API_REQUEST_BODY_LENGTH;
|
|
898
|
-
|
|
899
976
|
const getLoopIterations = (LoopConf?: LoopConf) => {
|
|
900
977
|
const declared = (LoopConf && LoopConf.iterations) || 1;
|
|
901
978
|
return Math.min(MAX_REQUEST_LOOPS_ITERATIONS, declared);
|
|
@@ -913,7 +990,7 @@ const extendResponseHeaders = (headers, redirectHeaders) => {
|
|
|
913
990
|
|
|
914
991
|
const isExpectedStatus = ({ expectedStatus, url }, status: number) => {
|
|
915
992
|
if (expectedStatus === ALLOWED_RESPONSE_STATUSES.SUCCESS) {
|
|
916
|
-
return 200 <= status && status < 400;
|
|
993
|
+
return (200 <= status && status < 400) || status === 101;
|
|
917
994
|
}
|
|
918
995
|
else if (expectedStatus === ALLOWED_RESPONSE_STATUSES.ERROR) {
|
|
919
996
|
log.debug('user asked to fail this request', url);
|
|
@@ -949,12 +1026,3 @@ const setTCPReuse = (request, agent, sslAgent) => {
|
|
|
949
1026
|
};
|
|
950
1027
|
|
|
951
1028
|
};
|
|
952
|
-
|
|
953
|
-
class RequestFailuresError extends Error {
|
|
954
|
-
constructor(message: string, public histogram: Histogram = { [message]: 1 }) {
|
|
955
|
-
super(message);
|
|
956
|
-
|
|
957
|
-
// Workaround suggested in: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
|
958
|
-
Object.setPrototypeOf(this, RequestFailuresError.prototype);
|
|
959
|
-
}
|
|
960
|
-
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_API_REQUEST_BODY_LENGTH,
|
|
3
|
+
MAX_LOAD_REQUEST_BODY_LENGTH
|
|
4
|
+
} from '@loadmill/core/dist/conf/extrema';
|
|
5
|
+
import * as envUtils from '@loadmill/universal/dist/env-utils';
|
|
6
|
+
|
|
7
|
+
export const getMaxRequestBodySize = () =>
|
|
8
|
+
envUtils.isBrowser() ? MAX_LOAD_REQUEST_BODY_LENGTH : MAX_API_REQUEST_BODY_LENGTH;
|
package/src/ws.ts
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import WebSocket from 'ws'; // todo should be picked with other option (socketio or non in browser)
|
|
2
|
+
import { delay } from '@loadmill/universal/dist/promise-utils';
|
|
3
|
+
import log from '@loadmill/universal/dist/log';
|
|
4
|
+
import { HttpResponseStatus, LoadmillHeaders } from '@loadmill/core/dist/request';
|
|
5
|
+
import { RequestFailuresError } from './errors';
|
|
6
|
+
|
|
7
|
+
enum WSState {
|
|
8
|
+
CONNECTING,
|
|
9
|
+
OPEN,
|
|
10
|
+
CLOSING,
|
|
11
|
+
CLOSED,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const SWITCHING_PROTOCOLS = 'Switching Protocols';
|
|
15
|
+
const WS_CONNECTION_TIMEOUT_MS = 10000;
|
|
16
|
+
|
|
17
|
+
export class WSRequest {
|
|
18
|
+
private hasErrorOccured?: boolean; // need this otherwise we have race condition between verifyConnectedAndOpen and onError
|
|
19
|
+
private ws: WebSocket;
|
|
20
|
+
public url: string; // need this for isExpectedStatus function
|
|
21
|
+
public expectedStatus: HttpResponseStatus; // need this for isExpectedStatus function
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
private readonly wsRequestArgs: WSRequestArguments,
|
|
25
|
+
private readonly wsHandler: WSSequenceHandler,
|
|
26
|
+
private readonly onError: (e: Error) => void,
|
|
27
|
+
) {
|
|
28
|
+
this.url = wsRequestArgs.url;
|
|
29
|
+
this.expectedStatus = wsRequestArgs.expectedStatus;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* This function is executed when we are ready to send the ws request
|
|
34
|
+
* @param cb This callback is being executed after we successfully connected to ws and sent a ws message if there was any
|
|
35
|
+
*/
|
|
36
|
+
async ok(cb: (response: WSResponse) => boolean) {
|
|
37
|
+
const response = {
|
|
38
|
+
status: 101,
|
|
39
|
+
res: {
|
|
40
|
+
statusMessage: SWITCHING_PROTOCOLS,
|
|
41
|
+
},
|
|
42
|
+
header: {},
|
|
43
|
+
req: {},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const existingWS = this.wsHandler.getConnection(this.wsRequestArgs.url);
|
|
47
|
+
|
|
48
|
+
log.debug(`Connection state ${getConnectionState(existingWS)}`);
|
|
49
|
+
if (!existingWS || (existingWS && existingWS.readyState !== WSState.OPEN)) {
|
|
50
|
+
await this.addConnection(response);
|
|
51
|
+
} else {
|
|
52
|
+
this.ws = existingWS;
|
|
53
|
+
log.debug('Reusing existing ws connection', this.ws.url);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
this.sendMessage();
|
|
58
|
+
} catch (e) {
|
|
59
|
+
log.error('Failed to send a ws message', e);
|
|
60
|
+
}
|
|
61
|
+
cb(response);
|
|
62
|
+
return { };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async addConnection(response: WSResponse) {
|
|
66
|
+
this.ws = new WebSocket(this.wsRequestArgs.url, undefined, { headers: this.wsRequestArgs.headers });
|
|
67
|
+
this.addEventListeners(response);
|
|
68
|
+
!this.hasErrorOccured && await this.verifyOpen();
|
|
69
|
+
this.wsHandler.storeConnection(this.wsRequestArgs.url, this.ws);
|
|
70
|
+
// todo add some debug details for the user to know we created a new connection in this request
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async addEventListeners(response: WSResponse) {
|
|
74
|
+
this.addOnErrorListener();
|
|
75
|
+
this.addOnUpgradeListener(response);
|
|
76
|
+
this.addOnOpenListener();
|
|
77
|
+
this.addOnMessageListener();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private addOnErrorListener() {
|
|
81
|
+
this.ws.on('error', (e) => {
|
|
82
|
+
try {
|
|
83
|
+
log.debug('inside on error', e);
|
|
84
|
+
this.onError(e);
|
|
85
|
+
this.hasErrorOccured = true;
|
|
86
|
+
} catch (e) {
|
|
87
|
+
log.warn('Failed to handle a ws error', e);
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
log.debug('closing ws');
|
|
91
|
+
this.ws.close();
|
|
92
|
+
} catch (e) {
|
|
93
|
+
log.warn('Failed to close a ws connection', e);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private addOnUpgradeListener(response: WSResponse) {
|
|
99
|
+
this.ws.on('upgrade', (event) => {
|
|
100
|
+
try {
|
|
101
|
+
const { statusCode, statusMessage, headers } = event;
|
|
102
|
+
log.debug('inside upgrade event', { statusCode, statusMessage, headers });
|
|
103
|
+
response.status = statusCode || 101;
|
|
104
|
+
response.res.statusMessage = statusMessage || SWITCHING_PROTOCOLS;
|
|
105
|
+
response.header = headers as LoadmillHeaders;
|
|
106
|
+
} catch (e) {
|
|
107
|
+
log.debug('upgrade event err', e);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private addOnOpenListener() {
|
|
113
|
+
this.ws.on('open', () => {
|
|
114
|
+
log.debug('inside on open, currently doing nothing here.');
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private addOnMessageListener() {
|
|
119
|
+
this.ws.on('message', (message: string) => {
|
|
120
|
+
try {
|
|
121
|
+
log.debug('got incoming message', message);
|
|
122
|
+
this.wsHandler.addMessage(message);
|
|
123
|
+
} catch (e) {
|
|
124
|
+
log.debug('error getting message', e);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async verifyOpen() {
|
|
130
|
+
const readyState = await this.waitForConnectedState(this.wsRequestArgs.timeout);
|
|
131
|
+
log.debug(`Connection state ${WSState[readyState]}`);
|
|
132
|
+
if (readyState !== WebSocket.OPEN && !this.hasErrorOccured) {
|
|
133
|
+
log.error('Could not connect to WebSocket address', { connectionState: WSState[readyState], url: this.ws.url });
|
|
134
|
+
throw new RequestFailuresError('Could not connect to WebSocket address');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Waiting for WS connection to go out of CONNECTING state
|
|
140
|
+
* @param socket The ws connection object
|
|
141
|
+
* @param timeout The timeout in milliseconds for the waiting procedure
|
|
142
|
+
* @returns WSState
|
|
143
|
+
*/
|
|
144
|
+
waitForConnectedState = async (timeout: number = WS_CONNECTION_TIMEOUT_MS): Promise<WSState> => {
|
|
145
|
+
const WS_CONNECTION_INTERVAL_MS = 100;
|
|
146
|
+
if (this.ws.readyState !== WebSocket.CONNECTING) {
|
|
147
|
+
return this.ws.readyState;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const maxIterations = timeout / WS_CONNECTION_INTERVAL_MS;
|
|
151
|
+
let i = 0;
|
|
152
|
+
while (this.ws.readyState === WebSocket.CONNECTING && i < maxIterations) {
|
|
153
|
+
await delay(WS_CONNECTION_INTERVAL_MS);
|
|
154
|
+
i++;
|
|
155
|
+
}
|
|
156
|
+
return this.ws.readyState;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
private sendMessage() {
|
|
161
|
+
log.debug('about to send message', { readyState: WSState[this.ws.readyState] } );
|
|
162
|
+
if (this.ws.readyState === WSState.OPEN && this.wsRequestArgs.message) {
|
|
163
|
+
log.debug('sending message', this.wsRequestArgs.message);
|
|
164
|
+
this.ws.send(this.wsRequestArgs.message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// need this because the SequenceExecutor expectes a request obj with on func prop
|
|
169
|
+
on(_eventName: string, _cb: Function) {
|
|
170
|
+
//do nothing
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export class WSSequenceHandler {
|
|
175
|
+
private _connections: { [url: string]: WebSocket };
|
|
176
|
+
messages: string[];
|
|
177
|
+
constructor() {
|
|
178
|
+
this._connections = {};
|
|
179
|
+
this.clearMessages();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
getConnection(url: string): WebSocket | undefined {
|
|
183
|
+
return this._connections[url];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
storeConnection(url: string, wsConnection: WebSocket) {
|
|
187
|
+
this._connections[url] = wsConnection;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async closeAllConnections() {
|
|
191
|
+
try {
|
|
192
|
+
log.debug('Closing all ws connections');
|
|
193
|
+
for (const connection of Object.values(this._connections)) {
|
|
194
|
+
connection.close();
|
|
195
|
+
}
|
|
196
|
+
} catch (e) {
|
|
197
|
+
log.warn('Failed to close all connections', e, { connections: this._connections });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
getMessages() {
|
|
202
|
+
return this.messages;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
addMessage(message: string) {
|
|
206
|
+
this.messages.push(message);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
clearMessages() {
|
|
210
|
+
this.messages = [];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function getConnectionState(existingWS?: WebSocket) {
|
|
215
|
+
return existingWS ? WSState[existingWS.readyState] : null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export type WSRequestArguments = {
|
|
219
|
+
expectedStatus: HttpResponseStatus;
|
|
220
|
+
headers: LoadmillHeaders
|
|
221
|
+
message: string;
|
|
222
|
+
timeout: number;
|
|
223
|
+
url: string;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export type WSResponse = {
|
|
227
|
+
status: number;
|
|
228
|
+
res: {
|
|
229
|
+
statusMessage: string;
|
|
230
|
+
};
|
|
231
|
+
header: LoadmillHeaders;
|
|
232
|
+
req: {};
|
|
233
|
+
}
|