@reldens/server-utils 0.43.0 → 0.44.0
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/.claude/api-reference.md +333 -0
- package/.claude/package-architecture.md +177 -0
- package/CLAUDE.md +46 -214
- package/lib/app-server-factory/cors-configurer.js +10 -0
- package/lib/app-server-factory/development-mode-detector.js +25 -0
- package/lib/app-server-factory/protocol-enforcer.js +11 -0
- package/lib/app-server-factory/rate-limit-configurer.js +10 -0
- package/lib/app-server-factory/reverse-proxy-configurer.js +10 -0
- package/lib/app-server-factory/security-configurer.js +18 -0
- package/lib/app-server-factory.js +86 -10
- package/lib/cdn-request-handler.js +130 -0
- package/lib/event-dispatcher.js +26 -0
- package/lib/http2-cdn-server.js +110 -139
- package/lib/request-logger.js +42 -0
- package/package.json +2 -2
|
@@ -10,6 +10,8 @@ const { ServerDefaultConfigurations } = require('./server-default-configurations
|
|
|
10
10
|
const { ServerFactoryUtils } = require('./server-factory-utils');
|
|
11
11
|
const { ServerHeaders } = require('./server-headers');
|
|
12
12
|
const { ServerErrorHandler } = require('./server-error-handler');
|
|
13
|
+
const { RequestLogger } = require('./request-logger');
|
|
14
|
+
const { EventDispatcher } = require('./event-dispatcher');
|
|
13
15
|
const { DevelopmentModeDetector } = require('./app-server-factory/development-mode-detector');
|
|
14
16
|
const { ProtocolEnforcer } = require('./app-server-factory/protocol-enforcer');
|
|
15
17
|
const { SecurityConfigurer } = require('./app-server-factory/security-configurer');
|
|
@@ -117,6 +119,9 @@ class AppServerFactory
|
|
|
117
119
|
this.reverseProxyEnabled = false;
|
|
118
120
|
this.reverseProxyRules = [];
|
|
119
121
|
this.onError = null;
|
|
122
|
+
this.onRequestSuccess = null;
|
|
123
|
+
this.onRequestError = null;
|
|
124
|
+
this.onEvent = null;
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
buildStaticHeaders(res, path)
|
|
@@ -148,6 +153,7 @@ class AppServerFactory
|
|
|
148
153
|
this.setupCors();
|
|
149
154
|
this.setupRateLimiting();
|
|
150
155
|
this.setupRequestParsing();
|
|
156
|
+
this.setupRequestLogging();
|
|
151
157
|
this.setupTrustedProxy();
|
|
152
158
|
try {
|
|
153
159
|
this.appServer = this.createServer();
|
|
@@ -161,6 +167,13 @@ class AppServerFactory
|
|
|
161
167
|
}
|
|
162
168
|
return false;
|
|
163
169
|
}
|
|
170
|
+
EventDispatcher.dispatch(
|
|
171
|
+
this.onEvent,
|
|
172
|
+
'app-server-created',
|
|
173
|
+
'appServerFactory',
|
|
174
|
+
this,
|
|
175
|
+
{port: this.port, useHttps: this.useHttps, isDevelopmentMode: this.isDevelopmentMode}
|
|
176
|
+
);
|
|
164
177
|
if(this.http2CdnEnabled){
|
|
165
178
|
if(!this.createHttp2CdnServer()){
|
|
166
179
|
this.error = {message: 'The createHttp2CdnServer() returned false.'};
|
|
@@ -191,6 +204,9 @@ class AppServerFactory
|
|
|
191
204
|
this.http2CdnServer.corsOrigins = this.http2CdnCorsOrigins;
|
|
192
205
|
this.http2CdnServer.corsAllowAll = this.http2CdnCorsAllowAll;
|
|
193
206
|
this.http2CdnServer.onError = this.onError;
|
|
207
|
+
this.http2CdnServer.onRequestSuccess = this.onRequestSuccess;
|
|
208
|
+
this.http2CdnServer.onRequestError = this.onRequestError;
|
|
209
|
+
this.http2CdnServer.onEvent = this.onEvent;
|
|
194
210
|
if(!this.http2CdnServer.create()){
|
|
195
211
|
this.error = this.http2CdnServer.error;
|
|
196
212
|
return false;
|
|
@@ -199,6 +215,13 @@ class AppServerFactory
|
|
|
199
215
|
this.error = this.http2CdnServer.error;
|
|
200
216
|
return false;
|
|
201
217
|
}
|
|
218
|
+
EventDispatcher.dispatch(
|
|
219
|
+
this.onEvent,
|
|
220
|
+
'http2-cdn-created',
|
|
221
|
+
'appServerFactory',
|
|
222
|
+
this,
|
|
223
|
+
{port: this.http2CdnPort}
|
|
224
|
+
);
|
|
202
225
|
return true;
|
|
203
226
|
}
|
|
204
227
|
|
|
@@ -226,7 +249,8 @@ class AppServerFactory
|
|
|
226
249
|
{
|
|
227
250
|
let detectConfig = {
|
|
228
251
|
developmentDomains: this.developmentDomains,
|
|
229
|
-
domains: this.domains
|
|
252
|
+
domains: this.domains,
|
|
253
|
+
onEvent: this.onEvent
|
|
230
254
|
};
|
|
231
255
|
if(0 < this.developmentPatterns.length){
|
|
232
256
|
detectConfig['developmentPatterns'] = this.developmentPatterns;
|
|
@@ -260,7 +284,8 @@ class AppServerFactory
|
|
|
260
284
|
this.protocolEnforcer.setup(this.app, {
|
|
261
285
|
isDevelopmentMode: this.isDevelopmentMode,
|
|
262
286
|
useHttps: this.useHttps,
|
|
263
|
-
enforceProtocol: this.enforceProtocol
|
|
287
|
+
enforceProtocol: this.enforceProtocol,
|
|
288
|
+
onEvent: this.onEvent
|
|
264
289
|
});
|
|
265
290
|
}
|
|
266
291
|
|
|
@@ -270,11 +295,13 @@ class AppServerFactory
|
|
|
270
295
|
isDevelopmentMode: this.isDevelopmentMode,
|
|
271
296
|
useHelmet: this.useHelmet,
|
|
272
297
|
helmetConfig: this.helmetConfig,
|
|
273
|
-
developmentExternalDomains: this.developmentExternalDomains
|
|
298
|
+
developmentExternalDomains: this.developmentExternalDomains,
|
|
299
|
+
onEvent: this.onEvent
|
|
274
300
|
});
|
|
275
301
|
this.securityConfigurer.setupXssProtection(this.app, {
|
|
276
302
|
useXssProtection: this.useXssProtection,
|
|
277
|
-
sanitizeOptions: this.sanitizeOptions
|
|
303
|
+
sanitizeOptions: this.sanitizeOptions,
|
|
304
|
+
onEvent: this.onEvent
|
|
278
305
|
});
|
|
279
306
|
}
|
|
280
307
|
|
|
@@ -291,7 +318,8 @@ class AppServerFactory
|
|
|
291
318
|
let corsConfig = {
|
|
292
319
|
isDevelopmentMode: this.isDevelopmentMode,
|
|
293
320
|
useCors: this.useCors,
|
|
294
|
-
corsOrigin: this.corsOrigin
|
|
321
|
+
corsOrigin: this.corsOrigin,
|
|
322
|
+
onEvent: this.onEvent
|
|
295
323
|
};
|
|
296
324
|
if(0 < this.corsMethods.length){
|
|
297
325
|
corsConfig['corsMethods'] = this.corsMethods;
|
|
@@ -321,7 +349,8 @@ class AppServerFactory
|
|
|
321
349
|
maxRequests: this.maxRequests,
|
|
322
350
|
developmentMultiplier: this.developmentMultiplier,
|
|
323
351
|
applyKeyGenerator: this.applyKeyGenerator,
|
|
324
|
-
tooManyRequestsMessage: this.tooManyRequestsMessage
|
|
352
|
+
tooManyRequestsMessage: this.tooManyRequestsMessage,
|
|
353
|
+
onEvent: this.onEvent
|
|
325
354
|
});
|
|
326
355
|
}
|
|
327
356
|
|
|
@@ -345,6 +374,14 @@ class AppServerFactory
|
|
|
345
374
|
}
|
|
346
375
|
}
|
|
347
376
|
|
|
377
|
+
setupRequestLogging()
|
|
378
|
+
{
|
|
379
|
+
if(!this.onRequestSuccess && !this.onRequestError){
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
this.app.use(RequestLogger.createMiddleware(this.onRequestSuccess, this.onRequestError));
|
|
383
|
+
}
|
|
384
|
+
|
|
348
385
|
setupReverseProxy()
|
|
349
386
|
{
|
|
350
387
|
if(!this.reverseProxyEnabled || 0 === this.reverseProxyRules.length){
|
|
@@ -354,7 +391,8 @@ class AppServerFactory
|
|
|
354
391
|
reverseProxyRules: this.reverseProxyRules,
|
|
355
392
|
isDevelopmentMode: this.isDevelopmentMode,
|
|
356
393
|
useVirtualHosts: this.useVirtualHosts,
|
|
357
|
-
onError: this.onError
|
|
394
|
+
onError: this.onError,
|
|
395
|
+
onEvent: this.onEvent
|
|
358
396
|
});
|
|
359
397
|
}
|
|
360
398
|
|
|
@@ -446,7 +484,15 @@ class AppServerFactory
|
|
|
446
484
|
createServer()
|
|
447
485
|
{
|
|
448
486
|
if(!this.useHttps){
|
|
449
|
-
|
|
487
|
+
let httpServer = http.createServer(this.app);
|
|
488
|
+
EventDispatcher.dispatch(
|
|
489
|
+
this.onEvent,
|
|
490
|
+
'http-server-created',
|
|
491
|
+
'appServerFactory',
|
|
492
|
+
this,
|
|
493
|
+
{port: this.port}
|
|
494
|
+
);
|
|
495
|
+
return httpServer;
|
|
450
496
|
}
|
|
451
497
|
if(this.useVirtualHosts && 0 < this.domains.length){
|
|
452
498
|
return this.createHttpsServerWithSNI();
|
|
@@ -473,7 +519,15 @@ class AppServerFactory
|
|
|
473
519
|
credentials.ca = ca;
|
|
474
520
|
}
|
|
475
521
|
}
|
|
476
|
-
|
|
522
|
+
let httpsServer = https.createServer(credentials, this.app);
|
|
523
|
+
EventDispatcher.dispatch(
|
|
524
|
+
this.onEvent,
|
|
525
|
+
'https-server-created',
|
|
526
|
+
'appServerFactory',
|
|
527
|
+
this,
|
|
528
|
+
{port: this.port}
|
|
529
|
+
);
|
|
530
|
+
return httpsServer;
|
|
477
531
|
}
|
|
478
532
|
|
|
479
533
|
createHttpsServerWithSNI()
|
|
@@ -517,7 +571,15 @@ class AppServerFactory
|
|
|
517
571
|
let ctx = tls.createSecureContext({key, cert});
|
|
518
572
|
callback(null, ctx);
|
|
519
573
|
};
|
|
520
|
-
|
|
574
|
+
let sniServer = https.createServer(httpsOptions, this.app);
|
|
575
|
+
EventDispatcher.dispatch(
|
|
576
|
+
this.onEvent,
|
|
577
|
+
'sni-server-created',
|
|
578
|
+
'appServerFactory',
|
|
579
|
+
this,
|
|
580
|
+
{port: this.port, domainsCount: this.domains.length}
|
|
581
|
+
);
|
|
582
|
+
return sniServer;
|
|
521
583
|
}
|
|
522
584
|
|
|
523
585
|
loadDefaultCredentials()
|
|
@@ -543,6 +605,13 @@ class AppServerFactory
|
|
|
543
605
|
return false;
|
|
544
606
|
}
|
|
545
607
|
this.appServer.listen(listenPort);
|
|
608
|
+
EventDispatcher.dispatch(
|
|
609
|
+
this.onEvent,
|
|
610
|
+
'app-server-listening',
|
|
611
|
+
'appServerFactory',
|
|
612
|
+
this,
|
|
613
|
+
{port: listenPort}
|
|
614
|
+
);
|
|
546
615
|
return true;
|
|
547
616
|
}
|
|
548
617
|
|
|
@@ -604,6 +673,13 @@ class AppServerFactory
|
|
|
604
673
|
return false;
|
|
605
674
|
}
|
|
606
675
|
this.domains.push(domainConfig);
|
|
676
|
+
EventDispatcher.dispatch(
|
|
677
|
+
this.onEvent,
|
|
678
|
+
'domain-added',
|
|
679
|
+
'appServerFactory',
|
|
680
|
+
this,
|
|
681
|
+
{hostname: domainConfig.hostname}
|
|
682
|
+
);
|
|
607
683
|
return true;
|
|
608
684
|
}
|
|
609
685
|
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CdnRequestHandler
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { FileHandler } = require('./file-handler');
|
|
8
|
+
const { ServerFactoryUtils } = require('./server-factory-utils');
|
|
9
|
+
const { ServerErrorHandler } = require('./server-error-handler');
|
|
10
|
+
|
|
11
|
+
class CdnRequestHandler
|
|
12
|
+
{
|
|
13
|
+
|
|
14
|
+
constructor(cdnServer)
|
|
15
|
+
{
|
|
16
|
+
this.cdnServer = cdnServer;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
handleRequest(requestContext)
|
|
20
|
+
{
|
|
21
|
+
let startTime = Date.now();
|
|
22
|
+
let requestData = {
|
|
23
|
+
serverType: 'cdn',
|
|
24
|
+
method: requestContext.method,
|
|
25
|
+
path: requestContext.path,
|
|
26
|
+
hostname: requestContext.hostname,
|
|
27
|
+
statusCode: 200,
|
|
28
|
+
responseTime: 0,
|
|
29
|
+
ip: requestContext.ip,
|
|
30
|
+
userAgent: requestContext.userAgent,
|
|
31
|
+
timestamp: new Date().toISOString()
|
|
32
|
+
};
|
|
33
|
+
try {
|
|
34
|
+
if(!requestData.path){
|
|
35
|
+
requestContext.sendResponse(400);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
let requestOrigin = requestContext.origin || '';
|
|
39
|
+
let allowedOrigin = ServerFactoryUtils.validateOrigin(
|
|
40
|
+
requestOrigin,
|
|
41
|
+
this.cdnServer.corsOrigins,
|
|
42
|
+
this.cdnServer.corsAllowAll
|
|
43
|
+
);
|
|
44
|
+
if('OPTIONS' === requestData.method){
|
|
45
|
+
this.handleOptionsRequest(requestContext, allowedOrigin);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
let filePath = this.cdnServer.resolveFilePath(requestData.path);
|
|
49
|
+
if(!filePath){
|
|
50
|
+
requestData.statusCode = 404;
|
|
51
|
+
requestData.responseTime = Date.now()-startTime;
|
|
52
|
+
requestContext.sendResponse(404);
|
|
53
|
+
this.cdnServer.invokeRequestError(requestData);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
let responseHeaders = this.buildResponseHeaders(allowedOrigin, filePath, requestContext.isHttp2);
|
|
57
|
+
requestContext.onSuccess(() => {
|
|
58
|
+
requestData.responseTime = Date.now()-startTime;
|
|
59
|
+
this.cdnServer.invokeRequestSuccess(requestData);
|
|
60
|
+
});
|
|
61
|
+
requestContext.onError((err) => {
|
|
62
|
+
requestData.statusCode = 500;
|
|
63
|
+
requestData.responseTime = Date.now()-startTime;
|
|
64
|
+
requestData.error = err;
|
|
65
|
+
ServerErrorHandler.handleError(
|
|
66
|
+
this.cdnServer.onError,
|
|
67
|
+
'http2CdnServer',
|
|
68
|
+
this.cdnServer,
|
|
69
|
+
requestContext.isHttp2 ? 'stream-error' : 'http1-stream-error',
|
|
70
|
+
err,
|
|
71
|
+
{port: this.cdnServer.port, path: requestData.path}
|
|
72
|
+
);
|
|
73
|
+
this.cdnServer.invokeRequestError(requestData);
|
|
74
|
+
});
|
|
75
|
+
requestContext.sendFile(filePath, responseHeaders);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
requestData.statusCode = 500;
|
|
78
|
+
requestData.responseTime = Date.now()-startTime;
|
|
79
|
+
requestData.error = err;
|
|
80
|
+
ServerErrorHandler.handleError(
|
|
81
|
+
this.cdnServer.onError,
|
|
82
|
+
'http2CdnServer',
|
|
83
|
+
this.cdnServer,
|
|
84
|
+
requestContext.isHttp2 ? 'handle-stream-error' : 'handle-http1-request-error',
|
|
85
|
+
err,
|
|
86
|
+
{port: this.cdnServer.port, path: requestData.path}
|
|
87
|
+
);
|
|
88
|
+
this.cdnServer.invokeRequestError(requestData);
|
|
89
|
+
requestContext.sendResponse(500);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
handleOptionsRequest(requestContext, allowedOrigin)
|
|
94
|
+
{
|
|
95
|
+
let optionsHeaders = requestContext.isHttp2 ? {':status': 200} : {};
|
|
96
|
+
if(allowedOrigin){
|
|
97
|
+
optionsHeaders['access-control-allow-origin'] = allowedOrigin;
|
|
98
|
+
}
|
|
99
|
+
optionsHeaders['access-control-allow-methods'] = this.cdnServer.corsMethods;
|
|
100
|
+
optionsHeaders['access-control-allow-headers'] = this.cdnServer.corsHeaders;
|
|
101
|
+
requestContext.sendResponse(200, optionsHeaders);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
buildResponseHeaders(allowedOrigin, filePath, isHttp2)
|
|
105
|
+
{
|
|
106
|
+
let ext = FileHandler.extension(filePath);
|
|
107
|
+
let cacheAge = ServerFactoryUtils.getCacheConfigForPath(filePath, this.cdnServer.cacheConfig);
|
|
108
|
+
let responseHeaders = isHttp2 ? {':status': 200} : {};
|
|
109
|
+
let securityKeys = Object.keys(this.cdnServer.securityHeaders);
|
|
110
|
+
for(let headerKey of securityKeys){
|
|
111
|
+
responseHeaders[headerKey] = this.cdnServer.securityHeaders[headerKey];
|
|
112
|
+
}
|
|
113
|
+
if(allowedOrigin){
|
|
114
|
+
responseHeaders['access-control-allow-origin'] = allowedOrigin;
|
|
115
|
+
}
|
|
116
|
+
if(this.cdnServer.varyHeader){
|
|
117
|
+
responseHeaders['vary'] = this.cdnServer.varyHeader;
|
|
118
|
+
}
|
|
119
|
+
if(cacheAge){
|
|
120
|
+
responseHeaders['cache-control'] = 'public, max-age='+cacheAge+', immutable';
|
|
121
|
+
}
|
|
122
|
+
if(this.cdnServer.mimeTypes[ext]){
|
|
123
|
+
responseHeaders['content-type'] = this.cdnServer.mimeTypes[ext];
|
|
124
|
+
}
|
|
125
|
+
return responseHeaders;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports.CdnRequestHandler = CdnRequestHandler;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - EventDispatcher
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class EventDispatcher
|
|
8
|
+
{
|
|
9
|
+
|
|
10
|
+
static dispatch(onEventCallback, eventType, instanceName, instance, data = {})
|
|
11
|
+
{
|
|
12
|
+
if('function' !== typeof onEventCallback){
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
onEventCallback({
|
|
16
|
+
eventType: eventType,
|
|
17
|
+
instanceName: instanceName,
|
|
18
|
+
instance: instance,
|
|
19
|
+
data: data,
|
|
20
|
+
timestamp: new Date().toISOString()
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports.EventDispatcher = EventDispatcher;
|
package/lib/http2-cdn-server.js
CHANGED
|
@@ -10,6 +10,8 @@ const { ServerDefaultConfigurations } = require('./server-default-configurations
|
|
|
10
10
|
const { ServerFactoryUtils } = require('./server-factory-utils');
|
|
11
11
|
const { ServerHeaders } = require('./server-headers');
|
|
12
12
|
const { ServerErrorHandler } = require('./server-error-handler');
|
|
13
|
+
const { EventDispatcher } = require('./event-dispatcher');
|
|
14
|
+
const { CdnRequestHandler } = require('./cdn-request-handler');
|
|
13
15
|
|
|
14
16
|
class Http2CdnServer
|
|
15
17
|
{
|
|
@@ -35,6 +37,10 @@ class Http2CdnServer
|
|
|
35
37
|
this.securityHeaders = this.serverHeaders.http2SecurityHeaders;
|
|
36
38
|
this.varyHeader = this.serverHeaders.http2VaryHeader;
|
|
37
39
|
this.onError = null;
|
|
40
|
+
this.onRequestSuccess = null;
|
|
41
|
+
this.onRequestError = null;
|
|
42
|
+
this.onEvent = null;
|
|
43
|
+
this.requestHandler = new CdnRequestHandler(this);
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
create()
|
|
@@ -62,6 +68,13 @@ class Http2CdnServer
|
|
|
62
68
|
}
|
|
63
69
|
this.http2Server = http2.createSecureServer(options);
|
|
64
70
|
this.setupEventHandlers();
|
|
71
|
+
EventDispatcher.dispatch(
|
|
72
|
+
this.onEvent,
|
|
73
|
+
'cdn-server-created',
|
|
74
|
+
'http2CdnServer',
|
|
75
|
+
this,
|
|
76
|
+
{port: this.port, allowHTTP1: this.allowHTTP1}
|
|
77
|
+
);
|
|
65
78
|
return true;
|
|
66
79
|
}
|
|
67
80
|
|
|
@@ -103,160 +116,111 @@ class Http2CdnServer
|
|
|
103
116
|
{port: this.port}
|
|
104
117
|
);
|
|
105
118
|
});
|
|
119
|
+
EventDispatcher.dispatch(
|
|
120
|
+
this.onEvent,
|
|
121
|
+
'cdn-handlers-setup',
|
|
122
|
+
'http2CdnServer',
|
|
123
|
+
this,
|
|
124
|
+
{port: this.port}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
invokeRequestSuccess(requestData)
|
|
129
|
+
{
|
|
130
|
+
if('function' !== typeof this.onRequestSuccess){
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.onRequestSuccess(requestData);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
invokeRequestError(errorData)
|
|
137
|
+
{
|
|
138
|
+
if('function' !== typeof this.onRequestError){
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.onRequestError(errorData);
|
|
106
142
|
}
|
|
107
143
|
|
|
108
144
|
handleStream(stream, headers)
|
|
109
145
|
{
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
146
|
+
let requestContext = {
|
|
147
|
+
isHttp2: true,
|
|
148
|
+
method: headers[':method'],
|
|
149
|
+
path: headers[':path'],
|
|
150
|
+
hostname: headers['x-forwarded-host'] || headers[':authority'],
|
|
151
|
+
origin: headers['origin'] || '',
|
|
152
|
+
ip: null,
|
|
153
|
+
userAgent: null,
|
|
154
|
+
sendResponse: (statusCode, responseHeaders) => {
|
|
155
|
+
if(responseHeaders){
|
|
156
|
+
stream.respond(responseHeaders);
|
|
157
|
+
stream.end();
|
|
158
|
+
return;
|
|
123
159
|
}
|
|
124
|
-
|
|
125
|
-
optionsHeaders['access-control-allow-headers'] = this.corsHeaders;
|
|
126
|
-
stream.respond(optionsHeaders);
|
|
127
|
-
stream.end();
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
let filePath = this.resolveFilePath(requestPath);
|
|
131
|
-
if(!filePath){
|
|
132
|
-
stream.respond({':status': 404});
|
|
160
|
+
stream.respond({':status': statusCode});
|
|
133
161
|
stream.end();
|
|
134
|
-
|
|
162
|
+
},
|
|
163
|
+
sendFile: (filePath, responseHeaders) => {
|
|
164
|
+
stream.on('error', (err) => {
|
|
165
|
+
requestContext.errorCallback(err);
|
|
166
|
+
});
|
|
167
|
+
stream.on('finish', () => {
|
|
168
|
+
requestContext.successCallback();
|
|
169
|
+
});
|
|
170
|
+
stream.respondWithFile(filePath, responseHeaders);
|
|
171
|
+
},
|
|
172
|
+
onSuccess: (callback) => {
|
|
173
|
+
requestContext.successCallback = callback;
|
|
174
|
+
},
|
|
175
|
+
onError: (callback) => {
|
|
176
|
+
requestContext.errorCallback = callback;
|
|
135
177
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
let responseHeaders = {':status': 200};
|
|
139
|
-
let securityKeys = Object.keys(this.securityHeaders);
|
|
140
|
-
for(let headerKey of securityKeys){
|
|
141
|
-
responseHeaders[headerKey] = this.securityHeaders[headerKey];
|
|
142
|
-
}
|
|
143
|
-
if(allowedOrigin){
|
|
144
|
-
responseHeaders['access-control-allow-origin'] = allowedOrigin;
|
|
145
|
-
}
|
|
146
|
-
if(this.varyHeader){
|
|
147
|
-
responseHeaders['vary'] = this.varyHeader;
|
|
148
|
-
}
|
|
149
|
-
if(cacheAge){
|
|
150
|
-
responseHeaders['cache-control'] = 'public, max-age='+cacheAge+', immutable';
|
|
151
|
-
}
|
|
152
|
-
if(this.mimeTypes[ext]){
|
|
153
|
-
responseHeaders['content-type'] = this.mimeTypes[ext];
|
|
154
|
-
}
|
|
155
|
-
stream.on('error', (err) => {
|
|
156
|
-
ServerErrorHandler.handleError(
|
|
157
|
-
this.onError,
|
|
158
|
-
'http2CdnServer',
|
|
159
|
-
this,
|
|
160
|
-
'stream-error',
|
|
161
|
-
err,
|
|
162
|
-
{port: this.port, path: requestPath}
|
|
163
|
-
);
|
|
164
|
-
});
|
|
165
|
-
stream.respondWithFile(filePath, responseHeaders);
|
|
166
|
-
} catch (err) {
|
|
167
|
-
ServerErrorHandler.handleError(
|
|
168
|
-
this.onError,
|
|
169
|
-
'http2CdnServer',
|
|
170
|
-
this,
|
|
171
|
-
'handle-stream-error',
|
|
172
|
-
err,
|
|
173
|
-
{port: this.port, path: headers[':path']}
|
|
174
|
-
);
|
|
175
|
-
if(!stream.destroyed){
|
|
176
|
-
stream.respond({':status': 500});
|
|
177
|
-
stream.end();
|
|
178
|
-
}
|
|
179
|
-
}
|
|
178
|
+
};
|
|
179
|
+
this.requestHandler.handleRequest(requestContext);
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
handleHttp1Request(req, res)
|
|
183
183
|
{
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if(allowedOrigin){
|
|
196
|
-
optionsHeaders['access-control-allow-origin'] = allowedOrigin;
|
|
197
|
-
}
|
|
198
|
-
optionsHeaders['access-control-allow-methods'] = this.corsMethods;
|
|
199
|
-
optionsHeaders['access-control-allow-headers'] = this.corsHeaders;
|
|
200
|
-
res.writeHead(200, optionsHeaders);
|
|
201
|
-
res.end();
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
let filePath = this.resolveFilePath(requestPath);
|
|
205
|
-
if(!filePath){
|
|
206
|
-
res.writeHead(404);
|
|
207
|
-
res.end();
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
let ext = FileHandler.extension(filePath);
|
|
211
|
-
let cacheAge = ServerFactoryUtils.getCacheConfigForPath(filePath, this.cacheConfig);
|
|
212
|
-
let responseHeaders = {};
|
|
213
|
-
let securityKeys = Object.keys(this.securityHeaders);
|
|
214
|
-
for(let headerKey of securityKeys){
|
|
215
|
-
responseHeaders[headerKey] = this.securityHeaders[headerKey];
|
|
216
|
-
}
|
|
217
|
-
if(allowedOrigin){
|
|
218
|
-
responseHeaders['access-control-allow-origin'] = allowedOrigin;
|
|
219
|
-
}
|
|
220
|
-
if(this.varyHeader){
|
|
221
|
-
responseHeaders['vary'] = this.varyHeader;
|
|
222
|
-
}
|
|
223
|
-
if(cacheAge){
|
|
224
|
-
responseHeaders['cache-control'] = 'public, max-age='+cacheAge+', immutable';
|
|
225
|
-
}
|
|
226
|
-
if(this.mimeTypes[ext]){
|
|
227
|
-
responseHeaders['content-type'] = this.mimeTypes[ext];
|
|
228
|
-
}
|
|
229
|
-
let fileStream = FileHandler.createReadStream(filePath);
|
|
230
|
-
fileStream.on('error', (err) => {
|
|
231
|
-
ServerErrorHandler.handleError(
|
|
232
|
-
this.onError,
|
|
233
|
-
'http2CdnServer',
|
|
234
|
-
this,
|
|
235
|
-
'http1-stream-error',
|
|
236
|
-
err,
|
|
237
|
-
{port: this.port, path: requestPath}
|
|
238
|
-
);
|
|
239
|
-
if(!res.headersSent){
|
|
240
|
-
res.writeHead(500);
|
|
184
|
+
let requestContext = {
|
|
185
|
+
isHttp2: false,
|
|
186
|
+
method: req.method,
|
|
187
|
+
path: req.url,
|
|
188
|
+
hostname: req.headers['x-forwarded-host'] || req.headers.host,
|
|
189
|
+
origin: req.headers['origin'] || '',
|
|
190
|
+
ip: req.headers['x-forwarded-for'] || req.socket.remoteAddress,
|
|
191
|
+
userAgent: req.headers['user-agent'],
|
|
192
|
+
sendResponse: (statusCode, responseHeaders) => {
|
|
193
|
+
if(responseHeaders){
|
|
194
|
+
res.writeHead(statusCode, responseHeaders);
|
|
241
195
|
res.end();
|
|
196
|
+
return;
|
|
242
197
|
}
|
|
243
|
-
|
|
244
|
-
res.writeHead(200, responseHeaders);
|
|
245
|
-
fileStream.pipe(res);
|
|
246
|
-
} catch (err) {
|
|
247
|
-
ServerErrorHandler.handleError(
|
|
248
|
-
this.onError,
|
|
249
|
-
'http2CdnServer',
|
|
250
|
-
this,
|
|
251
|
-
'handle-http1-request-error',
|
|
252
|
-
err,
|
|
253
|
-
{port: this.port, path: req.url}
|
|
254
|
-
);
|
|
255
|
-
if(!res.headersSent){
|
|
256
|
-
res.writeHead(500);
|
|
198
|
+
res.writeHead(statusCode);
|
|
257
199
|
res.end();
|
|
200
|
+
},
|
|
201
|
+
sendFile: (filePath, responseHeaders) => {
|
|
202
|
+
let fileStream = FileHandler.createReadStream(filePath);
|
|
203
|
+
fileStream.on('error', (err) => {
|
|
204
|
+
if(!res.headersSent){
|
|
205
|
+
res.writeHead(500);
|
|
206
|
+
res.end();
|
|
207
|
+
}
|
|
208
|
+
requestContext.errorCallback(err);
|
|
209
|
+
});
|
|
210
|
+
res.writeHead(200, responseHeaders);
|
|
211
|
+
fileStream.pipe(res);
|
|
212
|
+
res.on('finish', () => {
|
|
213
|
+
requestContext.successCallback();
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
onSuccess: (callback) => {
|
|
217
|
+
requestContext.successCallback = callback;
|
|
218
|
+
},
|
|
219
|
+
onError: (callback) => {
|
|
220
|
+
requestContext.errorCallback = callback;
|
|
258
221
|
}
|
|
259
|
-
}
|
|
222
|
+
};
|
|
223
|
+
this.requestHandler.handleRequest(requestContext);
|
|
260
224
|
}
|
|
261
225
|
|
|
262
226
|
resolveFilePath(requestPath)
|
|
@@ -282,6 +246,13 @@ class Http2CdnServer
|
|
|
282
246
|
return false;
|
|
283
247
|
}
|
|
284
248
|
this.http2Server.listen(this.port);
|
|
249
|
+
EventDispatcher.dispatch(
|
|
250
|
+
this.onEvent,
|
|
251
|
+
'cdn-server-listening',
|
|
252
|
+
'http2CdnServer',
|
|
253
|
+
this,
|
|
254
|
+
{port: this.port}
|
|
255
|
+
);
|
|
285
256
|
return true;
|
|
286
257
|
}
|
|
287
258
|
|