@reldens/server-utils 0.42.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.
@@ -5,11 +5,13 @@
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
+ const { EventDispatcher } = require('./event-dispatcher');
14
+ const { CdnRequestHandler } = require('./cdn-request-handler');
13
15
 
14
16
  class Http2CdnServer
15
17
  {
@@ -34,6 +36,11 @@ class Http2CdnServer
34
36
  this.corsHeaders = this.serverHeaders.http2CorsHeaders;
35
37
  this.securityHeaders = this.serverHeaders.http2SecurityHeaders;
36
38
  this.varyHeader = this.serverHeaders.http2VaryHeader;
39
+ this.onError = null;
40
+ this.onRequestSuccess = null;
41
+ this.onRequestError = null;
42
+ this.onEvent = null;
43
+ this.requestHandler = new CdnRequestHandler(this);
37
44
  }
38
45
 
39
46
  create()
@@ -60,71 +67,167 @@ class Http2CdnServer
60
67
  }
61
68
  }
62
69
  this.http2Server = http2.createSecureServer(options);
63
- this.setupStreamHandler();
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
+ );
64
78
  return true;
65
79
  }
66
80
 
67
- setupStreamHandler()
81
+ setupEventHandlers()
68
82
  {
69
83
  this.http2Server.on('stream', (stream, headers) => {
70
84
  this.handleStream(stream, headers);
71
85
  });
86
+ this.http2Server.on('request', (req, res) => {
87
+ this.handleHttp1Request(req, res);
88
+ });
89
+ this.http2Server.on('error', (err) => {
90
+ ServerErrorHandler.handleError(
91
+ this.onError,
92
+ 'http2CdnServer',
93
+ this,
94
+ 'server-error',
95
+ err,
96
+ {port: this.port}
97
+ );
98
+ });
99
+ this.http2Server.on('tlsClientError', (err, tlsSocket) => {
100
+ ServerErrorHandler.handleError(
101
+ this.onError,
102
+ 'http2CdnServer',
103
+ this,
104
+ 'tls-client-error',
105
+ err,
106
+ {port: this.port, remoteAddress: tlsSocket.remoteAddress}
107
+ );
108
+ });
109
+ this.http2Server.on('sessionError', (err) => {
110
+ ServerErrorHandler.handleError(
111
+ this.onError,
112
+ 'http2CdnServer',
113
+ this,
114
+ 'session-error',
115
+ err,
116
+ {port: this.port}
117
+ );
118
+ });
119
+ EventDispatcher.dispatch(
120
+ this.onEvent,
121
+ 'cdn-handlers-setup',
122
+ 'http2CdnServer',
123
+ this,
124
+ {port: this.port}
125
+ );
72
126
  }
73
127
 
74
- handleStream(stream, headers)
128
+ invokeRequestSuccess(requestData)
75
129
  {
76
- let requestPath = headers[':path'];
77
- if(!requestPath){
78
- stream.respond({':status': 400});
79
- stream.end();
130
+ if('function' !== typeof this.onRequestSuccess){
80
131
  return;
81
132
  }
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();
133
+ this.onRequestSuccess(requestData);
134
+ }
135
+
136
+ invokeRequestError(errorData)
137
+ {
138
+ if('function' !== typeof this.onRequestError){
99
139
  return;
100
140
  }
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);
141
+ this.onRequestError(errorData);
142
+ }
143
+
144
+ handleStream(stream, headers)
145
+ {
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;
159
+ }
160
+ stream.respond({':status': statusCode});
161
+ stream.end();
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;
177
+ }
178
+ };
179
+ this.requestHandler.handleRequest(requestContext);
180
+ }
181
+
182
+ handleHttp1Request(req, res)
183
+ {
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);
195
+ res.end();
196
+ return;
197
+ }
198
+ res.writeHead(statusCode);
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;
221
+ }
222
+ };
223
+ this.requestHandler.handleRequest(requestContext);
121
224
  }
122
225
 
123
226
  resolveFilePath(requestPath)
124
227
  {
125
228
  let cleanPath = ServerFactoryUtils.stripQueryString(requestPath);
126
229
  for(let staticPath of this.staticPaths){
127
- let fullPath = path.join(staticPath, cleanPath);
230
+ let fullPath = FileHandler.joinPaths(staticPath, cleanPath);
128
231
  if(!FileHandler.exists(fullPath)){
129
232
  continue;
130
233
  }
@@ -143,6 +246,13 @@ class Http2CdnServer
143
246
  return false;
144
247
  }
145
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
+ );
146
256
  return true;
147
257
  }
148
258
 
@@ -0,0 +1,42 @@
1
+ /**
2
+ *
3
+ * Reldens - RequestLogger
4
+ *
5
+ */
6
+
7
+ class RequestLogger
8
+ {
9
+
10
+ static createMiddleware(onRequestSuccess, onRequestError)
11
+ {
12
+ return (req, res, next) => {
13
+ let startTime = Date.now();
14
+ res.on('finish', () => {
15
+ let responseTime = Date.now()-startTime;
16
+ let requestData = {
17
+ method: req.method,
18
+ path: req.originalUrl,
19
+ statusCode: res.statusCode,
20
+ responseTime: responseTime,
21
+ ip: req.headers['x-forwarded-for'] || req.ip,
22
+ userAgent: req.get('user-agent'),
23
+ hostname: req.headers['x-forwarded-host'] || req.get('host'),
24
+ timestamp: new Date().toISOString()
25
+ };
26
+ if(400 <= res.statusCode){
27
+ if('function' === typeof onRequestError){
28
+ onRequestError(requestData);
29
+ }
30
+ return;
31
+ }
32
+ if('function' === typeof onRequestSuccess){
33
+ onRequestSuccess(requestData);
34
+ }
35
+ });
36
+ next();
37
+ };
38
+ }
39
+
40
+ }
41
+
42
+ module.exports.RequestLogger = RequestLogger;
@@ -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.42.0",
4
+ "version": "0.44.0",
5
5
  "description": "Reldens - Server Utils",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "url": "https://github.com/damian-pastorini/reldens-server-utils/issues"
37
37
  },
38
38
  "dependencies": {
39
- "body-parser": "2.2.1",
39
+ "body-parser": "2.2.2",
40
40
  "compression": "1.8.1",
41
41
  "cors": "2.8.5",
42
42
  "express": "4.22.1",