@reldens/server-utils 0.36.0 → 0.38.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/index.js CHANGED
@@ -8,10 +8,18 @@ const { FileHandler } = require('./lib/file-handler');
8
8
  const { AppServerFactory } = require('./lib/app-server-factory');
9
9
  const { UploaderFactory } = require('./lib/uploader-factory');
10
10
  const { Encryptor } = require('./lib/encryptor');
11
+ const { Http2CdnServer } = require('./lib/http2-cdn-server');
12
+ const { ServerDefaultConfigurations } = require('./lib/server-default-configurations');
13
+ const { ServerFactoryUtils } = require('./lib/server-factory-utils');
14
+ const { ServerHeaders } = require('./lib/server-headers');
11
15
 
12
16
  module.exports = {
13
17
  FileHandler,
14
18
  AppServerFactory,
15
19
  UploaderFactory,
16
- Encryptor
20
+ Encryptor,
21
+ Http2CdnServer,
22
+ ServerDefaultConfigurations,
23
+ ServerFactoryUtils,
24
+ ServerHeaders
17
25
  };
@@ -0,0 +1,132 @@
1
+ /**
2
+ *
3
+ * Reldens - ReverseProxyConfigurer
4
+ *
5
+ */
6
+
7
+ const { createProxyMiddleware } = require('http-proxy-middleware');
8
+ const { ServerHeaders } = require('../server-headers');
9
+
10
+ class ReverseProxyConfigurer
11
+ {
12
+
13
+ constructor()
14
+ {
15
+ this.isDevelopmentMode = false;
16
+ this.useVirtualHosts = false;
17
+ this.reverseProxyRules = [];
18
+ this.reverseProxyOptions = {
19
+ changeOrigin: true,
20
+ ws: true,
21
+ secure: false,
22
+ logLevel: 'warn'
23
+ };
24
+ this.serverHeaders = new ServerHeaders();
25
+ }
26
+
27
+ setup(app, config)
28
+ {
29
+ this.isDevelopmentMode = config.isDevelopmentMode || false;
30
+ this.useVirtualHosts = config.useVirtualHosts || false;
31
+ this.reverseProxyRules = config.reverseProxyRules || [];
32
+ this.reverseProxyOptions = config.reverseProxyOptions || this.reverseProxyOptions;
33
+ if(0 === this.reverseProxyRules.length){
34
+ return;
35
+ }
36
+ this.applyRules(app);
37
+ }
38
+
39
+ applyRules(app)
40
+ {
41
+ for(let rule of this.reverseProxyRules){
42
+ if(!this.validateProxyRule(rule)){
43
+ continue;
44
+ }
45
+ let proxyMiddleware = this.createProxyMiddleware(rule);
46
+ if(!proxyMiddleware){
47
+ continue;
48
+ }
49
+ let pathPrefix = rule.pathPrefix || '/';
50
+ if(this.useVirtualHosts){
51
+ app.use(pathPrefix, (req, res, next) => {
52
+ let hostname = this.extractHostname(req);
53
+ if(hostname !== rule.hostname){
54
+ return next();
55
+ }
56
+ return proxyMiddleware(req, res, next);
57
+ });
58
+ continue;
59
+ }
60
+ app.use(pathPrefix, proxyMiddleware);
61
+ }
62
+ }
63
+
64
+ extractHostname(req)
65
+ {
66
+ if(req.domain && req.domain.hostname){
67
+ return req.domain.hostname;
68
+ }
69
+ let host = req.get('host') || '';
70
+ return host.split(':')[0].toLowerCase();
71
+ }
72
+
73
+ validateProxyRule(rule)
74
+ {
75
+ if(!rule){
76
+ return false;
77
+ }
78
+ if(!rule.hostname){
79
+ return false;
80
+ }
81
+ if(!rule.target){
82
+ return false;
83
+ }
84
+ return true;
85
+ }
86
+
87
+ createProxyMiddleware(rule)
88
+ {
89
+ let options = Object.assign({}, this.reverseProxyOptions);
90
+ if('boolean' === typeof rule.changeOrigin){
91
+ options.changeOrigin = rule.changeOrigin;
92
+ }
93
+ if('boolean' === typeof rule.websocket){
94
+ options.ws = rule.websocket;
95
+ }
96
+ if('boolean' === typeof rule.secure){
97
+ options.secure = rule.secure;
98
+ }
99
+ if('string' === typeof rule.logLevel){
100
+ options.logLevel = rule.logLevel;
101
+ }
102
+ options.target = rule.target;
103
+ options.onError = this.handleProxyError.bind(this);
104
+ options.onProxyReq = (proxyReq, req) => {
105
+ if(req.headers['x-forwarded-for']){
106
+ return;
107
+ }
108
+ let clientIp = req.ip || req.connection.remoteAddress || '';
109
+ proxyReq.setHeader(this.serverHeaders.proxyForwardedFor, clientIp);
110
+ proxyReq.setHeader(this.serverHeaders.proxyForwardedProto, req.protocol);
111
+ proxyReq.setHeader(this.serverHeaders.proxyForwardedHost, req.get('host') || '');
112
+ };
113
+ return createProxyMiddleware(options);
114
+ }
115
+
116
+ handleProxyError(err, req, res)
117
+ {
118
+ if(res.headersSent){
119
+ return;
120
+ }
121
+ if('ECONNREFUSED' === err.code){
122
+ return res.status(502).send('Bad Gateway - Backend server unavailable');
123
+ }
124
+ if('ETIMEDOUT' === err.code || 'ESOCKETTIMEDOUT' === err.code){
125
+ return res.status(504).send('Gateway Timeout');
126
+ }
127
+ return res.status(500).send('Proxy Error');
128
+ }
129
+
130
+ }
131
+
132
+ module.exports.ReverseProxyConfigurer = ReverseProxyConfigurer;
@@ -16,38 +16,36 @@ class SecurityConfigurer
16
16
  this.useHelmet = true;
