@63klabs/cache-data 1.2.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.
- package/CHANGELOG.md +234 -0
- package/LICENSE.txt +21 -0
- package/README.md +1265 -0
- package/SECURITY.md +5 -0
- package/package.json +58 -0
- package/src/index.js +9 -0
- package/src/lib/dao-cache.js +2024 -0
- package/src/lib/dao-endpoint.js +186 -0
- package/src/lib/tools/APIRequest.class.js +673 -0
- package/src/lib/tools/AWS.classes.js +250 -0
- package/src/lib/tools/CachedParametersSecrets.classes.js +492 -0
- package/src/lib/tools/ClientRequest.class.js +567 -0
- package/src/lib/tools/Connections.classes.js +466 -0
- package/src/lib/tools/DebugAndLog.class.js +416 -0
- package/src/lib/tools/ImmutableObject.class.js +71 -0
- package/src/lib/tools/RequestInfo.class.js +323 -0
- package/src/lib/tools/Response.class.js +547 -0
- package/src/lib/tools/ResponseDataModel.class.js +183 -0
- package/src/lib/tools/Timer.class.js +189 -0
- package/src/lib/tools/generic.response.html.js +88 -0
- package/src/lib/tools/generic.response.json.js +102 -0
- package/src/lib/tools/generic.response.rss.js +88 -0
- package/src/lib/tools/generic.response.text.js +86 -0
- package/src/lib/tools/generic.response.xml.js +82 -0
- package/src/lib/tools/index.js +318 -0
- package/src/lib/tools/utils.js +305 -0
- package/src/lib/tools/vars.js +34 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
// This file is used to make API requests and handle responses
|
|
2
|
+
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const {AWS, AWSXRay} = require('./AWS.classes.js');
|
|
5
|
+
const DebugAndLog = require('./DebugAndLog.class.js');
|
|
6
|
+
|
|
7
|
+
/* Either return an XRay segment or mock one up so we don't need much logic if xray isn't used */
|
|
8
|
+
const xRayProxyFunc = {
|
|
9
|
+
addMetadata: (mockParam, mockObj) => { DebugAndLog.debug(`Mocking XRay addMetadata: ${mockParam} | ${mockObj}`); },
|
|
10
|
+
addAnnotation: (mockParam, mockObj) => { DebugAndLog.debug(`Mocking XRay addAnnotation: ${mockParam} | ${mockObj}`); },
|
|
11
|
+
addError: (mockError) => { DebugAndLog.debug(`Mocking XRay addError: ${mockError}`); },
|
|
12
|
+
addFaultFlag: () => { DebugAndLog.debug(`Mocking XRay addFaultFlag`); },
|
|
13
|
+
addErrorFlag: () => { DebugAndLog.debug(`Mocking XRay addErrorFlag`); },
|
|
14
|
+
close: () => { DebugAndLog.debug(`Mocking XRay close`); }
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const xRayProxy = (AWSXRay !== null) ? AWS.XRay.getSegment() : {
|
|
18
|
+
...xRayProxyFunc,
|
|
19
|
+
addNewSubsegment: (mockString) => {
|
|
20
|
+
DebugAndLog.debug(`Mocking XRay subsegment: ${mockString}`);
|
|
21
|
+
return xRayProxyFunc;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* An internal tools function used by APIRequest. https.get does not work well
|
|
27
|
+
* inside a class object (specifically doesn't like this.*), so we make it
|
|
28
|
+
* external to the class and pass the class as a reference to be updated either
|
|
29
|
+
* with a response or redirect uri.
|
|
30
|
+
* @param {object} options The options object for https.get()
|
|
31
|
+
* @param {APIRequest} requestObject The APIRequest object that contains internal functions, request info (including uri) and redirects. This object will be updated with any redirects and responses
|
|
32
|
+
* @returns A promise that will resolve to a boolean denoting whether or not the response is considered complete (no unresolved redirects). The boolean does not mean "error free." Even if we receive errors it is considered complete.
|
|
33
|
+
*/
|
|
34
|
+
const _httpGetExecute = async function (options, requestObject, xRaySegment = xRayProxy) {
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
Return a promise that will resolve to true or false based upon success
|
|
38
|
+
*/
|
|
39
|
+
return new Promise ((resolve, reject) => {
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
Functions/variables we'll use within https.get()
|
|
43
|
+
We need to declare functions that we will be using within https.get()
|
|
44
|
+
"locally" and refer to the requestObject to perform updates
|
|
45
|
+
setResponse() and addRedirect() also performs the resolve() and reject() for the promise
|
|
46
|
+
*/
|
|
47
|
+
const setResponse = function (response) { requestObject.setResponse(response); resolve(true)};
|
|
48
|
+
const addRedirect = function (uri) { requestObject.addRedirect(uri); resolve(false)};
|
|
49
|
+
const redirects = requestObject.getNumberOfRedirects();
|
|
50
|
+
const uri = requestObject.getURI();
|
|
51
|
+
|
|
52
|
+
let body = "";
|
|
53
|
+
|
|
54
|
+
/*
|
|
55
|
+
Perform the https.get()
|
|
56
|
+
*/
|
|
57
|
+
let req = https.request(uri, options, (res) => {
|
|
58
|
+
|
|
59
|
+
DebugAndLog.debug(`Performing https.get callback on response with status code: ${res.statusCode} ${new URL(uri).host}`);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
- IF it is a redirect, then add the redirect to the request object
|
|
65
|
+
and it will not be marked as complete.
|
|
66
|
+
|
|
67
|
+
- ELSE we'll update the request object with the response which
|
|
68
|
+
will mark it as complete.
|
|
69
|
+
*/
|
|
70
|
+
if ( res.statusCode === 301
|
|
71
|
+
|| res.statusCode === 302
|
|
72
|
+
|| res.statusCode === 303
|
|
73
|
+
|| res.statusCode === 307
|
|
74
|
+
)
|
|
75
|
+
{
|
|
76
|
+
|
|
77
|
+
DebugAndLog.debug(`Processing a redirect: ${res.statusCode}`);
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
- IF We have not performed the max number of redirects then we
|
|
81
|
+
will process the current redirect.
|
|
82
|
+
|
|
83
|
+
- ELSE We will produce an error of too many redirects.
|
|
84
|
+
*/
|
|
85
|
+
if ( redirects < APIRequest.MAX_REDIRECTS ) {
|
|
86
|
+
|
|
87
|
+
// we'll gather variables to use in logging
|
|
88
|
+
let newLocation = res.headers.location;
|
|
89
|
+
let nloc = new URL(newLocation);
|
|
90
|
+
let rloc = new URL(uri);
|
|
91
|
+
|
|
92
|
+
/* log essential as warning, others only when debugging
|
|
93
|
+
Note that we only list the hostname and path because
|
|
94
|
+
sensitive info may be in the query string. Also,
|
|
95
|
+
redirects typically don't involve query strings anyway */
|
|
96
|
+
if (res.statusCode === 301) {
|
|
97
|
+
// report as this as permanent, we'll want to report this in the log as a warning and fix
|
|
98
|
+
DebugAndLog.warning("301 | Redirect (Moved Permanently) received", {requested: rloc.protocol +"//"+ rloc.hostname + rloc.pathname, redirect: nloc.protocol +"//"+ nloc.hostname + nloc.pathname });
|
|
99
|
+
} else {
|
|
100
|
+
// Temporary redirect, just follow it, don't if we are not in debug mode
|
|
101
|
+
DebugAndLog.debug(res.statusCode+" Redirect received", {requested: rloc.protocol +"//"+ rloc.hostname + rloc.pathname, redirect: nloc.protocol +"//"+ nloc.hostname + nloc.pathname })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// don't let the redirect downgrade
|
|
105
|
+
if ( rloc.protocol === "https:" && nloc.protocol !== "https:") { // URL() protocol has the colon 'https:'
|
|
106
|
+
newLocation = newLocation.replace("http:","https:");
|
|
107
|
+
DebugAndLog.debug("We requested https but are being redirected to http. Upgrading back to https - "+newLocation);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// update the request object with the redirect so it can be reprocessed
|
|
111
|
+
DebugAndLog.debug("Setting uri to "+newLocation);
|
|
112
|
+
addRedirect(newLocation);
|
|
113
|
+
|
|
114
|
+
} else {
|
|
115
|
+
DebugAndLog.warn(`Too many redirects. Limit of ${APIRequest.MAX_REDIRECTS}`);
|
|
116
|
+
setResponse(APIRequest.responseFormat(false, 500, "Too many redirects"));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
} else {
|
|
120
|
+
|
|
121
|
+
/*
|
|
122
|
+
- IF we receive a 304 (not modified) then send that back as
|
|
123
|
+
a response. (Protocol is to return a null body)
|
|
124
|
+
|
|
125
|
+
- ELSE process as usual
|
|
126
|
+
*/
|
|
127
|
+
if (res.statusCode === 304) {
|
|
128
|
+
// 304 not modified
|
|
129
|
+
DebugAndLog.debug("304 Not Modified. Setting body to null");
|
|
130
|
+
setResponse(APIRequest.responseFormat(
|
|
131
|
+
true,
|
|
132
|
+
res.statusCode,
|
|
133
|
+
"SUCCESS",
|
|
134
|
+
res.headers,
|
|
135
|
+
null));
|
|
136
|
+
} else {
|
|
137
|
+
|
|
138
|
+
DebugAndLog.debug("No 'Redirect' or 'Not Modified' received. Processing http get as usual");
|
|
139
|
+
|
|
140
|
+
/*
|
|
141
|
+
The 3 classic https.get() functions
|
|
142
|
+
What to do on "data", "end" and "error"
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
146
|
+
|
|
147
|
+
res.on('end', () => {
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
|
|
151
|
+
let success = (res.statusCode < 400);
|
|
152
|
+
|
|
153
|
+
xRaySegment.addAnnotation('response_status', res.statusCode);
|
|
154
|
+
|
|
155
|
+
xRaySegment.addMetadata('http',
|
|
156
|
+
{
|
|
157
|
+
request: {
|
|
158
|
+
method: requestObject.getMethod(),
|
|
159
|
+
host: requestObject.getHost(),
|
|
160
|
+
url: requestObject.getURI(false)
|
|
161
|
+
},
|
|
162
|
+
response: {
|
|
163
|
+
status: res.statusCode,
|
|
164
|
+
headers: res.headers
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
// Add request data
|
|
169
|
+
xRaySegment.http = {
|
|
170
|
+
request: {
|
|
171
|
+
method: requestObject.getMethod(),
|
|
172
|
+
url: requestObject.getURI(false),
|
|
173
|
+
traced: true
|
|
174
|
+
},
|
|
175
|
+
response: {
|
|
176
|
+
status: res.statusCode,
|
|
177
|
+
headers: res.headers
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
DebugAndLog.debug(`Response status ${res.statusCode}`, {status: res.statusCode, headers: res.headers});
|
|
182
|
+
|
|
183
|
+
if (res.statusCode >= 500) {
|
|
184
|
+
xRaySegment.addFaultFlag();
|
|
185
|
+
xRaySegment.addError(new Error(`Response status ${res.statusCode}`));
|
|
186
|
+
// xRaySegment.close(); // we are handling in calling func
|
|
187
|
+
} else if (res.statusCode >= 400) {
|
|
188
|
+
xRaySegment.addErrorFlag();
|
|
189
|
+
xRaySegment.addError(new Error(`Response status ${res.statusCode}`));
|
|
190
|
+
// xRaySegment.close(); // we are handling in calling func
|
|
191
|
+
} else {
|
|
192
|
+
// xRaySegment.close(); // we are handling in calling func
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
setResponse(APIRequest.responseFormat(
|
|
196
|
+
success,
|
|
197
|
+
res.statusCode,
|
|
198
|
+
(success ? "SUCCESS" : "FAIL"),
|
|
199
|
+
res.headers,
|
|
200
|
+
body));
|
|
201
|
+
|
|
202
|
+
} catch (error) {
|
|
203
|
+
DebugAndLog.error(`Error during http get callback for host ${requestObject.getHost()} ${requestObject.getNote()} ${error.message}`, error.stack);
|
|
204
|
+
xRaySegment.addError(error);
|
|
205
|
+
setResponse(APIRequest.responseFormat(false, 500, "https.get resulted in error"));
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
res.on('error', error => {
|
|
210
|
+
DebugAndLog.error(`API error during request/response for host ${requestObject.getHost()} ${requestObject.getNote()} ${error.message}`, error.stack);
|
|
211
|
+
xRaySegment.addError(error);
|
|
212
|
+
// xRaySegment.close(); // we are handling in calling func
|
|
213
|
+
setResponse(APIRequest.responseFormat(false, 500, "https.get resulted in error"));
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
} catch (error) {
|
|
220
|
+
DebugAndLog.error(`Error during http get callback for host ${requestObject.getHost()} ${requestObject.getNote()} ${error.message}`, error.stack);
|
|
221
|
+
xRaySegment.addError(error);
|
|
222
|
+
setResponse(APIRequest.responseFormat(false, 500, "https.get resulted in error"));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
req.on('timeout', () => {
|
|
228
|
+
DebugAndLog.warn(`Endpoint request timeout reached (${requestObject.getTimeOutInMilliseconds()}ms) for host: ${requestObject.getHost()}`, {host: requestObject.getHost(), note: requestObject.getNote()});
|
|
229
|
+
// create a new error object to pass to xray
|
|
230
|
+
xRaySegment.addFaultFlag();
|
|
231
|
+
xRaySegment.addError(new Error("Endpoint request timeout reached"));
|
|
232
|
+
// xRaySegment.close(); // we are handling in calling func
|
|
233
|
+
setResponse(APIRequest.responseFormat(false, 504, "https.request resulted in timeout"));
|
|
234
|
+
req.destroy(); //req.end()
|
|
235
|
+
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
req.on('error', error => {
|
|
239
|
+
DebugAndLog.error(`API error during request for host ${requestObject.getHost()} ${requestObject.getNote()} ${error.message}`, error.stack);
|
|
240
|
+
xRaySegment.addFaultFlag();
|
|
241
|
+
xRaySegment.addError(error);
|
|
242
|
+
// xRaySegment.close(); // we are handling in calling func
|
|
243
|
+
setResponse(APIRequest.responseFormat(false, 500, "https.request resulted in error"));
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if ( requestObject.getMethod() === "POST" && requestObject.getBody() !== null ) {
|
|
247
|
+
req.write(requestObject.getBody());
|
|
248
|
+
}
|
|
249
|
+
req.end();
|
|
250
|
+
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Submit GET and POST requests and handle responses.
|
|
256
|
+
* This class can be used in a DAO class object within its call() method.
|
|
257
|
+
* @example
|
|
258
|
+
*
|
|
259
|
+
* async call() {
|
|
260
|
+
*
|
|
261
|
+
* var response = null;
|
|
262
|
+
*
|
|
263
|
+
* try {
|
|
264
|
+
* var apiRequest = new tools.APIRequest(this.request);
|
|
265
|
+
* response = await apiRequest.send();
|
|
266
|
+
*
|
|
267
|
+
* } catch (error) {
|
|
268
|
+
* DebugAndLog.error(`Error in call: ${error.message}`, error.stack);
|
|
269
|
+
* response = tools.APIRequest.responseFormat(false, 500, "Error in call()");
|
|
270
|
+
* }
|
|
271
|
+
*
|
|
272
|
+
* return response;
|
|
273
|
+
*
|
|
274
|
+
* };
|
|
275
|
+
*/
|
|
276
|
+
class APIRequest {
|
|
277
|
+
|
|
278
|
+
static MAX_REDIRECTS = 5;
|
|
279
|
+
|
|
280
|
+
#redirects = [];
|
|
281
|
+
#requestComplete = false;
|
|
282
|
+
#response = null;
|
|
283
|
+
#request = null;
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Function used to make an API request utilized directly or from within
|
|
287
|
+
* a data access object.
|
|
288
|
+
*
|
|
289
|
+
* @param {ConnectionObject} request
|
|
290
|
+
*/
|
|
291
|
+
constructor(request) {
|
|
292
|
+
this.resetRequest();
|
|
293
|
+
|
|
294
|
+
/* We need to have a method, protocol, uri (host/domain), and parameters set
|
|
295
|
+
Everything else is optional */
|
|
296
|
+
|
|
297
|
+
let timeOutInMilliseconds = 8000;
|
|
298
|
+
|
|
299
|
+
/* Default values */
|
|
300
|
+
let req = {
|
|
301
|
+
method: "GET",
|
|
302
|
+
uri: "",
|
|
303
|
+
protocol: "https",
|
|
304
|
+
host: "",
|
|
305
|
+
path: "",
|
|
306
|
+
parameters: {},
|
|
307
|
+
headers: {},
|
|
308
|
+
body: null,
|
|
309
|
+
note: "",
|
|
310
|
+
options: {
|
|
311
|
+
timeout: timeOutInMilliseconds,
|
|
312
|
+
separateDuplicateParameters: false,
|
|
313
|
+
separateDuplicateParametersAppendToKey: "", // "" "[]", or "0++", "1++"
|
|
314
|
+
combinedDuplicateParameterDelimiter: ',' // "," or "|" or " "
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
/* if we have a method or protocol passed to us, set them */
|
|
319
|
+
if ( "method" in request && request.method !== "" && request.method !== null) { req.method = request.method.toUpperCase(); }
|
|
320
|
+
if ( "protocol" in request && request.protocol !== "" && request.protocol !== null) { req.protocol = request.protocol.toLowerCase(); }
|
|
321
|
+
|
|
322
|
+
if ("body" in request) { req.body = request.body; }
|
|
323
|
+
if ("headers" in request && request.headers !== null) { req.headers = request.headers; }
|
|
324
|
+
if ("note" in request) { req.note = request.note; }
|
|
325
|
+
|
|
326
|
+
// With options we want to keep our defaults so we'll use Object.assign
|
|
327
|
+
if ("options" in request && request.options !== null) { req.options = Object.assign(req.options, request.options); }
|
|
328
|
+
|
|
329
|
+
/* if there is no timeout set, or if it is less than 1, then set to default */
|
|
330
|
+
if ( !("timeout" in req.options && req.options.timeout > 0) ) {
|
|
331
|
+
req.options.timeout = timeOutInMilliseconds;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* if we have a uri, set it, otherwise form one using host and path */
|
|
335
|
+
if ( "uri" in request && request.uri !== null && request.uri !== "" ) {
|
|
336
|
+
req.uri = request.uri;
|
|
337
|
+
} else if ("host" in request && request.host !== "" && request.host !== null) {
|
|
338
|
+
let path = ("path" in request && request.path !== null && request.path !== null) ? request.path : "";
|
|
339
|
+
req.uri = `${req.protocol}://${request.host}${path}`; // we use req.protocol because it is already set
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/* if we have parameters, create a query string and append to uri */
|
|
343
|
+
if (
|
|
344
|
+
"parameters" in request
|
|
345
|
+
&& request.parameters !== null
|
|
346
|
+
&& (typeof request.parameters === 'object' && Object.keys(request.parameters).length !== 0)
|
|
347
|
+
){
|
|
348
|
+
|
|
349
|
+
req.uri += this._queryStringFromObject(request.parameters, req.options);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
this.#request = req;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
_queryStringFromObject = function (parameters, options) {
|
|
356
|
+
|
|
357
|
+
let qString = [];
|
|
358
|
+
|
|
359
|
+
for (const [key,value] of Object.entries(parameters) ) {
|
|
360
|
+
|
|
361
|
+
/* if the value is an array, then we have to join into one parameter or separate into multiple key/value pairs */
|
|
362
|
+
if ( Array.isArray(value) ) {
|
|
363
|
+
let values = [];
|
|
364
|
+
|
|
365
|
+
/* apply encodeURIComponent() to each element in value array */
|
|
366
|
+
for (const v of value) {
|
|
367
|
+
values.push(encodeURIComponent(v));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if ( "separateDuplicateParameters" in options && options.separateDuplicateParameters === true) {
|
|
371
|
+
let a = "";
|
|
372
|
+
if ( "separateDuplicateParametersAppendToKey" in options ) {
|
|
373
|
+
if ( options.separateDuplicateParametersAppendToKey === '1++' || options.separateDuplicateParametersAppendToKey === '0++') {
|
|
374
|
+
a = (options.separateDuplicateParametersAppendToKey === "1++") ? 1 : 0;
|
|
375
|
+
} else {
|
|
376
|
+
a = options.separateDuplicateParametersAppendToKey;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
for (const v of values) {
|
|
381
|
+
qString.push(`${key}${a}=${v}`); // we encoded above
|
|
382
|
+
if(Number.isInteger(a)) { a++; }
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
const delim = ("combinedDuplicateParameterDelimiter" in options && options.combinedDuplicateParameterDelimiter !== null && options.combinedDuplicateParameterDelimiter !== "") ? options.combinedDuplicateParameterDelimiter : ",";
|
|
386
|
+
qString.push(`${key}=${values.join(delim)}`); // we encoded above
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
} else {
|
|
390
|
+
qString.push(`${key}=${encodeURIComponent(value)}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return (qString.length > 0) ? '?'+qString.join("&") : "";
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Clears out any redirects, completion flag, and response
|
|
399
|
+
*/
|
|
400
|
+
resetRequest() {
|
|
401
|
+
this.#redirects = [];
|
|
402
|
+
this.#requestComplete = false;
|
|
403
|
+
this.#response = null;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Set the uri of the request
|
|
408
|
+
* @param {string} newURI
|
|
409
|
+
*/
|
|
410
|
+
updateRequestURI(newURI) {
|
|
411
|
+
this.#request.uri = newURI;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Add a redirect uri to the stack
|
|
416
|
+
* @param {string} uri
|
|
417
|
+
*/
|
|
418
|
+
addRedirect(uri) {
|
|
419
|
+
this.#redirects.push(uri);
|
|
420
|
+
this.updateRequestURI(uri);
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
*
|
|
425
|
+
* @returns {number} Number of redirects currently experienced for this request
|
|
426
|
+
*/
|
|
427
|
+
getNumberOfRedirects() {
|
|
428
|
+
return this.#redirects.length;
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* When the request is complete, set the response and mark as complete
|
|
433
|
+
* @param {object} response
|
|
434
|
+
*/
|
|
435
|
+
setResponse(response) {
|
|
436
|
+
this.#requestComplete = true;
|
|
437
|
+
this.#response = response;
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* @param {boolean} includeQueryString Whether or not to include the query string in the URI - For logging purposes when you don't want to include sensitive information
|
|
442
|
+
* @returns {string} The current URI of the request
|
|
443
|
+
*/
|
|
444
|
+
getURI(includeQueryString = true) {
|
|
445
|
+
return (includeQueryString) ? this.#request.uri : this.#request.uri.split("?")[0];
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
*
|
|
450
|
+
* @returns {string|null} The body of a post request
|
|
451
|
+
*/
|
|
452
|
+
getBody() {
|
|
453
|
+
return this.#request.body;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
*
|
|
458
|
+
* @returns {string} The request method
|
|
459
|
+
*/
|
|
460
|
+
getMethod() {
|
|
461
|
+
return this.#request.method;
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
*
|
|
466
|
+
* @returns {string} A note for troubleshooting and tracing the request
|
|
467
|
+
*/
|
|
468
|
+
getNote() {
|
|
469
|
+
return this.#request.note;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
*
|
|
474
|
+
* @returns {number} ClientRequest timout in milliseconds
|
|
475
|
+
*/
|
|
476
|
+
getTimeOutInMilliseconds() {
|
|
477
|
+
return this.#request.options.timeout;
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
*
|
|
482
|
+
* @returns {string} The host domain submitted for the request
|
|
483
|
+
*/
|
|
484
|
+
getHost() {
|
|
485
|
+
return (new URL(this.getURI())).host;
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Send the request
|
|
490
|
+
* @returns {object}
|
|
491
|
+
*/
|
|
492
|
+
async send() {
|
|
493
|
+
|
|
494
|
+
this.resetRequest();
|
|
495
|
+
|
|
496
|
+
var response = null;
|
|
497
|
+
|
|
498
|
+
switch (this.#request.method) {
|
|
499
|
+
case "GET":
|
|
500
|
+
response = await this.send_get();
|
|
501
|
+
break;
|
|
502
|
+
case "POST":
|
|
503
|
+
response = await this.send_get(); // this.method should already be set and is all it needs
|
|
504
|
+
break;
|
|
505
|
+
default:
|
|
506
|
+
break; // PUT, DELETE, etc not yet implemented
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return response;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Process the request
|
|
514
|
+
* @returns {object} Response
|
|
515
|
+
*/
|
|
516
|
+
async send_get() {
|
|
517
|
+
|
|
518
|
+
return new Promise (async (resolve, reject) => {
|
|
519
|
+
// https://stackoverflow.com/questions/41470296/how-to-await-and-return-the-result-of-a-http-request-so-that-multiple-request
|
|
520
|
+
|
|
521
|
+
// https://nodejs.org/api/https.html#https_https_request_url_options_callback
|
|
522
|
+
// https://usefulangle.com/post/170/nodejs-synchronous-http-request
|
|
523
|
+
|
|
524
|
+
try {
|
|
525
|
+
|
|
526
|
+
if ( "note" in this.#request ) {
|
|
527
|
+
DebugAndLog.msg("Sending request for: "+this.#request.note);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// create the options object, use either options passed in, or an empty one
|
|
531
|
+
let options = ( this.#request.options !== null) ? this.#request.options : {};
|
|
532
|
+
|
|
533
|
+
// add the request headers to options
|
|
534
|
+
if ( this.#request.headers !== null ) {
|
|
535
|
+
options.headers = this.#request.headers;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// add the request method to options (default is GET)
|
|
539
|
+
if ( this.#request.method === null || this.#request.method === "") {
|
|
540
|
+
this.#request.method = "GET";
|
|
541
|
+
}
|
|
542
|
+
options.method = this.#request.method;
|
|
543
|
+
|
|
544
|
+
try {
|
|
545
|
+
|
|
546
|
+
// we will want to follow redirects, so keep submitting until considered complete
|
|
547
|
+
while ( !this.#requestComplete ) {
|
|
548
|
+
if (AWSXRay) {
|
|
549
|
+
|
|
550
|
+
// async send() {
|
|
551
|
+
// const parentSegment = AWSXRay.getSegment();
|
|
552
|
+
// const customSegment = new AWSXRay.Segment('APIRequest');
|
|
553
|
+
|
|
554
|
+
// return await AWSXRay.captureAsyncFunc('APIRequest', async (subsegment) => {
|
|
555
|
+
// AWSXRay.setSegment(customSegment);
|
|
556
|
+
// try {
|
|
557
|
+
// // Your custom subsegments here
|
|
558
|
+
// subsegment.addAnnotation('host', this.host);
|
|
559
|
+
// subsegment.addAnnotation('method', this.method);
|
|
560
|
+
|
|
561
|
+
// const result = await this._performRequest();
|
|
562
|
+
// return result;
|
|
563
|
+
// } finally {
|
|
564
|
+
// AWSXRay.setSegment(parentSegment);
|
|
565
|
+
// }
|
|
566
|
+
// }, customSegment);
|
|
567
|
+
// }
|
|
568
|
+
|
|
569
|
+
const subsegmentName = "APIRequest/" + ((this.getHost()) ? this.getHost() : new URL(this.getURI()).hostname);
|
|
570
|
+
// const parentSegment = AWSXRay.getSegment();
|
|
571
|
+
// const customSegment = new AWSXRay.Segment('APIRequest');
|
|
572
|
+
|
|
573
|
+
await AWSXRay.captureAsyncFunc(subsegmentName, async (subsegment) => {
|
|
574
|
+
// AWSXRay.setSegment(subsegment);
|
|
575
|
+
|
|
576
|
+
try {
|
|
577
|
+
|
|
578
|
+
subsegment.namespace = 'remote';
|
|
579
|
+
|
|
580
|
+
// Add searchable annotations
|
|
581
|
+
subsegment.addAnnotation('request_method', this.getMethod());
|
|
582
|
+
subsegment.addAnnotation('request_host', this.getHost());
|
|
583
|
+
subsegment.addAnnotation('request_uri', this.getURI(false));
|
|
584
|
+
subsegment.addAnnotation('request_note', this.getNote());
|
|
585
|
+
|
|
586
|
+
const result = await _httpGetExecute(options, this, subsegment);
|
|
587
|
+
|
|
588
|
+
console.log("RESULT", result);
|
|
589
|
+
|
|
590
|
+
subsegment.addAnnotation('success', result ? "true" : "false");
|
|
591
|
+
subsegment.addAnnotation('status_code', this.#response?.statusCode || 500);
|
|
592
|
+
subsegment.addAnnotation('note', this.getNote());
|
|
593
|
+
return result;
|
|
594
|
+
} catch (error) {
|
|
595
|
+
DebugAndLog.error(`Error in APIRequest call to remote endpoint (${this.getNote()}): ${error.message}`, error.stack);
|
|
596
|
+
subsegment.addError(error);
|
|
597
|
+
throw error;
|
|
598
|
+
} finally {
|
|
599
|
+
subsegment.close();
|
|
600
|
+
// AWSXRay.setSegment(parentSegment);
|
|
601
|
+
}
|
|
602
|
+
}/*, customSegment*/);
|
|
603
|
+
} else {
|
|
604
|
+
await _httpGetExecute(options, this);
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
// we now have a completed response
|
|
609
|
+
resolve( this.#response );
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
DebugAndLog.error(`Error in APIRequest call to _httpGetExecute (${this.getNote()}): ${error.message}`, error.stack);
|
|
613
|
+
reject(APIRequest.responseFormat(false, 500, "Error during send request"));
|
|
614
|
+
}
|
|
615
|
+
} catch (error) {
|
|
616
|
+
DebugAndLog.error(`API error while trying request for host ${this.getHost()} ${this.getNote()} ${error.message}`, { APIRequest: this.toObject(), trace: error.stack } );
|
|
617
|
+
reject(APIRequest.responseFormat(false, 500, "Error during send request"));
|
|
618
|
+
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Get information about the ClientRequest and the Response including
|
|
627
|
+
* any redirects encountered, the request and response objects,
|
|
628
|
+
* whether or not the request was sent, and the max number of
|
|
629
|
+
* redirects allowed.
|
|
630
|
+
* @returns { {MAX_REDIRECTS: number, request: object, requestComplete: boolean, redirects: Array<string>, response: object}} Information about the request
|
|
631
|
+
*/
|
|
632
|
+
toObject() {
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
MAX_REDIRECTS: APIRequest.MAX_REDIRECTS,
|
|
636
|
+
request: this.#request,
|
|
637
|
+
requestComplete: this.#requestComplete,
|
|
638
|
+
redirects: this.#redirects,
|
|
639
|
+
response: this.#response
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Formats the response for returning to program logic
|
|
646
|
+
* @param {boolean} success
|
|
647
|
+
* @param {number} statusCode
|
|
648
|
+
* @param {string} message
|
|
649
|
+
* @param {object} headers
|
|
650
|
+
* @param {string} body
|
|
651
|
+
* @returns {
|
|
652
|
+
* {
|
|
653
|
+
* success: boolean
|
|
654
|
+
* statusCode: number
|
|
655
|
+
* headers: object
|
|
656
|
+
* body: string
|
|
657
|
+
* message: string
|
|
658
|
+
* }
|
|
659
|
+
* }
|
|
660
|
+
*/
|
|
661
|
+
static responseFormat(success = false, statusCode = 0, message = null, headers = null, body = null) {
|
|
662
|
+
|
|
663
|
+
return {
|
|
664
|
+
success: success,
|
|
665
|
+
statusCode: statusCode,
|
|
666
|
+
headers: headers,
|
|
667
|
+
body: body,
|
|
668
|
+
message: message
|
|
669
|
+
};
|
|
670
|
+
};
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
module.exports = APIRequest;
|