@reldens/server-utils 0.41.0 → 0.43.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.md CHANGED
@@ -67,6 +67,8 @@ npm test
67
67
  - `appendToFile(filePath, content)` - Append to file
68
68
  - `prependToFile(filePath, content)` - Prepend to file
69
69
  - `replaceInFile(filePath, searchValue, replaceValue)` - Replace in file
70
+ - `createReadStream(filePath, options)` - Create readable stream for file
71
+ - `getFileModificationTime(filePath)` - Get file last modification time
70
72
  - All methods include built-in error handling, no need for try/catch
71
73
  - DO NOT use FileHandler.exists to validate other FileHandler methods
72
74
  - DO NOT enclose FileHandler methods in try/catch blocks
@@ -79,6 +81,7 @@ npm test
79
81
  - HTTP/2 CDN server integration
80
82
  - Reverse proxy with WebSocket support
81
83
  - Development mode detection and configuration
84
+ - Custom error handling via onError callback
82
85
  - Security configurers (modular):
83
86
  - `DevelopmentModeDetector` - Auto-detect development environment
84
87
  - `ProtocolEnforcer` - HTTP/HTTPS protocol enforcement
@@ -86,6 +89,8 @@ npm test
86
89
  - `CorsConfigurer` - CORS with dynamic origin validation
87
90
  - `RateLimitConfigurer` - Global and endpoint-specific rate limiting
88
91
  - `ReverseProxyConfigurer` - Domain-based reverse proxy
92
+ - Configuration properties:
93
+ - `onError` - Custom error handler callback for server errors
89
94
  - Methods:
90
95
  - `createAppServer(config)` - Create and configure server
91
96
  - `addDomain(domainConfig)` - Add virtual host domain
@@ -146,15 +151,30 @@ npm test
146
151
  - HTTP/1.1 fallback support
147
152
  - Security headers (X-Content-Type-Options, X-Frame-Options, Vary)
148
153
  - Query string stripping for cache optimization
154
+ - Comprehensive error handling (server, TLS, session, stream errors)
155
+ - Custom error handler callback support
156
+ - Configuration properties:
157
+ - `onError` - Custom error handler callback for server errors
149
158
  - Methods:
150
159
  - `create()` - Create HTTP/2 secure server
151
160
  - `listen()` - Start listening on configured port
152
161
  - `close()` - Gracefully close server
153
162
  - `handleStream(stream, headers)` - Handle HTTP/2 stream
163
+ - `handleHttp1Request(req, res)` - Handle HTTP/1.1 fallback requests
154
164
  - `resolveFilePath(requestPath)` - Resolve file path from request
165
+ - `setupEventHandlers()` - Configure server error event handlers
155
166
 
156
167
  ### Utility Classes
157
168
 
169
+ **ServerErrorHandler** (`lib/server-error-handler.js`):
170
+ - Centralized error handling for all server components
171
+ - Static class with standardized error handling interface
172
+ - Methods:
173
+ - `handleError(onErrorCallback, instanceName, instance, key, error, context)` - Handle and delegate errors
174
+ - Used by Http2CdnServer, AppServerFactory, and ReverseProxyConfigurer
175
+ - Supports custom error callbacks for application-specific error processing
176
+ - Provides structured error context with instance details and metadata
177
+
158
178
  **ServerDefaultConfigurations** (`lib/server-default-configurations.js`):
159
179
  - Static class providing default configurations
160
180
  - `mimeTypes` - Comprehensive MIME type mappings for common file extensions
@@ -220,10 +240,17 @@ npm test
220
240
  - SSL termination
221
241
  - Header preservation (X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host)
222
242
  - Virtual host integration