17
17
  this.useXssProtection = true;
18
18
  this.helmetConfig = false;
19
+ this.helmetOptions = {};
19
20
  this.sanitizeOptions = {allowedTags: [], allowedAttributes: {}};
20
21
  }
21
22
 
22
23
  setupHelmet(app, config)
23
24
  {
24
- this.isDevelopmentMode = config.isDevelopmentMode || false;
25
25
  this.useHelmet = config.useHelmet !== false;
26
- this.helmetConfig = config.helmetConfig || false;
27
26
  if(!this.useHelmet){
28
27
  return;
29
28
  }
30
- let helmetOptions = this.setModeOptions({
29
+ app.use(helmet(this.mapHelmetOptions(config)));
30
+ }
31
+
32
+ mapHelmetOptions(config)
33
+ {
34
+ this.isDevelopmentMode = config.isDevelopmentMode || false;
35
+ this.helmetConfig = config.helmetConfig || {};
36
+ this.helmetOptions = {
31
37
  crossOriginEmbedderPolicy: false,
32
38
  crossOriginOpenerPolicy: false,
33
39
  crossOriginResourcePolicy: false,
34
40
  originAgentCluster: false
35
- }, config);
36
- if(this.helmetConfig){
37
- Object.assign(helmetOptions, this.helmetConfig);
38
- }
39
- app.use(helmet(helmetOptions));
40
- }
41
-
42
- setModeOptions(helmetOptions, config)
43
- {
41
+ };
44
42
  if(this.isDevelopmentMode){
45
- helmetOptions.contentSecurityPolicy = false;
46
- helmetOptions.hsts = false;
47
- helmetOptions.noSniff = false;
48
- return helmetOptions;
43
+ this.helmetOptions.contentSecurityPolicy = false;
44
+ this.helmetOptions.hsts = false;
45
+ this.helmetOptions.noSniff = false;
46
+ return this.helmetOptions;
49
47
  }
50
- helmetOptions.contentSecurityPolicy = {
48
+ this.helmetOptions.contentSecurityPolicy = {
51
49
  directives: {
52
50
  defaultSrc: ["'self'"],
53
51
  scriptSrc: ["'self'"],
@@ -62,13 +60,50 @@ class SecurityConfigurer
62
60
  formAction: ["'self'"]
63
61
  }
64
62
  };
63
+ if(this.helmetConfig.contentSecurityPolicy){
64
+ if(this.helmetConfig.contentSecurityPolicy.overrideDirectives){
65
+ this.helmetOptions.contentSecurityPolicy.directives = this.helmetConfig.contentSecurityPolicy.directives;
66
+ }
67
+ if(
68
+ !this.helmetConfig.contentSecurityPolicy.overrideDirectives
69
+ && this.helmetConfig.contentSecurityPolicy.directives
70
+ ){
71
+ let configDirectivesKeys = Object.keys(this.helmetConfig.contentSecurityPolicy.directives);
72
+ for(let directiveKey of configDirectivesKeys){
73
+ let directiveValues = this.helmetConfig.contentSecurityPolicy.directives[directiveKey];
74
+ if(!Array.isArray(directiveValues)){
75
+ continue;
76
+ }
77
+ if(!this.helmetOptions.contentSecurityPolicy.directives[directiveKey]){
78
+ this.helmetOptions.contentSecurityPolicy.directives[directiveKey] = [];
79
+ }
80
+ for(let value of directiveValues){
81
+ this.helmetOptions.contentSecurityPolicy.directives[directiveKey].push(value);
82
+ }
83
+ }
84
+ }
85
+ let cspKeys = Object.keys(this.helmetConfig.contentSecurityPolicy);
86
+ for(let cspKey of cspKeys){
87
+ if('directives' === cspKey || 'overrideDirectives' === cspKey){
88
+ continue;
89
+ }
90
+ this.helmetOptions.contentSecurityPolicy[cspKey] = this.helmetConfig.contentSecurityPolicy[cspKey];
91
+ }
92
+ }
93
+ let helmetConfigKeys = Object.keys(this.helmetConfig);
94
+ for(let configKey of helmetConfigKeys){
95
+ if('contentSecurityPolicy' === configKey){
96
+ continue;
97
+ }
98
+ this.helmetOptions[configKey] = this.helmetConfig[configKey];
99
+ }
65
100
  if(config.developmentExternalDomains){
66
101
  this.addExternalDomainsToCsp(
67
- helmetOptions.contentSecurityPolicy.directives,
102
+ this.helmetOptions.contentSecurityPolicy.directives,
68
103
  config.developmentExternalDomains
69
104
  );
70
105
  }
71
- return helmetOptions;
106
+ return this.helmetOptions;
72
107
  }
73
108
 
74
109
  addExternalDomainsToCsp(directives, externalDomains)
@@ -79,11 +114,12 @@ class SecurityConfigurer
79
114
  if(!Array.isArray(domains)){
80
115
  continue;
81
116
  }
117
+ let camelCaseKey = directiveKey.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
82
118
  for(let domain of domains){
83
- if(directives[directiveKey]){
84
- directives[directiveKey].push(domain);
119
+ if(directives[camelCaseKey]){
120
+ directives[camelCaseKey].push(domain);
85
121
  }
86
- let elemKey = directiveKey.replace('-src', '-src-elem');
122
+ let elemKey = camelCaseKey+'Elem';
87
123
  if(directives[elemKey]){
88
124
  directives[elemKey].push(domain);
89
125
  }
@@ -5,10 +5,15 @@
5
5
  */
6
6
 
7
7
  const { FileHandler } = require('./file-handler');
8
+ const { Http2CdnServer } = require('./http2-cdn-server');
9
+ const { ServerDefaultConfigurations } = require('./server-default-configurations');
10
+ const { ServerFactoryUtils } = require('./server-factory-utils');
11
+ const { ServerHeaders } = require('./server-headers');
8
12
  const { DevelopmentModeDetector } = require('./app-server-factory/development-mode-detector');
9
13
  const { ProtocolEnforcer } = require('./app-server-factory/protocol-enforcer');
10
14
  const { SecurityConfigurer } = require('./app-server-factory/security-configurer');
11
15
  const { CorsConfigurer } = require('./app-server-factory/cors-configurer');
16
+ const { ReverseProxyConfigurer } = require('./app-server-factory/reverse-proxy-configurer');
12
17
  const { RateLimitConfigurer } = require('./app-server-factory/rate-limit-configurer');
13
18
  const http = require('http');
14
19
  const https = require('https');
@@ -61,21 +66,8 @@ class AppServerFactory
61
66
  this.defaultDomain = '';
62
67
  this.maxRequestSize = '10mb';
63
68
  this.sanitizeOptions = {allowedTags: [], allowedAttributes: {}};
64
- this.cacheConfig = {
65
- '.css': 31536000,
66
- '.js': 31536000,
67
- '.woff': 31536000,
68
- '.woff2': 31536000,
69
- '.ttf': 31536000,
70
- '.eot': 31536000,
71
- '.jpg': 2592000,
72
- '.jpeg': 2592000,
73
- '.png': 2592000,
74
- '.gif': 2592000,
75
- '.webp': 2592000,
76
- '.svg': 2592000,
77
- '.ico': 2592000
78
- };
69
+ this.cacheConfig = ServerDefaultConfigurations.cacheConfig;
70
+ this.serverHeaders = new ServerHeaders();
79
71
  this.staticOptions = {
80
72
  maxAge: '1d',
81
73
  immutable: false,
@@ -98,6 +90,7 @@ class AppServerFactory
98
90
  this.securityConfigurer = new SecurityConfigurer();
99
91
  this.corsConfigurer = new CorsConfigurer();
100
92
  this.rateLimitConfigurer = new RateLimitConfigurer();
93
+ this.reverseProxyConfigurer = new ReverseProxyConfigurer();
101
94
  this.useCompression = true;
102
95
  this.compressionOptions = {
103
96
  level: 6,
@@ -109,28 +102,32 @@ class AppServerFactory
109
102
  return compression.filter(req, res);
110
103
  }
111
104
  };
105
+ this.http2CdnEnabled = false;
106
+ this.http2CdnPort = 8443;
107
+ this.http2CdnKeyPath = '';
108
+ this.http2CdnCertPath = '';
109
+ this.http2CdnHttpsChain = '';
110
+ this.http2CdnStaticPaths = [];
111
+ this.http2CdnCorsOrigins = [];
112
+ this.http2CdnCorsAllowAll = false;
113
+ this.http2CdnMimeTypes = {};
114
+ this.http2CdnCacheConfig = {};
115
+ this.http2CdnServer = false;
116
+ this.reverseProxyEnabled = false;
117
+ this.reverseProxyRules = [];
112
118
  }
113
119
 
114
120
  buildStaticHeaders(res, path)
115
121
  {
116
- res.set('X-Content-Type-Options', 'nosniff');
117
- res.set('X-Frame-Options', 'DENY');
118
- res.set('Vary', 'Accept-Encoding');
119
- let cacheMaxAge = this.getCacheConfigForPath(path);
120
- if(cacheMaxAge){
121
- res.set('Cache-Control', 'public, max-age='+cacheMaxAge+', immutable');
122
+ let securityHeaderKeys = Object.keys(this.serverHeaders.expressSecurityHeaders);
123
+ for(let headerKey of securityHeaderKeys){
124
+ res.set(headerKey, this.serverHeaders.expressSecurityHeaders[headerKey]);
122
125
  }
123
- }
124
-
125
- getCacheConfigForPath(path)
126
- {
127
- let cacheKeys = Object.keys(this.cacheConfig);
128
- for(let ext of cacheKeys){
129
- if(path.endsWith(ext)){
130
- return this.cacheConfig[ext];
131
- }
126
+ res.set('Vary', this.serverHeaders.expressVaryHeader);
127
+ let cacheMaxAge = ServerFactoryUtils.getCacheConfigForPath(path, this.cacheConfig);
128
+ if(cacheMaxAge){
129
+ res.set('Cache-Control', this.serverHeaders.buildCacheControlHeader(cacheMaxAge));
132
130
  }
133
- return false;
134
131
  }
135
132
 
136
133
  createAppServer(appServerConfig)
@@ -148,6 +145,7 @@ class AppServerFactory
148
145
  this.setupCors();
149
146
  this.setupRateLimiting();
150
147
  this.setupRequestParsing();
148
+ this.setupReverseProxy();
151
149
  this.setupTrustedProxy();
152
150
  try {
153
151
  this.appServer = this.createServer();
@@ -157,14 +155,48 @@ class AppServerFactory
157
155
  }
158
156
  if(!this.appServer){
159
157
  if(!this.error.message){
160
- this.error = {message: 'The createServer() returned false - check certificate paths and permissions'};
158
+ this.error = {message: 'The createServer() returned false - check certificate paths and permissions.'};
161
159
  }
162
160
  return false;
163
161
  }
162
+ if(this.http2CdnEnabled){
163
+ if(!this.createHttp2CdnServer()){
164
+ this.error = {message: 'The createHttp2CdnServer() returned false.'};
165
+ return false;
166
+ }
167
+ }
164
168
  if(this.autoListen){
165
169
  this.listen();
166
170
  }
167
- return {app: this.app, appServer: this.appServer};
171
+ return {app: this.app, appServer: this.appServer, http2CdnServer: this.http2CdnServer};
172
+ }
173
+
174
+ createHttp2CdnServer()
175
+ {
176
+ this.http2CdnServer = new Http2CdnServer();
177
+ this.http2CdnServer.enabled = this.http2CdnEnabled;
178
+ this.http2CdnServer.port = this.http2CdnPort;
179
+ this.http2CdnServer.keyPath = this.http2CdnKeyPath || this.keyPath;
180
+ this.http2CdnServer.certPath = this.http2CdnCertPath || this.certPath;
181
+ this.http2CdnServer.httpsChain = this.http2CdnHttpsChain || this.httpsChain;
182
+ this.http2CdnServer.staticPaths = this.http2CdnStaticPaths;
183
+ this.http2CdnServer.cacheConfig = this.http2CdnCacheConfig && 0 < Object.keys(this.http2CdnCacheConfig).length
184
+ ? this.http2CdnCacheConfig
185
+ : this.cacheConfig;
186
+ if(this.http2CdnMimeTypes && 0 < Object.keys(this.http2CdnMimeTypes).length){
187
+ this.http2CdnServer.mimeTypes = this.http2CdnMimeTypes;
188
+ }
189
+ this.http2CdnServer.corsOrigins = this.http2CdnCorsOrigins;
190
+ this.http2CdnServer.corsAllowAll = this.http2CdnCorsAllowAll;
191
+ if(!this.http2CdnServer.create()){
192
+ this.error = this.http2CdnServer.error;
193
+ return false;
194
+ }
195
+ if(!this.http2CdnServer.listen()){
196
+ this.error = this.http2CdnServer.error;
197
+ return false;
198
+ }
199
+ return true;
168
200
  }
169
201
 
170
202
  extractDomainFromHttpUrl(url)
@@ -209,12 +241,14 @@ class AppServerFactory
209
241
  }
210
242
  this.staticOptions.immutable = false;
211
243
  this.staticOptions.setHeaders = (res, path) => {
212
- res.set('X-Content-Type-Options', 'nosniff');
213
- res.set('X-Frame-Options', 'SAMEORIGIN');
214
- res.set('Cache-Control', 'no-cache, no-store, must-revalidate');
215
- res.set('Pragma', 'no-cache');
216
- res.set('Expires', '0');
217
- res.set('Vary', 'Accept-Encoding');
244
+ let securityHeaderKeys = Object.keys(this.serverHeaders.expressSecurityHeaders);
245
+ for(let headerKey of securityHeaderKeys){
246
+ res.set(headerKey, this.serverHeaders.expressSecurityHeaders[headerKey]);
247
+ }
248
+ res.set('Cache-Control', this.serverHeaders.expressCacheControlNoCache);
249
+ res.set('Pragma', this.serverHeaders.expressPragma);
250
+ res.set('Expires', this.serverHeaders.expressExpires);
251
+ res.set('Vary', this.serverHeaders.expressVaryHeader);
218
252
  };
219
253
  }
220
254
 
@@ -308,6 +342,18 @@ class AppServerFactory
308
342
  }
