@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,547 @@
|
|
|
1
|
+
|
|
2
|
+
const jsonGenericResponse = require('./generic.response.json');
|
|
3
|
+
const htmlGenericResponse = require('./generic.response.html');
|
|
4
|
+
const rssGenericResponse = require('./generic.response.rss');
|
|
5
|
+
const xmlGenericResponse = require('./generic.response.xml');
|
|
6
|
+
const textGenericResponse = require('./generic.response.text');
|
|
7
|
+
const ClientRequest = require('./ClientRequest.class');
|
|
8
|
+
const DebugAndLog = require('./DebugAndLog.class');
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
Example Response
|
|
12
|
+
statusCode: 404,
|
|
13
|
+
headers: {
|
|
14
|
+
"Access-Control-Allow-Origin": "*",
|
|
15
|
+
"Content-Type": "application/json"
|
|
16
|
+
},
|
|
17
|
+
body: {
|
|
18
|
+
message: "Not Found"
|
|
19
|
+
}
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Can be used to create a custom Response interface
|
|
23
|
+
*/
|
|
24
|
+
class Response {
|
|
25
|
+
|
|
26
|
+
static #isInitialized = false;
|
|
27
|
+
|
|
28
|
+
static #jsonResponses = jsonGenericResponse;
|
|
29
|
+
static #htmlResponses = htmlGenericResponse;
|
|
30
|
+
static #rssResponses = rssGenericResponse;
|
|
31
|
+
static #xmlResponses = xmlGenericResponse;
|
|
32
|
+
static #textResponses = textGenericResponse;
|
|
33
|
+
|
|
34
|
+
static CONTENT_TYPE = {
|
|
35
|
+
JSON: Response.#jsonResponses.contentType,
|
|
36
|
+
HTML: Response.#htmlResponses.contentType,
|
|
37
|
+
XML: Response.#xmlResponses.contentType,
|
|
38
|
+
RSS: Response.#rssResponses.contentType,
|
|
39
|
+
TEXT: Response.#textResponses.contentType,
|
|
40
|
+
JAVASCRIPT: 'application/javascript',
|
|
41
|
+
CSS: 'text/css',
|
|
42
|
+
CSV: 'text/csv'
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
static #settings = {
|
|
46
|
+
errorExpirationInSeconds: (60 * 3),
|
|
47
|
+
routeExpirationInSeconds: 0,
|
|
48
|
+
contentType: Response.CONTENT_TYPE.JSON
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
_clientRequest = null;
|
|
52
|
+
_statusCode = 200;
|
|
53
|
+
_headers = {};
|
|
54
|
+
_body = null;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {ClientRequest} clientRequest
|
|
58
|
+
* @param {{statusCode: number, headers: object, body: string|number|object|array }} obj Default structure.
|
|
59
|
+
*/
|
|
60
|
+
constructor(clientRequest, obj = {}, contentType = null) {
|
|
61
|
+
this._clientRequest = clientRequest;
|
|
62
|
+
this.reset(obj, contentType);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @typedef statusResponseObject
|
|
67
|
+
* @property {number} statusCode
|
|
68
|
+
* @property {object} headers
|
|
69
|
+
* @property {object|array} body
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Initialize the Response class for all responses.
|
|
74
|
+
* Add Response.init(options) to the Config.init process or at the
|
|
75
|
+
* top of the main index.js file outside of the handler.
|
|
76
|
+
* @param {number} options.settings.errorExpirationInSeconds
|
|
77
|
+
* @param {number} options.settings.routeExpirationInSeconds
|
|
78
|
+
* @param {string} options.settings.contentType Any one of available Response.CONTENT_TYPE values
|
|
79
|
+
* @param {{response200: statusResponseObject, response404: statusResponseObject, response500: statusResponseObject}} options.jsonResponses
|
|
80
|
+
*/
|
|
81
|
+
static init = (options) => {
|
|
82
|
+
if (!Response.#isInitialized) {
|
|
83
|
+
|
|
84
|
+
Response.#isInitialized = true;
|
|
85
|
+
|
|
86
|
+
if ( options?.settings ) {
|
|
87
|
+
// merge settings using assign object
|
|
88
|
+
//this.#settings = Object.assign({}, Response.#settings, options.settings);
|
|
89
|
+
Response.#settings = { ...Response.#settings, ...options.settings };
|
|
90
|
+
}
|
|
91
|
+
if ( options?.jsonResponses ) {
|
|
92
|
+
// merge settings using assign object
|
|
93
|
+
//Response.#jsonResponses = Object.assign({}, Response.#jsonResponses, options.jsonResponses);
|
|
94
|
+
Response.#jsonResponses = { ...Response.#jsonResponses, ...options.jsonResponses };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if ( options?.htmlResponses ) {
|
|
98
|
+
// merge settings using assign object
|
|
99
|
+
//Response.#htmlResponses = Object.assign({}, Response.#htmlResponses, options.htmlResponses);
|
|
100
|
+
Response.#htmlResponses = { ...Response.#htmlResponses, ...options.htmlResponses };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if ( options?.xmlResponses ) {
|
|
104
|
+
// merge settings using assign object
|
|
105
|
+
//Response.#xmlResponses = Object.assign({}, Response.#xmlResponses, options.xmlResponses);
|
|
106
|
+
Response.#htmlResponses = { ...Response.#xmlResponses, ...options.xmlResponses };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if ( options?.rssResponses ) {
|
|
110
|
+
// merge settings using assign object
|
|
111
|
+
//Response.#rssResponses = Object.assign({}, Response.#rssResponses, options.rssResponses);
|
|
112
|
+
Response.#rssResponses = { ...Response.#rssResponses, ...options.rssResponses };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if ( options?.textResponses ) {
|
|
116
|
+
// merge settings using assign object
|
|
117
|
+
//Response.#textResponses = Object.assign({}, Response.#textResponses, options.textResponses);
|
|
118
|
+
Response.#textResponses = { ...Response.#textResponses, ...options.textResponses };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Reset all properties of the response back to default values except for
|
|
126
|
+
* those properties specified in the object. Note that ClientRequest
|
|
127
|
+
* cannot be reset.
|
|
128
|
+
* @param {{statusCode: number|string, headers: object, body: string|number|object|array}} obj
|
|
129
|
+
* @param {string} contentType Accepted values may be obtained from Response.CONTENT_TYPES[JSON|HTML|XML|RSS|TEXT]
|
|
130
|
+
*/
|
|
131
|
+
reset = (obj, contentType = null) => {
|
|
132
|
+
|
|
133
|
+
let newObj = {};
|
|
134
|
+
|
|
135
|
+
newObj.statusCode = obj?.statusCode ?? 200;
|
|
136
|
+
|
|
137
|
+
if (contentType === null) {
|
|
138
|
+
const result = Response.inspectContentType(obj);
|
|
139
|
+
contentType = (result !== null) ? result : Response.#settings.contentType;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const genericResponses = Response.getGenericResponses(contentType);
|
|
143
|
+
|
|
144
|
+
newObj.headers = obj?.headers ?? genericResponses.response(newObj.statusCode).headers;
|
|
145
|
+
newObj.body = obj?.body ?? genericResponses.response(newObj.statusCode).body;
|
|
146
|
+
|
|
147
|
+
this.set(newObj, contentType);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Set the properties of the response. This will overwrite only properties
|
|
152
|
+
* supplied in the new object. Use .reset if you wish to clear out all properties even
|
|
153
|
+
* if not explicitly set in the object. ClientRequest cannot be set.
|
|
154
|
+
* @param {{statusCode: number|string, headers: object, body: string|number|object|array}} obj
|
|
155
|
+
*/
|
|
156
|
+
set = (obj, contentType = null) => {
|
|
157
|
+
|
|
158
|
+
if (contentType === null) {
|
|
159
|
+
const result = Response.inspectContentType(obj);
|
|
160
|
+
const thisResult = this.inspectContentType();
|
|
161
|
+
contentType = result || thisResult || Response.#settings.contentType;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (obj?.statusCode) this._statusCode = parseInt(obj.statusCode);
|
|
165
|
+
if (obj?.headers) this._headers = obj.headers;
|
|
166
|
+
if (obj?.body) this._body = obj.body;
|
|
167
|
+
|
|
168
|
+
this.addHeader('Content-Type', contentType);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
*
|
|
173
|
+
* @returns {number} Current statusCode of the Response
|
|
174
|
+
*/
|
|
175
|
+
getStatusCode = () => {
|
|
176
|
+
return this._statusCode;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
*
|
|
181
|
+
* @returns {object} Current headers of the Response
|
|
182
|
+
*/
|
|
183
|
+
getHeaders = () => {
|
|
184
|
+
return this._headers;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
*
|
|
189
|
+
* @returns {object|array|string|number|null} Current body of the Response
|
|
190
|
+
*/
|
|
191
|
+
getBody = () => {
|
|
192
|
+
return this._body;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
*
|
|
197
|
+
* @returns {string} Current ContentType of the Response
|
|
198
|
+
*/
|
|
199
|
+
static getContentType() {
|
|
200
|
+
return Response.#settings.contentType;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
*
|
|
205
|
+
* @returns {number} Current errorExpirationInSeconds of the Response
|
|
206
|
+
*/
|
|
207
|
+
static getErrorExpirationInSeconds() {
|
|
208
|
+
return Response.#settings.errorExpirationInSeconds;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
*
|
|
213
|
+
* @returns {number} Current routeExpirationInSeconds of the Response
|
|
214
|
+
*/
|
|
215
|
+
static getRouteExpirationInSeconds() {
|
|
216
|
+
return Response.#settings.routeExpirationInSeconds;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Static method to inspect the body and headers to determine the ContentType. Used by the internal methods.
|
|
221
|
+
* @param {{headers: object, body: object|array|string|number|null}} obj Object to inspect
|
|
222
|
+
* @returns {string|null} The ContentType as determined after inspecting the headers and body
|
|
223
|
+
*/
|
|
224
|
+
static inspectContentType = (obj) => {
|
|
225
|
+
const headerResult = Response.inspectHeaderContentType(obj.headers);
|
|
226
|
+
const bodyResult = Response.inspectBodyContentType(obj.body);
|
|
227
|
+
return (headerResult !== null) ? headerResult : bodyResult;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Static method to inspect the body to determine the ContentType. Used by the internal methods.
|
|
232
|
+
* @param {object|array|string|number|null} body
|
|
233
|
+
* @returns {string|null} The ContentType as determined after inspecting just the body
|
|
234
|
+
*/
|
|
235
|
+
static inspectBodyContentType = (body) => {
|
|
236
|
+
if (body !== null) {
|
|
237
|
+
if (typeof body === 'string') {
|
|
238
|
+
if (body.includes('</html>')) {
|
|
239
|
+
return Response.CONTENT_TYPE.HTML;
|
|
240
|
+
} else if (body.includes('</rss>')) {
|
|
241
|
+
return Response.CONTENT_TYPE.RSS;
|
|
242
|
+
} else if (body.includes('<?xml')) {
|
|
243
|
+
return Response.CONTENT_TYPE.XML;
|
|
244
|
+
} else {
|
|
245
|
+
return Response.CONTENT_TYPE.TEXT;
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
return Response.CONTENT_TYPE.JSON;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Static method to inspect the headers to determine the ContentType. Used by the internal methods.
|
|
256
|
+
* @param {object} headers
|
|
257
|
+
* @returns {string|null} The ContentType as determined after inspecting just the headers
|
|
258
|
+
*/
|
|
259
|
+
static inspectHeaderContentType = (headers) => {
|
|
260
|
+
return (headers && 'Content-Type' in headers ? headers['Content-Type'] : null);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Inspect the content type of this Response. Passes this headers and this body to the static method
|
|
265
|
+
* @returns {string|null} The ContentType as determined after inspecting the headers and body
|
|
266
|
+
*/
|
|
267
|
+
inspectContentType = () => {
|
|
268
|
+
return Response.inspectContentType({headers: this._headers, body: this._body});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Inspect the body to determine the ContentType. Passes this body to the static method
|
|
273
|
+
* @returns {string} ContentType string value determined from the current body
|
|
274
|
+
*/
|
|
275
|
+
inspectBodyContentType = () => {
|
|
276
|
+
return Response.inspectBodyContentType(this._body);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Inspect the headers to determine the ContentType. Passes this headers to the static method
|
|
281
|
+
* @returns {string} ContentType string value determined from the current headers
|
|
282
|
+
*/
|
|
283
|
+
inspectHeaderContentType = () => {
|
|
284
|
+
return Response.inspectHeaderContentType(this._headers);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get the current ContentType of the response. Inspects headers and body to determine ContentType. Returns the default from init if none is determined.
|
|
289
|
+
* @returns {string} ContentType string value determined from the header or current body
|
|
290
|
+
*/
|
|
291
|
+
getContentType = () => {
|
|
292
|
+
// Default content type is JSON
|
|
293
|
+
let defaultContentType = Response.#settings.contentType;
|
|
294
|
+
let contentType = this.inspectContentType();
|
|
295
|
+
if (contentType === null) {
|
|
296
|
+
contentType = defaultContentType;
|
|
297
|
+
}
|
|
298
|
+
return contentType;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Get the content type code for the response. This is the key for the CONTENT_TYPE object.
|
|
303
|
+
* @returns {string}
|
|
304
|
+
*/
|
|
305
|
+
getContentTypeCode = () => {
|
|
306
|
+
const contentTypeStr = this.getContentType();
|
|
307
|
+
const contentTypeCodes = Object.keys(Response.CONTENT_TYPE);
|
|
308
|
+
// loop through CONTENT_TYPE and find the index of the contentTypeStr
|
|
309
|
+
for (let i = 0; i < contentTypeCodes.length; i++) {
|
|
310
|
+
if (Response.CONTENT_TYPE[contentTypeCodes[i]] === contentTypeStr) {
|
|
311
|
+
return contentTypeCodes[i];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Set the status code of the response. This will overwrite the status code of the response.
|
|
318
|
+
* @param {number} statusCode
|
|
319
|
+
*/
|
|
320
|
+
setStatusCode = (statusCode) => {
|
|
321
|
+
this.set({statusCode: statusCode});
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Set the headers of the response. This will overwrite the headers of the response.
|
|
326
|
+
* @param {object} headers
|
|
327
|
+
*/
|
|
328
|
+
setHeaders = (headers) => {
|
|
329
|
+
this.set({headers: headers});
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Set the body of the response. This will overwrite the body of the response.
|
|
334
|
+
* @param {string|number|object|array} body
|
|
335
|
+
*/
|
|
336
|
+
setBody = (body) => {
|
|
337
|
+
this.set({body: body});
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Get the generic response for the content type. Generic responses are either provided by default from Cache-Data or loaded in during Response.init()
|
|
342
|
+
* @param {string} contentType
|
|
343
|
+
* @returns {statusResponseObject}
|
|
344
|
+
*/
|
|
345
|
+
static getGenericResponses = (contentType) => {
|
|
346
|
+
if (contentType === Response.CONTENT_TYPE.JSON || contentType === 'JSON') {
|
|
347
|
+
return Response.#jsonResponses;
|
|
348
|
+
} else if (contentType === Response.CONTENT_TYPE.HTML || contentType === 'HTML') {
|
|
349
|
+
return Response.#htmlResponses;
|
|
350
|
+
} else if (contentType === Response.CONTENT_TYPE.RSS || contentType === 'RSS') {
|
|
351
|
+
return Response.#rssResponses;
|
|
352
|
+
} else if (contentType === Response.CONTENT_TYPE.XML || contentType === 'XML') {
|
|
353
|
+
return Response.#xmlResponses;
|
|
354
|
+
} else if (contentType === Response.CONTENT_TYPE.TEXT || contentType === 'TEXT') {
|
|
355
|
+
return Response.#textResponses;
|
|
356
|
+
} else {
|
|
357
|
+
throw new Error(`Content Type: ${contentType} is not implemented for getResponses. Response.CONTENT_TYPES[JSON|HTML|XML|RSS|TEXT] must be used. Perform a custom implementation by extending the Response class.`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Add a header if it does not exist, if it exists then update the value
|
|
363
|
+
* @param {string} key
|
|
364
|
+
* @param {string} value
|
|
365
|
+
*/
|
|
366
|
+
addHeader = (key, value) => {
|
|
367
|
+
this._headers[key] = value;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
*
|
|
372
|
+
* @param {object} obj
|
|
373
|
+
*/
|
|
374
|
+
addToJsonBody = (obj) => {
|
|
375
|
+
if (typeof this._body === 'object') {
|
|
376
|
+
this._body = Object.assign({}, this._body, obj);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
*
|
|
382
|
+
* @returns {{statusCode: number, headers: object, body: null|string|Array|object}}
|
|
383
|
+
*/
|
|
384
|
+
toObject = () => {
|
|
385
|
+
return {
|
|
386
|
+
statusCode: this._statusCode,
|
|
387
|
+
headers: this._headers,
|
|
388
|
+
body: this._body
|
|
389
|
+
};
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
*
|
|
394
|
+
* @returns {string} A string representation of the Response object
|
|
395
|
+
*/
|
|
396
|
+
toString = () => {
|
|
397
|
+
return JSON.stringify(this.toObject());
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Used by JSON.stringify to convert the response to a stringified object
|
|
402
|
+
* @returns {{statusCode: number, headers: object, body: null|string|Array|object}} this class in object form ready for use by JSON.stringify
|
|
403
|
+
*/
|
|
404
|
+
toJSON = () => {
|
|
405
|
+
return this.toObject();
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Send the response back to the client. If the body is an object or array, it will be stringified.
|
|
410
|
+
* If the body is a string or number and the Content-Type header is json, it will be placed as a single element in an array then stringified.
|
|
411
|
+
* If the body of the response is null it returns null
|
|
412
|
+
* A response log entry is also created and sent to CloudWatch.
|
|
413
|
+
* @returns {{statusCode: number, headers: object, body: string}} An object containing response data formatted to return from Lambda
|
|
414
|
+
*/
|
|
415
|
+
finalize = () => {
|
|
416
|
+
|
|
417
|
+
let bodyAsString = null;
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
// if the header response type is not set, determine from contents of body. default to json
|
|
421
|
+
if (!('Content-Type' in this._headers)) {
|
|
422
|
+
this._headers['Content-Type'] = this.getContentType();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// If body is of type error then set status to 500
|
|
426
|
+
if (this._body instanceof Error) {
|
|
427
|
+
this.reset({statusCode: 500});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (this._body !== null) { // we'll keep null as null
|
|
431
|
+
|
|
432
|
+
// if response type is JSON we need to make sure we respond with stringified json
|
|
433
|
+
if (this._headers['Content-Type'] === Response.CONTENT_TYPE.JSON) {
|
|
434
|
+
|
|
435
|
+
// body is a string or number, place in array (unless the number is 404, then that signifies not found)
|
|
436
|
+
if (typeof this._body === 'string' || typeof this._body === 'number') {
|
|
437
|
+
if (this._body === 404) {
|
|
438
|
+
this.reset({statusCode: 404});
|
|
439
|
+
} else {
|
|
440
|
+
this._body = [this._body];
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// body is presumably an object or array, so stringify
|
|
445
|
+
bodyAsString = JSON.stringify(this._body);
|
|
446
|
+
|
|
447
|
+
} else { // if response type is not json we need to respond with a string (or null but we already did a null check)
|
|
448
|
+
bodyAsString = `${this._body}`;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
} catch (error) {
|
|
453
|
+
/* Log the error */
|
|
454
|
+
DebugAndLog.error(`Error Finalizing Response: ${error.message}`, error.stack);
|
|
455
|
+
this.reset({statusCode: 500});
|
|
456
|
+
bodyAsString = JSON.stringify(this._body); // we reset to 500 so stringify it
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
if (ClientRequest.requiresValidReferrer()) {
|
|
461
|
+
this.addHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
462
|
+
this.addHeader("Vary", "Origin");
|
|
463
|
+
this.addHeader("Access-Control-Allow-Origin", `https://${this._clientRequest.getClientReferrer()}`);
|
|
464
|
+
} else {
|
|
465
|
+
this.addHeader("Access-Control-Allow-Origin", "*");
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (this._statusCode >= 400) {
|
|
469
|
+
this.addHeader("Expires", (new Date(Date.now() + ( Response.#settings.errorExpirationInSeconds * 1000))).toUTCString());
|
|
470
|
+
this.addHeader("Cache-Control", "max-age="+Response.#settings.errorExpirationInSeconds);
|
|
471
|
+
} else if (Response.#settings.routeExpirationInSeconds > 0 ) {
|
|
472
|
+
this.addHeader("Expires", (new Date(Date.now() + ( Response.#settings.routeExpirationInSeconds * 1000))).toUTCString());
|
|
473
|
+
this.addHeader("Cache-Control", "max-age="+Response.#settings.routeExpirationInSeconds);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
this.addHeader('x-exec-ms', `${this._clientRequest.getFinalExecutionTime()}`);
|
|
477
|
+
|
|
478
|
+
this._log(bodyAsString);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
DebugAndLog.error(`Error Finalizing Response: Header and Logging Block: ${error.message}`, error.stack);
|
|
481
|
+
this.reset({statusCode: 500});
|
|
482
|
+
bodyAsString = JSON.stringify(this._body); // we reset to 500 so stringify it
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
statusCode: this._statusCode,
|
|
487
|
+
headers: this._headers,
|
|
488
|
+
body: bodyAsString
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Log the ClientRequest and Response to CloudWatch
|
|
495
|
+
* Formats a log entry parsing in CloudWatch Dashboard.
|
|
496
|
+
*/
|
|
497
|
+
_log(bodyAsString) {
|
|
498
|
+
|
|
499
|
+
try {
|
|
500
|
+
|
|
501
|
+
/* These are pushed onto the array in the same order that the CloudWatch
|
|
502
|
+
query is expecting to parse out.
|
|
503
|
+
-- NOTE: If you add any here, be sure to update the Dashboard template --
|
|
504
|
+
-- that parses response logs in template.yml !! --
|
|
505
|
+
-- loggingType, statusCode, bodySize, execTime, clientIP, userAgent, origin, referrer, route, params, key
|
|
506
|
+
*/
|
|
507
|
+
|
|
508
|
+
const loggingType = "RESPONSE";
|
|
509
|
+
const statusCode = this._statusCode;
|
|
510
|
+
const bytes = this._body !== null ? Buffer.byteLength(bodyAsString, 'utf8') : 0; // calculate byte size of response.body
|
|
511
|
+
const contentType = this.getContentTypeCode();
|
|
512
|
+
const execms = this._clientRequest.getFinalExecutionTime();
|
|
513
|
+
const clientIp = this._clientRequest.getClientIp();
|
|
514
|
+
const userAgent = this._clientRequest.getClientUserAgent();
|
|
515
|
+
const origin = this._clientRequest.getClientOrigin();
|
|
516
|
+
const referrer = this._clientRequest.getClientReferrer(true);
|
|
517
|
+
const {resource, queryKeys, routeLog, queryLog, apiKey } = this._clientRequest.getRequestLog();
|
|
518
|
+
|
|
519
|
+
let logFields = [];
|
|
520
|
+
logFields.push(statusCode);
|
|
521
|
+
logFields.push(bytes);
|
|
522
|
+
logFields.push(contentType);
|
|
523
|
+
logFields.push(execms);
|
|
524
|
+
logFields.push(clientIp);
|
|
525
|
+
logFields.push( (( userAgent !== "" && userAgent !== null) ? userAgent : "-").replace(/|/g, "") ); // doubtful, but userAgent could have | which will mess with log fields
|
|
526
|
+
logFields.push( (( origin !== "" && origin !== null) ? origin : "-") );
|
|
527
|
+
logFields.push( (( referrer !== "" && referrer !== null) ? referrer : "-") );
|
|
528
|
+
logFields.push(resource); // path includes any path parameter keys (not values)
|
|
529
|
+
logFields.push(queryKeys ? queryKeys : "-"); // just the keys used in query string (no values)
|
|
530
|
+
logFields.push(routeLog ? routeLog : "-"); // custom set routePath with values
|
|
531
|
+
logFields.push(queryLog ? queryLog : "-"); // custom set keys with values
|
|
532
|
+
logFields.push(apiKey ? apiKey : "-");
|
|
533
|
+
|
|
534
|
+
/* Join array together into single text string delimited by ' | ' */
|
|
535
|
+
const msg = logFields.join(" | ");
|
|
536
|
+
|
|
537
|
+
/* send it to CloudWatch via DebugAndLog.log() */
|
|
538
|
+
DebugAndLog.log(msg, loggingType);
|
|
539
|
+
|
|
540
|
+
} catch (error) {
|
|
541
|
+
DebugAndLog.error(`Error Logging Response: ${error.message}`, error.stack);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
module.exports = Response;
|