223
- - Graceful error handling (502 Bad Gateway, 504 Gateway Timeout)
224
- - `setup(app, config)` - Setup reverse proxy
225
- - `createProxyMiddleware(rule)` - Create proxy middleware for rule
226
- - `handleProxyError(err, req, res)` - Handle proxy errors
243
+ - Comprehensive error handling with proper HTTP status codes:
244
+ - 502 Bad Gateway for ECONNREFUSED errors
245
+ - 504 Gateway Timeout for ETIMEDOUT/ESOCKETTIMEDOUT errors
246
+ - 500 Internal Server Error for other proxy errors
247
+ - Custom error callback support via ServerErrorHandler
248
+ - Methods:
249
+ - `setup(app, config)` - Setup reverse proxy
250
+ - `createProxyMiddleware(rule)` - Create proxy middleware for rule
251
+ - `handleProxyError(err, req, res)` - Handle proxy errors with status codes
252
+ - `validateProxyRule(rule)` - Validate proxy rule configuration
253
+ - `extractHostname(req)` - Extract hostname from request
227
254
 
228
255
  ## Important Notes
229
256
 
package/README.md CHANGED
@@ -25,6 +25,7 @@ A Node.js server toolkit providing secure application server creation, HTTP/2 CD
25
25
  - Static file serving with security headers
26
26
  - Compression middleware with smart filtering
27
27
  - Input validation utilities
28
+ - Custom error handling with callback support
28
29
 
29
30
  ### Http2CdnServer
30
31
  - Dedicated HTTP/2 server for static asset delivery
@@ -37,6 +38,8 @@ A Node.js server toolkit providing secure application server creation, HTTP/2 CD
37
38
  - Standalone or integrated with AppServerFactory
38
39
  - Multiple static path support
39
40
  - Separate SSL certificate support from main server
41
+ - Comprehensive error handling (server, TLS, session, stream errors)
42
+ - Custom error handler callback support
40
43
 
41
44
  #### Modular Security Components
42
45
  The AppServerFactory now uses specialized security configurers:
@@ -47,6 +50,7 @@ The AppServerFactory now uses specialized security configurers:
47
50
  - **RateLimitConfigurer** - Global and endpoint-specific rate limiting
48
51
  - **ReverseProxyConfigurer** - Domain-based reverse proxy with WebSocket support
49
52
  - **SecurityConfigurer** - Helmet integration with CSP management and XSS protection
53
+ - **ServerErrorHandler** - Centralized error handling with custom callback support
50
54
 
51
55
  ### FileHandler
52
56
  - Secure file system operations with path validation
@@ -63,6 +67,8 @@ The AppServerFactory now uses specialized security configurers:
63
67
  - Directory walking with callback processing
64
68
  - File comparison and relative path calculations
65
69
  - Comprehensive error handling with detailed context
70
+ - Read stream creation for efficient file streaming
71
+ - File modification time retrieval
66
72
 
67
73
  ### Encryptor
68
74
  - Password hashing using PBKDF2 with configurable iterations
@@ -174,6 +180,34 @@ if(cdnServer.create()){
174
180
  }
175
181
  ```
176
182
 
183
+ ### Custom Error Handling
184
+
185
+ Configure custom error handlers for server errors:
186
+
187
+ ```javascript
188
+ let appServerFactory = new AppServerFactory();
189
+
190
+ appServerFactory.onError = (errorData) => {
191
+ console.error('Server error:', errorData.key);
192
+ console.error('Error details:', errorData.error);
193
+ console.error('Context:', errorData);
194
+ };
195
+
196
+ let serverResult = appServerFactory.createAppServer({
197
+ port: 443,
198
+ useHttps: true,
199
+ http2CdnEnabled: true,
200
+ autoListen: true
201
+ });
202
+ ```
203
+
204
+ The `onError` callback receives structured error data:
205
+ - `instanceName` - Name of the component (e.g., 'http2CdnServer', 'appServerFactory')
206
+ - `instance` - Reference to the component instance
207
+ - `key` - Error type identifier (e.g., 'server-error', 'tls-client-error', 'proxy-error')
208
+ - `error` - The actual error object
209
+ - `...context` - Additional context data (port, path, hostname, etc.)
210
+
177
211
  ### HTTPS Server with Optimized Caching
178
212
 
179
213
  ```javascript