309
343
  }
310
344
 
345
+ setupReverseProxy()
346
+ {
347
+ if(!this.reverseProxyEnabled || 0 === this.reverseProxyRules.length){
348
+ return;
349
+ }
350
+ this.reverseProxyConfigurer.setup(this.app, {
351
+ reverseProxyRules: this.reverseProxyRules,
352
+ isDevelopmentMode: this.isDevelopmentMode,
353
+ useVirtualHosts: this.useVirtualHosts
354
+ });
355
+ }
356
+
311
357
  setupTrustedProxy()
312
358
  {
313
359
  if('' !== this.trustedProxy){
@@ -545,6 +591,9 @@ class AppServerFactory
545
591
 
546
592
  async close()
547
593
  {
594
+ if(this.http2CdnServer){
595
+ await this.http2CdnServer.close();
596
+ }
548
597
  if(!this.appServer){
549
598
  return true;
550
599
  }
@@ -0,0 +1,161 @@
1
+ /**
2
+ *
3
+ * Reldens - Http2CdnServer
4
+ *
5
+ */
6
+
7
+ const http2 = require('http2');
8
+ const path = require('path');
9
+ const { FileHandler } = require('./file-handler');
10
+ const { ServerDefaultConfigurations } = require('./server-default-configurations');
11
+ const { ServerFactoryUtils } = require('./server-factory-utils');
12
+ const { ServerHeaders } = require('./server-headers');
13
+
14
+ class Http2CdnServer
15
+ {
16
+
17
+ constructor()
18
+ {
19
+ this.enabled = false;
20
+ this.port = 8443;
21
+ this.keyPath = '';
22
+ this.certPath = '';
23
+ this.httpsChain = '';
24
+ this.staticPaths = [];
25
+ this.cacheConfig = ServerDefaultConfigurations.cacheConfig;
26
+ this.allowHTTP1 = true;
27
+ this.http2Server = false;
28
+ this.error = {};
29
+ this.mimeTypes = ServerDefaultConfigurations.mimeTypes;
30
+ this.corsOrigins = [];
31
+ this.corsAllowAll = false;
32
+ this.serverHeaders = new ServerHeaders();
33
+ this.corsMethods = this.serverHeaders.http2CorsMethods;
34
+ this.corsHeaders = this.serverHeaders.http2CorsHeaders;
35
+ this.securityHeaders = this.serverHeaders.http2SecurityHeaders;
36
+ this.varyHeader = this.serverHeaders.http2VaryHeader;
37
+ }
38
+
39
+ create()
40
+ {
41
+ if(!this.keyPath || !this.certPath){
42
+ this.error = {message: 'Missing SSL certificates'};
43
+ return false;
44
+ }
45
+ let key = FileHandler.readFile(this.keyPath);
46
+ if(!key){
47
+ this.error = {message: 'Could not read key from: '+this.keyPath};
48
+ return false;
49
+ }
50
+ let cert = FileHandler.readFile(this.certPath);
51
+ if(!cert){
52
+ this.error = {message: 'Could not read cert from: '+this.certPath};
53
+ return false;
54
+ }
55
+ let options = {key, cert, allowHTTP1: this.allowHTTP1};
56
+ if(this.httpsChain){
57
+ let ca = FileHandler.readFile(this.httpsChain);
58
+ if(ca){
59
+ options.ca = ca;
60
+ }
61
+ }
62
+ this.http2Server = http2.createSecureServer(options);
63
+ this.setupStreamHandler();
64
+ return true;
65
+ }
66
+
67
+ setupStreamHandler()
68
+ {
69
+ this.http2Server.on('stream', (stream, headers) => {
70
+ this.handleStream(stream, headers);
71
+ });
72
+ }
73
+
74
+ handleStream(stream, headers)
75
+ {
76
+ let requestPath = headers[':path'];
77
+ if(!requestPath){
78
+ stream.respond({':status': 400});
79
+ stream.end();
80
+ return;
81
+ }
82
+ let requestOrigin = headers['origin'] || '';
83
+ let allowedOrigin = ServerFactoryUtils.validateOrigin(requestOrigin, this.corsOrigins, this.corsAllowAll);
84
+ if('OPTIONS' === headers[':method']){
85
+ let optionsHeaders = {':status': 200};
86
+ if(allowedOrigin){
87
+ optionsHeaders['access-control-allow-origin'] = allowedOrigin;
88
+ }
89
+ optionsHeaders['access-control-allow-methods'] = this.corsMethods;
90
+ optionsHeaders['access-control-allow-headers'] = this.corsHeaders;
91
+ stream.respond(optionsHeaders);
92
+ stream.end();
93
+ return;
94
+ }
95
+ let filePath = this.resolveFilePath(requestPath);
96
+ if(!filePath){
97
+ stream.respond({':status': 404});
98
+ stream.end();
99
+ return;
100
+ }
101
+ let ext = path.extname(filePath);
102
+ let cacheAge = ServerFactoryUtils.getCacheConfigForPath(filePath, this.cacheConfig);
103
+ let responseHeaders = {':status': 200};
104
+ let securityKeys = Object.keys(this.securityHeaders);
105
+ for(let headerKey of securityKeys){
106
+ responseHeaders[headerKey] = this.securityHeaders[headerKey];
107
+ }
108
+ if(allowedOrigin){
109
+ responseHeaders['access-control-allow-origin'] = allowedOrigin;
110
+ }
111
+ if(this.varyHeader){
112
+ responseHeaders['vary'] = this.varyHeader;
113
+ }
114
+ if(cacheAge){
115
+ responseHeaders['cache-control'] = 'public, max-age='+cacheAge+', immutable';
116
+ }
117
+ if(this.mimeTypes[ext]){
118
+ responseHeaders['content-type'] = this.mimeTypes[ext];
119
+ }
120
+ stream.respondWithFile(filePath, responseHeaders);
121
+ }
122
+
123
+ resolveFilePath(requestPath)
124
+ {
125
+ let cleanPath = ServerFactoryUtils.stripQueryString(requestPath);
126
+ for(let staticPath of this.staticPaths){
127
+ let fullPath = path.join(staticPath, cleanPath);
128
+ if(!FileHandler.exists(fullPath)){
129
+ continue;
130
+ }
131
+ if(!FileHandler.isFile(fullPath)){
132
+ continue;
133
+ }
134
+ return fullPath;
135
+ }
136
+ return false;
137
+ }
138
+
139
+ listen()
140
+ {
141
+ if(!this.http2Server){
142
+ this.error = {message: 'HTTP2 server not created'};
143
+ return false;
144
+ }
145
+ this.http2Server.listen(this.port);
146
+ return true;
147
+ }
148
+
149
+ async close()
150
+ {
151
+ if(!this.http2Server){
152
+ return true;
153
+ }
154
+ return new Promise((resolve) => {
155
+ this.http2Server.close(() => resolve(true));
156
+ });
157
+ }
158
+
159
+ }
160
+
161
+ module.exports.Http2CdnServer = Http2CdnServer;