mythix 2.8.7 → 2.8.9
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mythix",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.9",
|
|
4
4
|
"description": "Mythix is a NodeJS web-app framework",
|
|
5
5
|
"main": "src/index",
|
|
6
6
|
"scripts": {
|
|
@@ -20,17 +20,17 @@
|
|
|
20
20
|
"homepage": "https://github.com/th317erd/mythix#readme",
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@spothero/eslint-plugin-spothero": "github:spothero/eslint-plugin-spothero",
|
|
23
|
-
"@types/node": "^18.7
|
|
23
|
+
"@types/node": "^18.11.7",
|
|
24
24
|
"colors": "^1.4.0",
|
|
25
25
|
"diff": "^5.1.0",
|
|
26
|
-
"eslint": "^8.
|
|
26
|
+
"eslint": "^8.26.0",
|
|
27
27
|
"jasmine": "^4.4.0"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@types/events": "^3.0.0",
|
|
31
31
|
"chokidar": "^3.5.3",
|
|
32
32
|
"cmded": "^1.2.5",
|
|
33
|
-
"express": "^4.18.
|
|
33
|
+
"express": "^4.18.2",
|
|
34
34
|
"express-busboy": "github:th317erd/express-busboy#0754a570d7979097b31e48655b80d3fcd628d4e4",
|
|
35
35
|
"form-data": "^4.0.0",
|
|
36
36
|
"luxon": "^3.0.4",
|
|
@@ -106,19 +106,28 @@ function nodeRequestHandler(routeName, requestOptions) {
|
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
response.on('end', function() {
|
|
109
|
-
response.rawBody =
|
|
109
|
+
response.rawBody = responseData;
|
|
110
|
+
|
|
111
|
+
if (response.statusCode > 399) {
|
|
112
|
+
var error = new Error(response.statusText);
|
|
113
|
+
error.response = response;
|
|
114
|
+
|
|
115
|
+
reject(error);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
110
118
|
|
|
111
119
|
try {
|
|
112
120
|
var contentType = response.headers['content-type'];
|
|
121
|
+
var data;
|
|
113
122
|
|
|
114
|
-
if (contentType && contentType.match(/application\/json/i))
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
if (contentType && contentType.match(/application\/json/i))
|
|
124
|
+
data = JSON.parse(responseData.toString('utf8'));
|
|
125
|
+
else if (contentType && contentType.match(/text\/(plain|html)/))
|
|
126
|
+
data = responseData.toString('utf8');
|
|
127
|
+
else
|
|
128
|
+
data = response.body;
|
|
120
129
|
|
|
121
|
-
resolve(response);
|
|
130
|
+
resolve({ response, body: data });
|
|
122
131
|
} catch (error) {
|
|
123
132
|
return reject(error);
|
|
124
133
|
}
|
|
@@ -190,7 +199,7 @@ function browserRequestHandler(routeName, requestOptions) {
|
|
|
190
199
|
if (typeof requestOptions.responseHandler === 'function')
|
|
191
200
|
return requestOptions.responseHandler(response);
|
|
192
201
|
|
|
193
|
-
if (!response.ok) {
|
|
202
|
+
if (!response.ok || response.statusCode > 399) {
|
|
194
203
|
var error = new Error(response.statusText);
|
|
195
204
|
error.response = response;
|
|
196
205
|
|
|
@@ -199,14 +208,16 @@ function browserRequestHandler(routeName, requestOptions) {
|
|
|
199
208
|
}
|
|
200
209
|
|
|
201
210
|
var contentType = response.headers.get('Content-Type');
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
211
|
+
let data;
|
|
212
|
+
|
|
213
|
+
if (contentType && contentType.match(/application\/json/i))
|
|
214
|
+
data = response.json();
|
|
215
|
+
else if (contentType && contentType.match(/text\/(plain|html)/i))
|
|
216
|
+
data = response.text();
|
|
217
|
+
else
|
|
218
|
+
data = response.body;
|
|
208
219
|
|
|
209
|
-
resolve(response);
|
|
220
|
+
resolve({ response, body: data });
|
|
210
221
|
},
|
|
211
222
|
function(error) {
|
|
212
223
|
reject(error);
|
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* global Buffer */
|
|
4
|
+
|
|
5
|
+
const Path = require('path');
|
|
6
|
+
const FileSystem = require('fs');
|
|
7
|
+
const HTTP = require('http');
|
|
8
|
+
const HTTPS = require('https');
|
|
9
|
+
const Nife = require('nife');
|
|
10
|
+
const Express = require('express');
|
|
11
|
+
const ExpressBusBoy = require('express-busboy');
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
HTTPBaseError,
|
|
15
|
+
HTTPNotFoundError,
|
|
16
|
+
HTTPBadRequestError,
|
|
17
|
+
HTTPBadContentTypeError,
|
|
18
|
+
HTTPInternalServerError,
|
|
19
|
+
} = require('./http-errors');
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
statusCodeToMessage,
|
|
23
|
+
} = require('../utils/http-utils');
|
|
24
|
+
|
|
25
|
+
const REQUEST_ID_POSTFIX_LENGTH = 4;
|
|
26
|
+
const REQUEST_TIME_RESOLUTION = 3;
|
|
27
|
+
|
|
28
|
+
const DEFAULT_FILE_UPLOAD_BUFFER_SIZE = 2 * 1024 * 1024; // 2mb
|
|
29
|
+
const DEFAULT_FILE_UPLOAD_SIZE_LIMIT = 2 * 1024 * 1024; // 10mb
|
|
30
|
+
|
|
31
|
+
class HTTPServer {
|
|
32
|
+
constructor(application, _opts) {
|
|
33
|
+
let uploadPath = Path.resolve(application.getTempPath(), 'uploads');
|
|
34
|
+
|
|
35
|
+
let opts = Nife.extend(true, {
|
|
36
|
+
host: 'localhost',
|
|
37
|
+
port: '8000',
|
|
38
|
+
https: false,
|
|
39
|
+
uploads: {
|
|
40
|
+
upload: true,
|
|
41
|
+
path: uploadPath,
|
|
42
|
+
allowedPath: /./i,
|
|
43
|
+
highWaterMark: DEFAULT_FILE_UPLOAD_BUFFER_SIZE,
|
|
44
|
+
limits: {
|
|
45
|
+
fileSize: DEFAULT_FILE_UPLOAD_SIZE_LIMIT,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
}, _opts || {});
|
|
49
|
+
|
|
50
|
+
Object.defineProperties(this, {
|
|
51
|
+
'application': {
|
|
52
|
+
writable: false,
|
|
53
|
+
enumerable: false,
|
|
54
|
+
configurable: true,
|
|
55
|
+
value: application,
|
|
56
|
+
},
|
|
57
|
+
'server': {
|
|
58
|
+
writable: true,
|
|
59
|
+
enumerable: false,
|
|
60
|
+
configurable: true,
|
|
61
|
+
value: null,
|
|
62
|
+
},
|
|
63
|
+
'options': {
|
|
64
|
+
writable: false,
|
|
65
|
+
enumerable: false,
|
|
66
|
+
configurable: true,
|
|
67
|
+
value: opts,
|
|
68
|
+
},
|
|
69
|
+
'routes': {
|
|
70
|
+
writable: true,
|
|
71
|
+
enumerable: false,
|
|
72
|
+
configurable: true,
|
|
73
|
+
value: null,
|
|
74
|
+
},
|
|
75
|
+
'middleware': {
|
|
76
|
+
writable: true,
|
|
77
|
+
enumerable: false,
|
|
78
|
+
configurable: true,
|
|
79
|
+
value: opts.middleware,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getApplication() {
|
|
85
|
+
return this.application;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getLogger() {
|
|
89
|
+
let application = this.getApplication();
|
|
90
|
+
return application.getLogger();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getOptions() {
|
|
94
|
+
return this.options;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
getHTTPSCredentials(options) {
|
|
98
|
+
let keyContent = options.key;
|
|
99
|
+
if (!keyContent && options.keyPath)
|
|
100
|
+
keyContent = FileSystem.readFileSync(options.keyPath, 'latin1');
|
|
101
|
+
|
|
102
|
+
let certContent = options.cert;
|
|
103
|
+
if (!certContent && options.certPath)
|
|
104
|
+
certContent = FileSystem.readFileSync(options.certPath, 'latin1');
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
key: keyContent,
|
|
108
|
+
cert: certContent,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setRoutes(routes) {
|
|
113
|
+
this.routes = routes;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
executeMiddleware(middleware, request, response) {
|
|
117
|
+
let { route, params } = (this.findFirstMatchingRoute(request, this.routes) || {});
|
|
118
|
+
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
if (Nife.isEmpty(middleware)) {
|
|
121
|
+
resolve();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let application = this.getApplication();
|
|
126
|
+
if (!request.mythixApplication)
|
|
127
|
+
request.mythixApplication = application;
|
|
128
|
+
|
|
129
|
+
let logger = request.mythixLogger;
|
|
130
|
+
if (!logger)
|
|
131
|
+
logger = request.mythixLogger = this.createRequestLogger(application, request);
|
|
132
|
+
|
|
133
|
+
request.route = route;
|
|
134
|
+
request.params = params;
|
|
135
|
+
|
|
136
|
+
let middlewareIndex = 0;
|
|
137
|
+
const next = async () => {
|
|
138
|
+
if (middlewareIndex >= middleware.length)
|
|
139
|
+
return resolve();
|
|
140
|
+
|
|
141
|
+
let middlewareFunc = middleware[middlewareIndex++];
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
await middlewareFunc.call(this, request, response, next);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
let statusCode = error.statusCode || error.status_code || 500;
|
|
147
|
+
|
|
148
|
+
if (error instanceof HTTPBaseError) {
|
|
149
|
+
logger.error(`Error: ${statusCode} ${statusCodeToMessage(statusCode)}`);
|
|
150
|
+
this.errorHandler(error, error.getMessage(), statusCode, response, request);
|
|
151
|
+
} else {
|
|
152
|
+
if (statusCode) {
|
|
153
|
+
logger.error(`Error: ${statusCode} ${statusCodeToMessage(statusCode)}`);
|
|
154
|
+
this.errorHandler(error, error.message, statusCode, response, request);
|
|
155
|
+
} else {
|
|
156
|
+
logger.error(`Error: ${error.message}`, error);
|
|
157
|
+
this.errorHandler(error, error.message, 500, response, request);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
reject(error);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
next().catch(reject);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
baseMiddleware(request, response, rootNext) {
|
|
170
|
+
let middleware = this.middleware;
|
|
171
|
+
if (Nife.isEmpty(middleware))
|
|
172
|
+
return rootNext();
|
|
173
|
+
|
|
174
|
+
this.executeMiddleware(middleware, request, response).then(
|
|
175
|
+
() => rootNext(),
|
|
176
|
+
(error) => {
|
|
177
|
+
if (!(error instanceof HTTPBaseError))
|
|
178
|
+
this.getApplication().getLogger().error('Error in middleware: ', error);
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
findFirstMatchingRoute(request, _routes) {
|
|
184
|
+
const routeMatcher = (route, method, path, contentType) => {
|
|
185
|
+
let {
|
|
186
|
+
methodMatcher,
|
|
187
|
+
contentTypeMatcher,
|
|
188
|
+
pathMatcher,
|
|
189
|
+
} = route;
|
|
190
|
+
|
|
191
|
+
if (typeof methodMatcher === 'function' && !methodMatcher(method))
|
|
192
|
+
return;
|
|
193
|
+
|
|
194
|
+
let result = (typeof pathMatcher !== 'function') ? false : pathMatcher(path);
|
|
195
|
+
if (!result)
|
|
196
|
+
return;
|
|
197
|
+
|
|
198
|
+
if (typeof contentTypeMatcher === 'function' && !contentTypeMatcher(contentType))
|
|
199
|
+
throw new HTTPBadContentTypeError(route);
|
|
200
|
+
|
|
201
|
+
return result;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
let routes = _routes || [];
|
|
205
|
+
let method = request.method;
|
|
206
|
+
let contentType = Nife.get(request, 'headers.content-type');
|
|
207
|
+
let path = request.path;
|
|
208
|
+
|
|
209
|
+
for (let i = 0, il = routes.length; i < il; i++) {
|
|
210
|
+
let route = routes[i];
|
|
211
|
+
let result = routeMatcher(route, method, path, contentType);
|
|
212
|
+
if (!result)
|
|
213
|
+
continue;
|
|
214
|
+
|
|
215
|
+
return { route, params: result };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
throw new HTTPNotFoundError();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
getRouteController(_controller, route, params, request) {
|
|
222
|
+
let controller = _controller;
|
|
223
|
+
|
|
224
|
+
if (typeof controller === 'function') {
|
|
225
|
+
if (controller.constructor === Function.prototype.constructor) {
|
|
226
|
+
controller = controller.call(this, request, route, params);
|
|
227
|
+
if (Nife.instanceOf(controller, 'string'))
|
|
228
|
+
controller = this.getApplication().getController(controller);
|
|
229
|
+
else if (typeof controller === 'function')
|
|
230
|
+
controller = { controller };
|
|
231
|
+
} else {
|
|
232
|
+
controller = { controller };
|
|
233
|
+
}
|
|
234
|
+
} else if (Nife.instanceOf(controller, 'string')) {
|
|
235
|
+
controller = this.getApplication().getController(controller);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return controller;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
createRequestLogger(application, request) {
|
|
242
|
+
let requestID = (Date.now() + Math.random()).toFixed(REQUEST_ID_POSTFIX_LENGTH);
|
|
243
|
+
|
|
244
|
+
if (request.mythixLogger) {
|
|
245
|
+
if (!request.mythixRequestID)
|
|
246
|
+
request.mythixRequestID = requestID;
|
|
247
|
+
|
|
248
|
+
return request.mythixLogger;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let logger = application.getLogger();
|
|
252
|
+
let loggerMethod = ('' + request.method).toUpperCase();
|
|
253
|
+
let loggerURL = ('' + request.path);
|
|
254
|
+
let ipAddress = Nife.get(request, 'client.remoteAddress', '<unknown IP address>');
|
|
255
|
+
|
|
256
|
+
request.mythixRequestID = requestID;
|
|
257
|
+
|
|
258
|
+
return logger.clone({ formatter: (output) => `{${ipAddress}} - [#${requestID} ${loggerMethod} ${loggerURL}]: ${output}`});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
validateQueryParam(route, query, paramName, queryValue, queryParams) {
|
|
262
|
+
let { validate } = queryParams;
|
|
263
|
+
if (!validate)
|
|
264
|
+
return true;
|
|
265
|
+
|
|
266
|
+
if (validate instanceof RegExp)
|
|
267
|
+
return !!('' + queryValue).match(validate);
|
|
268
|
+
else if (typeof validate === 'function')
|
|
269
|
+
return !!validate.call(route, queryValue, paramName, query, queryParams);
|
|
270
|
+
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
compileQueryParams(route, query, queryParams) {
|
|
275
|
+
let finalQuery = Object.assign({}, query || {});
|
|
276
|
+
|
|
277
|
+
let paramNames = Object.keys(queryParams || {});
|
|
278
|
+
for (let i = 0, il = paramNames.length; i < il; i++) {
|
|
279
|
+
let paramName = paramNames[i];
|
|
280
|
+
let queryParam = queryParams[paramName];
|
|
281
|
+
if (!queryParam)
|
|
282
|
+
continue;
|
|
283
|
+
|
|
284
|
+
let queryValue = finalQuery[paramName];
|
|
285
|
+
if (queryValue == null) {
|
|
286
|
+
if (queryParam.required)
|
|
287
|
+
throw new HTTPBadRequestError(route, `Query param "${paramName}" is required`);
|
|
288
|
+
|
|
289
|
+
if (Object.prototype.hasOwnProperty.call(queryParam, 'defaultValue'))
|
|
290
|
+
finalQuery[paramName] = queryParam['defaultValue'];
|
|
291
|
+
} else {
|
|
292
|
+
if (!this.validateQueryParam(route, finalQuery, paramName, queryValue, queryParam))
|
|
293
|
+
throw new HTTPBadRequestError(route, `Query param "${paramName}" is invalid`);
|
|
294
|
+
|
|
295
|
+
if (Object.prototype.hasOwnProperty.call(queryParam, 'type'))
|
|
296
|
+
finalQuery[paramName] = Nife.coerceValue(queryValue, queryParam['type']);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return finalQuery;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async sendRequestToController(request, response, context) {
|
|
304
|
+
const executeRequest = async () => {
|
|
305
|
+
let controllerInstance = context.controllerInstance;
|
|
306
|
+
|
|
307
|
+
// Compile query params
|
|
308
|
+
context.query = this.compileQueryParams(context.route, context.query, (context.route && context.route.queryParams));
|
|
309
|
+
|
|
310
|
+
let route = context.route;
|
|
311
|
+
|
|
312
|
+
// Execute middleware if any exists
|
|
313
|
+
let middleware = (typeof controllerInstance.getMiddleware === 'function') ? controllerInstance.getMiddleware.call(controllerInstance, context) : [];
|
|
314
|
+
if (route && Nife.instanceOf(route.middleware, 'array') && Nife.isNotEmpty(route.middleware))
|
|
315
|
+
middleware = route.middleware.concat((middleware) ? middleware : []);
|
|
316
|
+
|
|
317
|
+
if (Nife.isNotEmpty(middleware))
|
|
318
|
+
await this.executeMiddleware(middleware, request, response);
|
|
319
|
+
|
|
320
|
+
return await controllerInstance.handleIncomingRequest.apply(controllerInstance, [ request, response, context ]);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
let application = this.getApplication();
|
|
324
|
+
let dbConnection = (typeof application.getDBConnection === 'function') ? application.getDBConnection() : undefined;
|
|
325
|
+
|
|
326
|
+
if (dbConnection && typeof dbConnection.createContext === 'function')
|
|
327
|
+
return await dbConnection.createContext(executeRequest, dbConnection, dbConnection);
|
|
328
|
+
else
|
|
329
|
+
return await executeRequest();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async baseRouter(request, response, next) {
|
|
333
|
+
let startTime = Nife.now();
|
|
334
|
+
let application = this.getApplication();
|
|
335
|
+
let controllerInstance;
|
|
336
|
+
let logger;
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
logger = this.createRequestLogger(application, request);
|
|
340
|
+
logger.info('Starting request');
|
|
341
|
+
|
|
342
|
+
let { route, params } = (this.findFirstMatchingRoute(request, this.routes) || {});
|
|
343
|
+
|
|
344
|
+
request.params = params || {};
|
|
345
|
+
|
|
346
|
+
let _controller = this.getRouteController(route.controller, route, params, request);
|
|
347
|
+
let {
|
|
348
|
+
controller,
|
|
349
|
+
controllerMethod,
|
|
350
|
+
} = (_controller || {});
|
|
351
|
+
|
|
352
|
+
let ControllerConstructor = controller;
|
|
353
|
+
|
|
354
|
+
if (!controller)
|
|
355
|
+
throw new HTTPInternalServerError(route, `Controller not found for route ${route.url}`);
|
|
356
|
+
|
|
357
|
+
if (Nife.isEmpty(controllerMethod))
|
|
358
|
+
controllerMethod = (request.method || 'get').toLowerCase();
|
|
359
|
+
|
|
360
|
+
controllerInstance = new ControllerConstructor(application, logger || application.getLogger(), request, response);
|
|
361
|
+
|
|
362
|
+
let context = {
|
|
363
|
+
params: request.params,
|
|
364
|
+
query: request.query,
|
|
365
|
+
route,
|
|
366
|
+
controller,
|
|
367
|
+
controllerMethod,
|
|
368
|
+
controllerInstance,
|
|
369
|
+
startTime,
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
let controllerResult = await this.sendRequestToController(request, response, context);
|
|
373
|
+
|
|
374
|
+
if (!(response.finished || response.statusMessage)) {
|
|
375
|
+
const handleOutgoing = async () => {
|
|
376
|
+
return await controllerInstance.handleOutgoingResponse(controllerResult, request, response, context);
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
let dbConnection = (typeof application.getDBConnection === 'function') ? application.getDBConnection() : undefined;
|
|
380
|
+
if (dbConnection && typeof dbConnection.createContext === 'function')
|
|
381
|
+
await dbConnection.createContext(handleOutgoing, dbConnection, dbConnection);
|
|
382
|
+
else
|
|
383
|
+
await handleOutgoing();
|
|
384
|
+
} else if (!response.finished) {
|
|
385
|
+
response.end();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let statusCode = response.statusCode || 200;
|
|
389
|
+
let requestTime = Nife.now() - startTime;
|
|
390
|
+
|
|
391
|
+
logger.log(`Completed request in ${requestTime.toFixed(REQUEST_TIME_RESOLUTION)}ms: ${statusCode} ${response.statusMessage || statusCodeToMessage(statusCode)}`);
|
|
392
|
+
} catch (error) {
|
|
393
|
+
if ((error instanceof HTTPInternalServerError || !(error instanceof HTTPBaseError)) && application.getOptions().testMode)
|
|
394
|
+
(logger || application.getLogger()).error(error);
|
|
395
|
+
|
|
396
|
+
let requestTime = Nife.now() - startTime;
|
|
397
|
+
let statusCode;
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
statusCode = error.statusCode || error.status_code || 500;
|
|
401
|
+
|
|
402
|
+
if (controllerInstance && typeof controllerInstance.errorHandler === 'function')
|
|
403
|
+
await controllerInstance.errorHandler(error, statusCode, request, response);
|
|
404
|
+
else if (error instanceof HTTPBaseError)
|
|
405
|
+
await this.errorHandler(error, error.getMessage(), statusCode, response, request);
|
|
406
|
+
else
|
|
407
|
+
await this.errorHandler(error, error.message, statusCode, response, request);
|
|
408
|
+
|
|
409
|
+
} catch (error2) {
|
|
410
|
+
statusCode = error2.statusCode || error2.status_code || 500;
|
|
411
|
+
|
|
412
|
+
await this.errorHandler(error2, error2.message, statusCode, response, request);
|
|
413
|
+
|
|
414
|
+
logger.log(`Completed request in ${requestTime.toFixed(REQUEST_TIME_RESOLUTION)}ms: ${statusCode} ${statusCodeToMessage(statusCode)}`, error2);
|
|
415
|
+
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
(logger || application.getLogger()).log(`Completed request in ${requestTime.toFixed(REQUEST_TIME_RESOLUTION)}ms: ${statusCode} ${statusCodeToMessage(statusCode)}`, error);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return next();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
errorHandler(error, message, statusCode, response /*, request */) {
|
|
426
|
+
if (response.statusMessage)
|
|
427
|
+
return;
|
|
428
|
+
|
|
429
|
+
if (error && error.headers) {
|
|
430
|
+
let headers = error.headers;
|
|
431
|
+
let headerKeys = Object.keys(headers);
|
|
432
|
+
|
|
433
|
+
for (let i = 0, il = headerKeys.length; i < il; i++) {
|
|
434
|
+
let headerKey = headerKeys[i];
|
|
435
|
+
let value = headers[headerKey];
|
|
436
|
+
if (value == null)
|
|
437
|
+
continue;
|
|
438
|
+
|
|
439
|
+
response.header(headerKey, ('' + value));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
response.status(statusCode || 500).send(message || statusCodeToMessage(statusCode) || 'Internal Server Error');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
createExpressApplication(options) {
|
|
447
|
+
// eslint-disable-next-line new-cap
|
|
448
|
+
let app = Express();
|
|
449
|
+
|
|
450
|
+
app.use(Express.raw({ type: 'application/json' }));
|
|
451
|
+
|
|
452
|
+
// Store _rawBody for request
|
|
453
|
+
app.use((request, response, next) => {
|
|
454
|
+
if ((/application\/json/i).test(request.headers['content-type']) && Buffer.isBuffer(request.body)) {
|
|
455
|
+
let bodyStr = request.body.toString('utf8');
|
|
456
|
+
request._rawBody = bodyStr;
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
request.body = JSON.parse(bodyStr);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
request.body = bodyStr;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
next();
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
ExpressBusBoy.extend(app, options.uploads);
|
|
469
|
+
|
|
470
|
+
return app;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async start() {
|
|
474
|
+
let options = this.getOptions();
|
|
475
|
+
let app = this.createExpressApplication(options);
|
|
476
|
+
let portString = (options.port) ? `:${options.port}` : '';
|
|
477
|
+
let server;
|
|
478
|
+
|
|
479
|
+
app.use(this.baseMiddleware.bind(this));
|
|
480
|
+
app.all('*', this.baseRouter.bind(this));
|
|
481
|
+
|
|
482
|
+
this.getLogger().log(`Starting ${(options.https) ? 'HTTPS' : 'HTTP'} server ${(options.https) ? 'https' : 'http'}://${options.host}${portString}...`);
|
|
483
|
+
|
|
484
|
+
if (options.https) {
|
|
485
|
+
let credentials = await this.getHTTPSCredentials(options.https);
|
|
486
|
+
server = HTTPS.createServer(credentials, app);
|
|
487
|
+
} else {
|
|
488
|
+
server = HTTP.createServer(app);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
server.listen(options.port);
|
|
492
|
+
|
|
493
|
+
this.server = server;
|
|
494
|
+
|
|
495
|
+
let listeningPort = server.address().port;
|
|
496
|
+
|
|
497
|
+
this.getLogger().info(`Web server listening at ${(options.https) ? 'https' : 'http'}://${options.host}:${listeningPort}`);
|
|
498
|
+
|
|
499
|
+
return server;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async stop() {
|
|
503
|
+
let server = this.server;
|
|
504
|
+
if (!server)
|
|
505
|
+
return;
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
this.getLogger().info('Shutting down web server...');
|
|
509
|
+
|
|
510
|
+
await new Promise((resolve, reject) => {
|
|
511
|
+
server.close((error) => {
|
|
512
|
+
if (error)
|
|
513
|
+
return reject(error);
|
|
514
|
+
|
|
515
|
+
resolve();
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
this.getLogger().info('Web server shut down successfully!');
|
|
520
|
+
} catch (error) {
|
|
521
|
+
this.getLogger().error('Error stopping HTTP server: ', error);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
module.exports = {
|
|
527
|
+
HTTPServer,
|
|
528
|
+
};
|