@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.
@@ -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
- return http.createServer(this.app);
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
- return https.createServer(credentials, this.app);
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
- return https.createServer(httpsOptions, this.app);
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;
@@ -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
- try {
111
- let requestPath = headers[':path'];
112
- if(!requestPath){
113
- stream.respond({':status': 400});
114
- stream.end();
115
- return;
116
- }
117
- let requestOrigin = headers['origin'] || '';
118
- let allowedOrigin = ServerFactoryUtils.validateOrigin(requestOrigin, this.corsOrigins, this.corsAllowAll);
119
- if('OPTIONS' === headers[':method']){
120
- let optionsHeaders = {':status': 200};
121
- if(allowedOrigin){
122
- optionsHeaders['access-control-allow-origin'] = allowedOrigin;
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
- optionsHeaders['access-control-allow-methods'] = this.corsMethods;
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
- return;
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
- let ext = FileHandler.extension(filePath);
137
- let cacheAge = ServerFactoryUtils.getCacheConfigForPath(filePath, this.cacheConfig);
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
- try {
185
- let requestPath = req.url;
186
- if(!requestPath){
187
- res.writeHead(400);
188
- res.end();
189
- return;
190
- }
191
- let requestOrigin = req.headers['origin'] || '';
192
- let allowedOrigin = ServerFactoryUtils.validateOrigin(requestOrigin, this.corsOrigins, this.corsAllowAll);
193
- if('OPTIONS' === req.method){
194
- let optionsHeaders = {};
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