@@ -225,6 +259,18 @@ if(FileHandler.createFolder('/path/to/new/folder')){
225
259
  // Generate a secure filename
226
260
  let secureFilename = FileHandler.generateSecureFilename('user-upload.jpg');
227
261
  console.log('Secure filename:', secureFilename);
262
+
263
+ // Create a read stream for efficient file handling
264
+ let stream = FileHandler.createReadStream('/path/to/large-file.txt');
265
+ if(stream){
266
+ stream.pipe(response);
267
+ }
268
+
269
+ // Get file modification time
270
+ let modTime = FileHandler.getFileModificationTime('/path/to/file.txt');
271
+ if(modTime){
272
+ console.log('Last modified:', modTime);
273
+ }
228
274
  ```
229
275
 
230
276
  ### Password Encryption
@@ -515,7 +561,10 @@ let serverResult = appServerFactory.createAppServer({
515
561
  - Header preservation (X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host)
516
562
  - Virtual host integration
517
563
  - Path-based routing
518
- - Graceful error handling
564
+ - Comprehensive error handling with proper HTTP status codes:
565
+ - 502 Bad Gateway for connection refused errors
566
+ - 504 Gateway Timeout for timeout errors
567
+ - 500 Internal Server Error for other proxy errors
519
568
 
520
569
  #### Rule Properties
521
570
 
@@ -569,6 +618,15 @@ reverseProxyRules: [
569
618
  - `reverseProxyEnabled` - Enable reverse proxy (default: false)
570
619
  - `reverseProxyRules` - Array of proxy rules (default: [])
571
620
 
621
+ ### AppServerFactory Error Handling
622
+
623
+ - `onError` - Custom error handler callback function receiving error data:
624
+ - `instanceName` - Component name (e.g., 'appServerFactory', 'http2CdnServer')
625
+ - `instance` - Component instance reference
626
+ - `key` - Error type identifier (e.g., 'server-error', 'virtual-host-error')
627
+ - `error` - The error object
628
+ - Additional context data (port, hostname, path, etc.)
629
+
572
630
  ### Http2CdnServer Methods
573
631
 
574
632
  - `create()` - Creates HTTP/2 secure server
@@ -591,6 +649,7 @@ reverseProxyRules: [
591
649
  - `securityHeaders` - Custom security headers
592
650
  - `varyHeader` - Vary header value (default: 'Accept-Encoding, Origin')
593
651
  - `mimeTypes` - MIME type mappings
652
+ - `onError` - Custom error handler callback
594
653
 
595
654
  ### FileHandler Methods
596
655
 
@@ -620,6 +679,8 @@ reverseProxyRules: [
620
679
  - `walkDirectory(path, callback)` - Recursively processes a directory tree
621
680
  - `getDirectorySize(path)` - Calculates total directory size
622
681
  - `emptyDirectory(path)` - Removes all contents from directory
682
+ - `createReadStream(path, options)` - Creates readable stream for file
683
+ - `getFileModificationTime(path)` - Gets file last modification time
623
684
 
624
685
  ### Encryptor Methods
625
686
 
@@ -643,6 +704,16 @@ reverseProxyRules: [
643
704
  - `validateFileContents(file, allowedType)` - Validates file content after upload
644
705
  - `convertToRegex(key)` - Converts MIME type patterns to regex
645
706
 
707
+ ### ServerErrorHandler Methods
708
+
709
+ - `handleError(onErrorCallback, instanceName, instance, key, error, context)` - Static method for centralized error handling
710
+ - `onErrorCallback` - Custom error handler function (optional)
711
+ - `instanceName` - Name of the component ('http2CdnServer', 'appServerFactory', etc.)
712
+ - `instance` - Reference to the component instance
713
+ - `key` - Error type identifier
714
+ - `error` - The error object
715
+ - `context` - Additional context object (port, hostname, path, etc.)
716
+
646
717
  ## Security Features
647
718
 
648
719
  ### Path Traversal Protection
@@ -668,7 +739,15 @@ Industry-standard encryption using PBKDF2 for passwords, AES-256-GCM for data en
668
739
 
669
740
  ## Error Handling
670
741
 
671
- All methods include comprehensive error handling with detailed error objects containing context information. Errors are logged appropriately and never expose sensitive system information.
742
+ All server components include comprehensive error handling with centralized error management through the ServerErrorHandler class. Custom error handlers can be configured via the `onError` callback to process errors according to application requirements. Errors are logged appropriately and never expose sensitive system information.
743
+
744
+ Error handling features:
745
+ - Centralized error management with ServerErrorHandler
746
+ - Custom error callback support across all components
747
+ - Structured error context with instance details and metadata
748
+ - Graceful HTTP status codes for proxy errors (502, 504, 500)
749
+ - TLS and session error handling for HTTP/2 servers
750
+ - Virtual host and SNI certificate error handling
672
751
 
673
752
  ---
674
753
 
@@ -6,6 +6,7 @@
6
6
 
7
7
  const { createProxyMiddleware } = require('http-proxy-middleware');
8
8
  const { ServerHeaders } = require('../server-headers');
9
+ const { ServerErrorHandler } = require('../server-error-handler');
9
10
 
10
11
  class ReverseProxyConfigurer
11
12
  {
@@ -22,6 +23,7 @@ class ReverseProxyConfigurer
22
23
  logLevel: 'warn'
23
24
  };
24
25
  this.serverHeaders = new ServerHeaders();
26
+ this.onError = null;
25
27
  }
26
28
 
27
29
  setup(app, config)
@@ -30,6 +32,7 @@ class ReverseProxyConfigurer
30
32
  this.useVirtualHosts = config.useVirtualHosts || false;
31
33
  this.reverseProxyRules = config.reverseProxyRules || [];
32
34
  this.reverseProxyOptions = config.reverseProxyOptions || this.reverseProxyOptions;
35
+ this.onError = config.onError || null;
33
36
  if(0 === this.reverseProxyRules.length){
34
37
  return;
35
38
  }
@@ -100,7 +103,9 @@ class ReverseProxyConfigurer
100
103
  options.logLevel = rule.logLevel;
101
104
  }
102
105
  options.target = rule.target;
103
- options.onError = this.handleProxyError.bind(this);
106
+ options.onError = (err, req, res) => {
107
+ this.handleProxyError(err, req, res);
108
+ };
104
109
  options.onProxyReq = (proxyReq, req) => {
105
110
  if(req.headers['x-forwarded-for']){
106
111
  return;
@@ -115,6 +120,16 @@ class ReverseProxyConfigurer
115
120
 
116
121
  handleProxyError(err, req, res)
117
122
  {
123
+ let hostname = req.get('host') || 'unknown';
124
+ let requestPath = req.path || req.url || 'unknown';
125
+ ServerErrorHandler.handleError(
126
+ this.onError,
127
+ 'reverseProxyConfigurer',
128
+ this,
129
+ 'proxy-error',
130
+ err,
131
+ {request: req, response: res, hostname: hostname, path: requestPath}
132
+ );
118
133
  if(res.headersSent){
119
134
  return;
120
135
  }
@@ -9,6 +9,7 @@ const { Http2CdnServer } = require('./http2-cdn-server');
9
9
  const { ServerDefaultConfigurations } = require('./server-default-configurations');
10
10
  const { ServerFactoryUtils } = require('./server-factory-utils');
11
11
  const { ServerHeaders } = require('./server-headers');
12
+ const { ServerErrorHandler } = require('./server-error-handler');
12
13
  const { DevelopmentModeDetector } = require('./app-server-factory/development-mode-detector');
13
14
  const { ProtocolEnforcer } = require('./app-server-factory/protocol-enforcer');
14
15
  const { SecurityConfigurer } = require('./app-server-factory/security-configurer');
@@ -115,6 +116,7 @@ class AppServerFactory
115
116
  this.http2CdnServer = false;
116
117
  this.reverseProxyEnabled = false;
117
118
  this.reverseProxyRules = [];
119
+ this.onError = null;
118
120
  }
119
121
 
120
122
  buildStaticHeaders(res, path)
@@ -188,6 +190,7 @@ class AppServerFactory
188
190
  }
189
191
  this.http2CdnServer.corsOrigins = this.http2CdnCorsOrigins;
190
192
  this.http2CdnServer.corsAllowAll = this.http2CdnCorsAllowAll;
193
+ this.http2CdnServer.onError = this.onError;
191
194
  if(!this.http2CdnServer.create()){
192
195
  this.error = this.http2CdnServer.error;
193
196
  return false;
@@ -350,7 +353,8 @@ class AppServerFactory
350
353
  this.reverseProxyConfigurer.setup(this.app, {
351
354
  reverseProxyRules: this.reverseProxyRules,
352
355
  isDevelopmentMode: this.isDevelopmentMode,
353
- useVirtualHosts: this.useVirtualHosts
356
+ useVirtualHosts: this.useVirtualHosts,
357
+ onError: this.onError
354
358
  });
355
359
  }
356
360
 
@@ -388,6 +392,14 @@ class AppServerFactory
388
392
  return next();
389
393
  }
390
394
  this.error = {message: 'No hostname provided and no default domain configured'};
395
+ ServerErrorHandler.handleError(
396
+ this.onError,
397
+ 'appServerFactory',
398
+ this,
399
+ 'virtual-host-no-hostname',
400
+ this.error,
401
+ {request: req, response: res}
402
+ );
391
403
  return res.status(400).send('Bad Request');
392
404
  }
393
405
  let domain = this.findDomainConfig(hostname);
@@ -397,6 +409,14 @@ class AppServerFactory
397
409
  return next();
398
410
  }
399
411
  this.error = {message: 'Unknown domain: '+hostname};
412
+ ServerErrorHandler.handleError(
413
+ this.onError,
414
+ 'appServerFactory',
415
+ this,
416
+ 'virtual-host-unknown-domain',
417
+ this.error,
418
+ {hostname: hostname, request: req, response: res}
419
+ );
400
420
  return res.status(404).send('Domain not found');
401
421
  }
402
422
  req.domain = domain;
@@ -471,11 +491,27 @@ class AppServerFactory
471
491
  let key = FileHandler.readFile(domain.keyPath, 'Domain Key');
472
492
  if(!key){
473
493
  this.error = {message: 'Could not read domain SSL key: '+domain.keyPath};
494
+ ServerErrorHandler.handleError(
495
+ this.onError,
496
+ 'appServerFactory',
497
+ this,
498
+ 'sni-key-read-failure',
499
+ this.error,
500
+ {hostname: hostname, domain: domain, keyPath: domain.keyPath}
501
+ );
474
502
  return callback(null, null);
475
503
  }
476
504
  let cert = FileHandler.readFile(domain.certPath, 'Domain Cert');
477
505
  if(!cert){
478
506
  this.error = {message: 'Could not read domain SSL certificate: '+domain.certPath};
507
+ ServerErrorHandler.handleError(
508
+ this.onError,
509
+ 'appServerFactory',
510
+ this,
511
+ 'sni-cert-read-failure',
512
+ this.error,
513
+ {hostname: hostname, domain: domain, certPath: domain.certPath}
514
+ );
479
515
  return callback(null, null);
480
516
  }
481
517
  let ctx = tls.createSecureContext({key, cert});
@@ -765,6 +765,19 @@ class FileHandler
765
765
  return this.writeFile(filePath, content.replace(searchValue, replaceValue));
766
766
  }
767
767
 
768
+ createReadStream(filePath, options)
769
+ {
770
+ if(!this.isValidPath(filePath)){
771
+ this.error = {message: 'Invalid file path.', filePath};
772
+ return false;
773
+ }
774
+ if(!this.isFile(filePath)){
775
+ this.error = {message: 'File does not exist or is not a file.', filePath};
776
+ return false;
777
+ }
778
+ return fs.createReadStream(filePath, options);
779
+ }
780
+
768
781
  }
769
782
 
770
783
  module.exports.FileHandler = new FileHandler();
@@ -5,11 +5,11 @@
5
5
  */
6
6
 
7
7
  const http2 = require('http2');
8
- const path = require('path');
9
8
  const { FileHandler } = require('./file-handler');
10
9
  const { ServerDefaultConfigurations } = require('./server-default-configurations');
11
10
  const { ServerFactoryUtils } = require('./server-factory-utils');
12
11
  const { ServerHeaders } = require('./server-headers');
12
+ const { ServerErrorHandler } = require('./server-error-handler');
13
13
 
14
14
  class Http2CdnServer
15
15
  {
@@ -34,6 +34,7 @@ class Http2CdnServer
34
34
  this.corsHeaders = this.serverHeaders.http2CorsHeaders;
35
35
  this.securityHeaders = this.serverHeaders.http2SecurityHeaders;
36
36
  this.varyHeader = this.serverHeaders.http2VaryHeader;
37
+ this.onError = null;
37
38
  }
38
39
 
39
40
  create()
@@ -60,71 +61,209 @@ class Http2CdnServer
60
61
  }
61
62
  }
62
63
  this.http2Server = http2.createSecureServer(options);
63
- this.setupStreamHandler();
64
+ this.setupEventHandlers();
64
65
  return true;
65
66
  }
66
67
 
67
- setupStreamHandler()
68
+ setupEventHandlers()
68
69
  {
69
70
  this.http2Server.on('stream', (stream, headers) => {
70
71
  this.handleStream(stream, headers);
71
72
  });
73
+ this.http2Server.on('request', (req, res) => {
74
+ this.handleHttp1Request(req, res);
75
+ });
76
+ this.http2Server.on('error', (err) => {
77
+ ServerErrorHandler.handleError(
78
+ this.onError,
79
+ 'http2CdnServer',
80
+ this,
81
+ 'server-error',
82
+ err,
83
+ {port: this.port}
84
+ );
85
+ });
86
+ this.http2Server.on('tlsClientError', (err, tlsSocket) => {
87
+ ServerErrorHandler.handleError(
88
+ this.onError,
89
+ 'http2CdnServer',
90
+ this,
91
+ 'tls-client-error',
92
+ err,
93
+ {port: this.port, remoteAddress: tlsSocket.remoteAddress}
94
+ );
95
+ });
96
+ this.http2Server.on('sessionError', (err) => {
97
+ ServerErrorHandler.handleError(
98
+ this.onError,
99
+ 'http2CdnServer',
100
+ this,
101
+ 'session-error',
102
+ err,
103
+ {port: this.port}
104
+ );
105
+ });
72
106
  }
73
107
 
74
108
  handleStream(stream, headers)
75
109
  {
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};
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;
123
+ }
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});
133
+ stream.end();
134
+ return;
135
+ }
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
+ }
86
143
  if(allowedOrigin){
87
- optionsHeaders['access-control-allow-origin'] = 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();
88
178
  }
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
179
  }
117
- if(this.mimeTypes[ext]){
118
- responseHeaders['content-type'] = this.mimeTypes[ext];
180
+ }
181
+
182
+ handleHttp1Request(req, res)
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);
241
+ res.end();
242
+ }
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);
257
+ res.end();
258
+ }
119
259
  }
120
- stream.respondWithFile(filePath, responseHeaders);
121
260
  }
122
261
 
123
262
  resolveFilePath(requestPath)
124
263
  {
125
264
  let cleanPath = ServerFactoryUtils.stripQueryString(requestPath);
126
265
  for(let staticPath of this.staticPaths){
127
- let fullPath = path.join(staticPath, cleanPath);
266
+ let fullPath = FileHandler.joinPaths(staticPath, cleanPath);
128
267
  if(!FileHandler.exists(fullPath)){
129
268
  continue;
130
269
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ *
3
+ * Reldens - ServerErrorHandler
4
+ *
5
+ */
6
+
7
+ class ServerErrorHandler
8
+ {
9
+
10
+ static handleError(onErrorCallback, instanceName, instance, key, error, context = {})
11
+ {
12
+ if('function' !== typeof onErrorCallback){
13
+ return;
14
+ }
15
+ onErrorCallback({
16
+ [instanceName]: instance,
17
+ key: key,
18
+ error: error,
19
+ ...context
20
+ });
21
+ }
22
+
23
+ }
24
+
25
+ module.exports.ServerErrorHandler = ServerErrorHandler;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/server-utils",
3
3
  "scope": "@reldens",
4
- "version": "0.41.0",
4
+ "version": "0.43.0",
5
5
  "description": "Reldens - Server Utils",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -39,7 +39,7 @@
39
39
  "body-parser": "2.2.1",
40
40
  "compression": "1.8.1",
41
41
  "cors": "2.8.5",
42
- "express": "4.21.2",
42
+ "express": "4.22.1",
43
43
  "express-rate-limit": "8.2.1",
44
44
  "express-session": "1.18.2",
45
45
  "helmet": "8.1.0",