@reldens/server-utils 0.43.0 → 0.45.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 +90 -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');
|
|
@@ -113,10 +115,14 @@ class AppServerFactory
|
|
|
113
115
|
this.http2CdnCorsAllowAll = false;
|
|
114
116
|
this.http2CdnMimeTypes = {};
|
|
115
117
|
this.http2CdnCacheConfig = {};
|
|
118
|
+
this.http2CdnSecurityHeaders = {};
|
|
116
119
|
this.http2CdnServer = false;
|
|
117
120
|
this.reverseProxyEnabled = false;
|
|
118
121
|
this.reverseProxyRules = [];
|
|
119
122
|
this.onError = null;
|
|
123
|
+
this.onRequestSuccess = null;
|
|
124
|
+
this.onRequestError = null;
|
|
125
|
+
this.onEvent = null;
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
buildStaticHeaders(res, path)
|
|
@@ -148,6 +154,7 @@ class AppServerFactory
|
|
|
148
154
|
this.setupCors();
|
|
149
155
|
this.setupRateLimiting();
|
|
150
156
|
this.setupRequestParsing();
|
|
157
|
+
this.setupRequestLogging();
|
|
151
158
|
this.setupTrustedProxy();
|
|
152
159
|
try {
|
|
153
160
|
this.appServer = this.createServer();
|
|
@@ -161,6 +168,13 @@ class AppServerFactory
|
|
|
161
168
|
}
|
|
162
169
|
return false;
|
|
163
170
|
}
|
|
171
|
+
EventDispatcher.dispatch(
|
|
172
|
+
this.onEvent,
|
|
173
|
+
'app-server-created',
|
|
174
|
+
'appServerFactory',
|
|
175
|
+
this,
|
|
176
|
+
{port: this.port, useHttps: this.useHttps, isDevelopmentMode: this.isDevelopmentMode}
|
|
177
|
+
);
|
|
164
178
|
if(this.http2CdnEnabled){
|
|
165
179
|
if(!this.createHttp2CdnServer()){
|
|
166
180
|
this.error = {message: 'The createHttp2CdnServer() returned false.'};
|
|
@@ -190,7 +204,13 @@ class AppServerFactory
|
|
|
190
204
|
}
|
|
191
205
|
this.http2CdnServer.corsOrigins = this.http2CdnCorsOrigins;
|
|
192
206
|
this.http2CdnServer.corsAllowAll = this.http2CdnCorsAllowAll;
|
|
207
|
+
if(this.http2CdnSecurityHeaders && 0 < Object.keys(this.http2CdnSecurityHeaders).length){
|
|
208
|
+
this.http2CdnServer.securityHeaders = this.http2CdnSecurityHeaders;
|
|
209
|
+
}
|
|
193
210
|
this.http2CdnServer.onError = this.onError;
|
|
211
|
+
this.http2CdnServer.onRequestSuccess = this.onRequestSuccess;
|
|
212
|
+
this.http2CdnServer.onRequestError = this.onRequestError;
|
|
213
|
+
this.http2CdnServer.onEvent = this.onEvent;
|
|
194
214
|
if(!this.http2CdnServer.create()){
|
|
195
215
|
this.error = this.http2CdnServer.error;
|
|
196
216
|
return false;
|
|
@@ -199,6 +219,13 @@ class AppServerFactory
|
|
|
199
219
|
this.error = this.http2CdnServer.error;
|
|
200
220
|
return false;
|
|
201
221
|
}
|
|
222
|
+
EventDispatcher.dispatch(
|
|
223
|
+
this.onEvent,
|
|
224
|
+
'http2-cdn-created',
|
|
225
|
+
'appServerFactory',
|
|
226
|
+
this,
|
|
227
|
+
{port: this.http2CdnPort}
|
|
228
|
+
);
|
|
202
229
|
return true;
|
|
203
230
|
}
|
|
204
231
|
|
|
@@ -226,7 +253,8 @@ class AppServerFactory
|
|
|
226
253
|
{
|
|
227
254
|
let detectConfig = {
|
|
228
255
|
developmentDomains: this.developmentDomains,
|
|
229
|
-
domains: this.domains
|
|
256
|
+
domains: this.domains,
|
|
257
|
+
onEvent: this.onEvent
|
|
230
258
|
};
|
|
231
259
|
if(0 < this.developmentPatterns.length){
|
|
232
260
|
detectConfig['developmentPatterns'] = this.developmentPatterns;
|
|
@@ -260,7 +288,8 @@ class AppServerFactory
|
|
|
260
288
|
this.protocolEnforcer.setup(this.app, {
|
|
261
289
|
isDevelopmentMode: this.isDevelopmentMode,
|
|
262
290
|
useHttps: this.useHttps,
|
|
263
|
-
enforceProtocol: this.enforceProtocol
|
|
291
|
+
enforceProtocol: this.enforceProtocol,
|
|
292
|
+
onEvent: this.onEvent
|
|
264
293
|
});
|
|
265
294
|
}
|
|
266
295
|
|
|
@@ -270,11 +299,13 @@ class AppServerFactory
|
|
|
270
299
|
isDevelopmentMode: this.isDevelopmentMode,
|
|
271
300
|
useHelmet: this.useHelmet,
|
|
272
301
|
helmetConfig: this.helmetConfig,
|
|
273
|
-
developmentExternalDomains: this.developmentExternalDomains
|
|
302
|
+
developmentExternalDomains: this.developmentExternalDomains,
|
|
303
|
+
onEvent: this.onEvent
|
|
274
304
|
});
|
|
275
305
|
this.securityConfigurer.setupXssProtection(this.app, {
|
|
276
306
|
useXssProtection: this.useXssProtection,
|
|
277
|
-
sanitizeOptions: this.sanitizeOptions
|
|
307
|
+
sanitizeOptions: this.sanitizeOptions,
|
|
308
|
+
onEvent: this.onEvent
|
|
278
309
|
});
|
|
279
310
|
}
|
|
280
311
|
|
|
@@ -291,7 +322,8 @@ class AppServerFactory
|
|
|
291
322
|
let corsConfig = {
|
|
292
323
|
isDevelopmentMode: this.isDevelopmentMode,
|
|
293
324
|
useCors: this.useCors,
|
|
294
|
-
corsOrigin: this.corsOrigin
|
|
325
|
+
corsOrigin: this.corsOrigin,
|
|
326
|
+
onEvent: this.onEvent
|
|
295
327
|
};
|
|
296
328
|
if(0 < this.corsMethods.length){
|
|
297
329
|
corsConfig['corsMethods'] = this.corsMethods;
|
|
@@ -321,7 +353,8 @@ class AppServerFactory
|
|
|
321
353
|
maxRequests: this.maxRequests,
|
|
322
354
|
developmentMultiplier: this.developmentMultiplier,
|
|
323
355
|
applyKeyGenerator: this.applyKeyGenerator,
|
|
324
|
-
tooManyRequestsMessage: this.tooManyRequestsMessage
|
|
356
|
+
tooManyRequestsMessage: this.tooManyRequestsMessage,
|
|
357
|
+
onEvent: this.onEvent
|
|
325
358
|
});
|
|
326
359
|
}
|
|
327
360
|
|
|
@@ -345,6 +378,14 @@ class AppServerFactory
|
|
|
345
378
|
}
|
|
346
379
|
}
|
|
347
380
|
|
|
381
|
+
setupRequestLogging()
|
|
382
|
+
{
|
|
383
|
+
if(!this.onRequestSuccess && !this.onRequestError){
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
this.app.use(RequestLogger.createMiddleware(this.onRequestSuccess, this.onRequestError));
|
|
387
|
+
}
|
|
388
|
+
|
|
348
389
|
setupReverseProxy()
|
|
349
390
|
{
|
|
350
391
|
if(!this.reverseProxyEnabled || 0 === this.reverseProxyRules.length){
|
|
@@ -354,7 +395,8 @@ class AppServerFactory
|
|
|
354
395
|
reverseProxyRules: this.reverseProxyRules,
|
|
355
396
|
isDevelopmentMode: this.isDevelopmentMode,
|
|
356
397
|
useVirtualHosts: this.useVirtualHosts,
|
|
357
|
-
onError: this.onError
|
|
398
|
+
onError: this.onError,
|
|
399
|
+
onEvent: this.onEvent
|
|
358
400
|
});
|
|
359
401
|
}
|
|
360
402
|
|
|
@@ -446,7 +488,15 @@ class AppServerFactory
|
|
|
446
488
|
createServer()
|
|
447
489
|
{
|
|
448
490
|
if(!this.useHttps){
|
|
449
|
-
|
|
491
|
+
let httpServer = http.createServer(this.app);
|
|
492
|
+
EventDispatcher.dispatch(
|
|
493
|
+
this.onEvent,
|
|
494
|
+
'http-server-created',
|
|
495
|
+
'appServerFactory',
|
|
496
|
+
this,
|
|
497
|
+
{port: this.port}
|
|
498
|
+
);
|
|
499
|
+
return httpServer;
|
|
450
500
|
}
|
|
451
501
|
if(this.useVirtualHosts && 0 < this.domains.length){
|
|
452
502
|
return this.createHttpsServerWithSNI();
|
|
@@ -473,7 +523,15 @@ class AppServerFactory
|
|
|
473
523
|
credentials.ca = ca;
|
|
474
524
|
}
|
|
475
525
|
}
|
|
476
|
-
|
|
526
|
+
let httpsServer = https.createServer(credentials, this.app);
|
|
527
|
+
EventDispatcher.dispatch(
|
|
528
|
+
this.onEvent,
|
|
529
|
+
'https-server-created',
|
|
530
|
+
'appServerFactory',
|
|
531
|
+
this,
|
|
532
|
+
{port: this.port}
|
|
533
|
+
);
|
|
534
|
+
return httpsServer;
|
|
477
535
|
}
|
|
478
536
|
|
|
479
537
|
createHttpsServerWithSNI()
|
|
@@ -517,7 +575,15 @@ class AppServerFactory
|
|
|
517
575
|
let ctx = tls.createSecureContext({key, cert});
|
|
518
576
|
callback(null, ctx);
|
|
519
577
|
};
|
|
520
|
-
|
|
578
|
+
let sniServer = https.createServer(httpsOptions, this.app);
|
|
579
|
+
EventDispatcher.dispatch(
|
|
580
|
+
this.onEvent,
|
|
581
|
+
'sni-server-created',
|
|
582
|
+
'appServerFactory',
|
|
583
|
+
this,
|
|
584
|
+
{port: this.port, domainsCount: this.domains.length}
|
|
585
|
+
);
|
|
586
|
+
return sniServer;
|
|
521
587
|
}
|
|
522
588
|
|
|
523
589
|
loadDefaultCredentials()
|
|
@@ -543,6 +609,13 @@ class AppServerFactory
|
|
|
543
609
|
return false;
|
|
544
610
|
}
|
|
545
611
|
this.appServer.listen(listenPort);
|
|
612
|
+
EventDispatcher.dispatch(
|
|
613
|
+
this.onEvent,
|
|
614
|
+
'app-server-listening',
|
|
615
|
+
'appServerFactory',
|
|
616
|
+
this,
|
|
617
|
+
{port: listenPort}
|
|
618
|
+
);
|
|
546
619
|
return true;
|
|
547
620
|
}
|
|
548
621
|
|
|
@@ -604,6 +677,13 @@ class AppServerFactory
|
|
|
604
677
|
return false;
|
|
605
678
|
}
|
|
606
679
|
this.domains.push(domainConfig);
|
|
680
|
+
EventDispatcher.dispatch(
|
|
681
|
+
this.onEvent,
|
|
682
|
+
'domain-added',
|
|
683
|
+
'appServerFactory',
|
|
684
|
+
this,
|
|
685
|
+
{hostname: domainConfig.hostname}
|
|
686
|
+
);
|
|
607
687
|
return true;
|
|
608
688
|
}
|
|
609
689
|
|
|
@@ -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
|
|