pixl-server-web 1.2.5 → 1.3.3

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Overview
2
2
 
3
- This module is a component for use in [pixl-server](https://www.npmjs.com/package/pixl-server). It implements a simple web server with support for both HTTP and HTTPS, serving static files, and hooks for adding custom URI handlers.
3
+ This module is a component for use in [pixl-server](https://www.github.com/jhuckaby/pixl-server). It implements a simple web server with support for both HTTP and HTTPS, serving static files, and hooks for adding custom URI handlers.
4
4
 
5
5
  # Table of Contents
6
6
 
@@ -25,6 +25,7 @@ This module is a component for use in [pixl-server](https://www.npmjs.com/packag
25
25
  + [request](#request)
26
26
  + [close](#close)
27
27
  * [http_keep_alive_timeout](#http_keep_alive_timeout)
28
+ * [http_socket_prelim_timeout](#http_socket_prelim_timeout)
28
29
  * [http_max_requests_per_connection](#http_max_requests_per_connection)
29
30
  * [http_gzip_opts](#http_gzip_opts)
30
31
  * [http_enable_brotli](#http_enable_brotli)
@@ -136,7 +137,7 @@ server.startup( function() {
136
137
  } );
137
138
  ```
138
139
 
139
- Notice how we are loading the [pixl-server](https://www.npmjs.com/package/pixl-server) parent module, and then specifying [pixl-server-web](https://www.npmjs.com/package/pixl-server-web) as a component:
140
+ Notice how we are loading the [pixl-server](https://www.github.com/jhuckaby/pixl-server) parent module, and then specifying [pixl-server-web](https://www.github.com/jhuckaby/pixl-server-web) as a component:
140
141
 
141
142
  ```js
142
143
  components: [
@@ -212,7 +213,7 @@ This param allows you to send back any additional custom HTTP headers with each
212
213
 
213
214
  ```js
214
215
  {
215
- http_response_headers: {
216
+ "http_response_headers": {
216
217
  "X-My-Custom-Header": "12345",
217
218
  "X-Another-Header": "Hello"
218
219
  }
@@ -221,7 +222,27 @@ This param allows you to send back any additional custom HTTP headers with each
221
222
 
222
223
  ## http_timeout
223
224
 
224
- This sets the idle socket timeout for all incoming HTTP requests. If omitted, the Node.js default is 2 minutes. Please specify your value in seconds.
225
+ This sets the idle socket timeout for all incoming HTTP requests, in seconds. If omitted, the Node.js default is 120 seconds. Example:
226
+
227
+ ```js
228
+ {
229
+ "http_timeout": 120
230
+ }
231
+ ```
232
+
233
+ This only applies to reading from sockets when data is expected. It is an *idle read timeout* on the socket itself, and doesn't apply to request handlers.
234
+
235
+ ## http_request_timeout
236
+
237
+ This property sets an actual hard request timeout for all incoming requests. If the total combined request processing, handling and response time exceeds this value, specified in seconds, then the request is aborted and a `HTTP 408 Request Timeout` response is sent back to the client. This defaults to `0` (disabled). Example use:
238
+
239
+ ```js
240
+ {
241
+ "http_request_timeout": 300
242
+ }
243
+ ```
244
+
245
+ Note that this includes request processing time (e.g. receiving uploaded data from a HTTP POST).
225
246
 
226
247
  ## http_keep_alives
227
248
 
@@ -259,19 +280,17 @@ This completely disables Keep-Alives for all connections. All requests result i
259
280
 
260
281
  ## http_keep_alive_timeout
261
282
 
262
- This sets the HTTP Keep-Alive idle timeout for all sockets. If omitted, the Node.js default is 5 seconds. See [server.keepAliveTimeout](https://nodejs.org/api/http.html#http_server_keepalivetimeout) for details. Example:
283
+ This sets the HTTP Keep-Alive idle timeout for all sockets, measured in seconds. If omitted, the Node.js default is 5 seconds. See [server.keepAliveTimeout](https://nodejs.org/api/http.html#http_server_keepalivetimeout) for details. Example:
263
284
 
264
285
  ```js
265
286
  {
266
- "http_keep_alive_timeout": 5000
287
+ "http_keep_alive_timeout": 5
267
288
  }
268
289
  ```
269
290
 
270
- This feature was introduced in Node.js version 8. Prior to that, the [http_timeout](#http_timeout) was used as the Keep-Alive timeout.
271
-
272
291
  ## http_socket_prelim_timeout
273
292
 
274
- This sets a special preliminary timeout for brand new sockets when they are first connected. If an HTTP request doesn't come over the socket within this timeout (specified in seconds), then the socket is hard closed. This timeout should always be set lower than the [http_timeout](#http_timeout) if used. This defaults to `0` (disabled). Example use:
293
+ This sets a special preliminary timeout for brand new sockets when they are first connected, measured in seconds. If an HTTP request doesn't come over the socket within this timeout (specified in seconds), then the socket is hard closed. This timeout should always be set lower than the [http_timeout](#http_timeout) if used. This defaults to `0` (disabled). Example use:
275
294
 
276
295
  ```js
277
296
  {
@@ -281,6 +300,8 @@ This sets a special preliminary timeout for brand new sockets when they are firs
281
300
 
282
301
  The idea here is to prevent certain DDoS-style attacks, where an attacker opens a large amount of TCP connections without sending any requests over them.
283
302
 
303
+ **Note:** Do not enable this feature if you attach a WebSocket server such as [ws](https://github.com/websockets/ws).
304
+
284
305
  ## http_max_requests_per_connection
285
306
 
286
307
  This allows you to set a maximum number of requests to allow per Keep-Alive connection. It defaults to `0` which means unlimited. If set, and the maximum is reached, a `Connection: close` header is returned, politely asking the client to close the connection. It does not actually hard-close the socket. Example:
@@ -520,7 +541,7 @@ server.WebServer.addURIHandler( /^\/custom\/match\/$/i, 'Custom2', function(args
520
541
 
521
542
  Your handler function is passed exactly two arguments. First, an `args` object containing all kinds of useful information about the request (see [args](#args) below), and a callback function that you must call when the request is complete and you want to send a response.
522
543
 
523
- If you specified a regular expression with paren groups for the URI, the matches array will be included in the `args` object as `args.matches`. Using this you can extract your matched groups from the URI, for e.g. `/^\/api\/(\w+)/`.
544
+ If you specified a regular expression with parenthesis groups for the URI, the matches array will be included in the `args` object as `args.matches`. Using this you can extract your matched groups from the URI, for e.g. `/^\/api\/(\w+)/`.
524
545
 
525
546
  Note that by default, URIs are only matched on their path portion (i.e. sans query string). To include the query string in URI matches, set the [http_full_uri_match](#http_full_uri_match) configuration property to `true`.
526
547
 
@@ -788,12 +809,16 @@ var session_id = args.cookies['session_id'];
788
809
 
789
810
  ### args.perf
790
811
 
791
- This is a reference to a [pixl-perf](https://www.npmjs.com/package/pixl-perf) object, which is used internally by the web server to track performance metrics for the request. The metrics may be logged at the end of each request (see [Logging](#logging) below) and included in the stats (see [Stats](#stats) below).
812
+ This is a reference to a [pixl-perf](https://www.github.com/jhuckaby/pixl-perf) object, which is used internally by the web server to track performance metrics for the request. The metrics may be logged at the end of each request (see [Logging](#logging) below) and included in the stats (see [Stats](#stats) below).
792
813
 
793
814
  ### args.server
794
815
 
795
816
  This is a reference to the pixl-server object which handled the request.
796
817
 
818
+ ### args.id
819
+
820
+ This is an internal ID string used by the server to track and log individual requests.
821
+
797
822
  ## Request Filters
798
823
 
799
824
  Filters allow you to preprocess a request, before any handlers get their hands on it. They can pass data through, manipulate it, or even interrupt and abort requests. Filters are attached to particular URIs or URI patterns, and multiple may be applied to one request, depending on your rules. They can be asynchronous, and can also pass data between one another if desired.
@@ -897,7 +922,7 @@ Here are descriptions of the data JSON properties:
897
922
  | `host` | The hostname from the request URL. |
898
923
  | `perf` | Performance metrics, see below. |
899
924
 
900
- The `perf` object contains performance metrics for the request, as returned from the [pixl-perf](https://www.npmjs.com/package/pixl-perf) module. It includes a `scale` property denoting that all the metrics are displayed in milliseconds (i.e. `1000`). The metrics themselves are in the `perf` object, and counters such as the number of bytes in/out are in the `counters` object.
925
+ The `perf` object contains performance metrics for the request, as returned from the [pixl-perf](https://www.github.com/jhuckaby/pixl-perf) module. It includes a `scale` property denoting that all the metrics are displayed in milliseconds (i.e. `1000`). The metrics themselves are in the `perf` object, and counters such as the number of bytes in/out are in the `counters` object.
901
926
 
902
927
  If you only want to log *some* requests, but not all of them, you can specify a regular expression in the [http_regex_log](#http_regex_log) configuration property, which is matched against the incoming request URIs. Example:
903
928
 
@@ -1111,7 +1136,7 @@ The `recent` array is a sorted list of the last 10 completed requests (most rece
1111
1136
  | `host` | String | The hostname from the request URL. |
1112
1137
  | `ips` | Array | The array of client IPs, including proxy IPs. |
1113
1138
  | `ua` | String | The client's `User-Agent` string. |
1114
- | `perf` | Object | A [pixl-perf](https://www.npmjs.com/package/pixl-perf) performance metrics object containing stats for the request. |
1139
+ | `perf` | Object | A [pixl-perf](https://www.github.com/jhuckaby/pixl-perf) performance metrics object containing stats for the request. |
1115
1140
 
1116
1141
  If you would like more than 10 requests, set the [http_recent_requests](#http_recent_requests) configuration property to the number you want.
1117
1142
 
@@ -1126,7 +1151,7 @@ The `queue` object contains information about the request queue. This includes
1126
1151
 
1127
1152
  ## Including Custom Stats
1128
1153
 
1129
- To include your own application-level metrics in the `getStats()` output, a [pixl-perf](https://www.npmjs.com/package/pixl-perf) performance tracker is made available to your URI handler code via `args.perf`. you can call `begin()` and `end()` on this object directly, to measure your own operations:
1154
+ To include your own application-level metrics in the `getStats()` output, a [pixl-perf](https://www.github.com/jhuckaby/pixl-perf) performance tracker is made available to your URI handler code via `args.perf`. you can call `begin()` and `end()` on this object directly, to measure your own operations:
1130
1155
 
1131
1156
  ```js
1132
1157
  server.WebServer.addURIHandler( '/my/custom/uri', 'Custom Name', function(args, callback) {
@@ -1147,7 +1172,7 @@ server.WebServer.addURIHandler( '/my/custom/uri', 'Custom Name', function(args,
1147
1172
 
1148
1173
  Please do not call `begin()` or `end()` without arguments, as that will mess up the existing performance tracking. Also, make sure you prefix your perf keys so you don't collide with the built-in ones.
1149
1174
 
1150
- Alternatively, you can use your own private [pixl-perf](https://www.npmjs.com/package/pixl-perf) object, and then "import" it into the `args.perf` object at the very end of your handler code, just before you fire the callback. Example:
1175
+ Alternatively, you can use your own private [pixl-perf](https://www.github.com/jhuckaby/pixl-perf) object, and then "import" it into the `args.perf` object at the very end of your handler code, just before you fire the callback. Example:
1151
1176
 
1152
1177
  ```js
1153
1178
  my_perf.end();
@@ -1156,7 +1181,7 @@ args.perf.import( my_perf, "app_" );
1156
1181
 
1157
1182
  This would import all your metrics and prefix the keys with `app_`.
1158
1183
 
1159
- See the [pixl-perf](https://www.npmjs.com/package/pixl-perf) documentation for more details on how to use the tracker.
1184
+ See the [pixl-perf](https://www.github.com/jhuckaby/pixl-perf) documentation for more details on how to use the tracker.
1160
1185
 
1161
1186
  ## Stats URI Handler
1162
1187
 
@@ -1293,7 +1318,7 @@ Certbot produces its own log file here: `/var/log/letsencrypt/letsencrypt.log`
1293
1318
 
1294
1319
  **The MIT License (MIT)**
1295
1320
 
1296
- *Copyright (c) 2015 - 2019 Joseph Huckaby.*
1321
+ *Copyright (c) 2015 - 2021 Joseph Huckaby.*
1297
1322
 
1298
1323
  Permission is hereby granted, free of charge, to any person obtaining a copy
1299
1324
  of this software and associated documentation files (the "Software"), to deal
package/lib/http.js CHANGED
@@ -161,6 +161,7 @@ module.exports = class HTTP {
161
161
  });
162
162
  delete self.conns[ id ];
163
163
  self.numConns--;
164
+ socket._pixl_data.aborted = true;
164
165
  } );
165
166
  } );
166
167
 
@@ -175,6 +176,7 @@ module.exports = class HTTP {
175
176
  }
176
177
 
177
178
  var err_args = {
179
+ id: args.id,
178
180
  ip: socket.remoteAddress,
179
181
  ips: args.ips,
180
182
  state: args.state,
package/lib/https.js CHANGED
@@ -159,6 +159,7 @@ module.exports = class HTTP2 {
159
159
  });
160
160
  delete self.conns[ id ];
161
161
  self.numConns--;
162
+ socket._pixl_data.aborted = true;
162
163
  } );
163
164
  } );
164
165
 
@@ -173,6 +174,7 @@ module.exports = class HTTP2 {
173
174
  }
174
175
 
175
176
  var err_args = {
177
+ id: args.id,
176
178
  ip: socket.remoteAddress,
177
179
  ips: args.ips,
178
180
  state: args.state,
package/lib/request.js CHANGED
@@ -13,6 +13,8 @@ module.exports = class Request {
13
13
  enqueueHTTPRequest(request, response) {
14
14
  // enqueue request for handling as soon as concurrency limits allow
15
15
  var args = {
16
+ id: this.getNextId('r'),
17
+ date: Date.now() / 1000,
16
18
  request: request,
17
19
  response: response
18
20
  };
@@ -23,7 +25,7 @@ module.exports = class Request {
23
25
  var ip = args.ip = this.getPublicIP(ips);
24
26
 
25
27
  this.logError(503, "Server is shutting down, denying request from: " + ip,
26
- { ips: ips, uri: request.url, headers: request.headers }
28
+ { id: args.id, ips: ips, uri: request.url, headers: request.headers }
27
29
  );
28
30
 
29
31
  args.perf = new Perf();
@@ -35,6 +37,7 @@ module.exports = class Request {
35
37
  // allow special URIs to skip the line
36
38
  if (this.queueSkipMatch && request.url.match(this.queueSkipMatch)) {
37
39
  this.logDebug(8, "Bumping request to front of queue: " + request.url);
40
+ this.requests[ args.id ] = args;
38
41
  this.queue.unshift(args);
39
42
  return;
40
43
  }
@@ -45,6 +48,7 @@ module.exports = class Request {
45
48
  var ip = args.ip = this.getPublicIP(ips);
46
49
 
47
50
  this.logError(429, "Queue is maxed out (" + this.queue.running() + " active reqs), denying new request from: " + ip, {
51
+ id: args.id,
48
52
  ips: ips,
49
53
  uri: request.url,
50
54
  headers: request.headers,
@@ -65,6 +69,7 @@ module.exports = class Request {
65
69
  var ip = args.ip = this.getPublicIP(ips);
66
70
 
67
71
  this.logError(429, "Queue is maxed out (" + this.queue.length() + " pending reqs), denying new request from: " + ip, {
72
+ id: args.id,
68
73
  ips: ips,
69
74
  uri: request.url,
70
75
  headers: request.headers,
@@ -79,15 +84,47 @@ module.exports = class Request {
79
84
  return;
80
85
  }
81
86
 
87
+ this.requests[ args.id ] = args;
82
88
  this.queue.push(args);
83
89
  }
84
90
 
85
91
  parseHTTPRequest(args, callback) {
86
92
  // handle raw http request
93
+ // (async dequeue handler function)
87
94
  var self = this;
88
95
  var request = args.request;
89
96
  var response = args.response;
90
- args.callback = callback;
97
+
98
+ // all requests will end up in this callback here
99
+ args.callback = function() {
100
+ if (args.timer) { clearTimeout(args.timer); delete args.timer; }
101
+ delete self.requests[ args.id ];
102
+ callback();
103
+ };
104
+
105
+ // add timer for request timeout
106
+ if (this.config.get('http_request_timeout')) {
107
+ args.timer = setTimeout( function() {
108
+ // request took too long
109
+ delete args.timer;
110
+
111
+ self.logError(408, "Request timed out: " + self.config.get('http_request_timeout') + " seconds", {
112
+ id: args.id,
113
+ socket: request.socket._pixl_data.id,
114
+ ips: args.ips,
115
+ url: self.getSelfURL(args.request, request.url) || request.url,
116
+ state: args.state
117
+ });
118
+
119
+ self.sendHTTPResponse( args,
120
+ "408 Request Timeout",
121
+ { 'Content-Type': "text/html" },
122
+ "408 Request Timeout: " + self.config.get('http_request_timeout') + " seconds.\n"
123
+ );
124
+
125
+ self.deleteUploadTempFiles(args);
126
+ }, this.config.get('http_request_timeout') * 1000 );
127
+ }
91
128
 
92
129
  // check for early abort (client error)
93
130
  if (request.socket._pixl_data.aborted) {
@@ -102,6 +139,7 @@ module.exports = class Request {
102
139
  var ip = this.getPublicIP(ips);
103
140
 
104
141
  this.logDebug(8, "New HTTP request: " + request.method + " " + request.url + " (" + ips.join(', ') + ")", {
142
+ id: args.id,
105
143
  socket: request.socket._pixl_data.id,
106
144
  version: request.httpVersion
107
145
  });
@@ -156,7 +194,7 @@ module.exports = class Request {
156
194
  if (this.server.shut) {
157
195
  // server is shutting down, deny new requests
158
196
  this.logError(503, "Server is shutting down, denying request from: " + ip,
159
- { ips: ips, uri: request.url, headers: request.headers }
197
+ { id: args.id, ips: ips, uri: request.url, headers: request.headers }
160
198
  );
161
199
  this.sendHTTPResponse( args, "503 Service Unavailable", {}, "503 Service Unavailable (server shutting down)" );
162
200
  return;
@@ -181,12 +219,12 @@ module.exports = class Request {
181
219
 
182
220
  if (content_type.match(/(multipart|urlencoded)/i) && !content_encoding) {
183
221
  // use formidable for the heavy lifting
184
- var form = new Formidable.IncomingForm({
222
+ var form = Formidable({
185
223
  keepExtensions: true,
186
224
  maxFieldsSize: self.config.get('http_max_upload_size'),
187
225
  maxFileSize: self.config.get('http_max_upload_size'),
188
- hash: false,
189
- uploadDir: self.config.get('http_temp_dir')
226
+ uploadDir: self.config.get('http_temp_dir'),
227
+ allowEmptyFiles: self.config.get('http_allow_empty_files') || false
190
228
  });
191
229
 
192
230
  form.on('progress', function(bytesReceived, bytesExpected) {
@@ -199,15 +237,30 @@ module.exports = class Request {
199
237
  form.parse(request, function(err, _fields, _files) {
200
238
  args.perf.end('read');
201
239
  if (err) {
202
- self.logError(400, "Error processing data from: " + ip + ": " + request.url + ": " + err,
203
- { ips: ips, uri: request.url, headers: request.headers }
240
+ self.logError(400, "Error processing data from: " + ip + ": " + request.url + ": " + (err.message || err),
241
+ { id: args.id, ips: ips, uri: request.url, headers: request.headers }
204
242
  );
205
243
  self.sendHTTPResponse( args, "400 Bad Request", {}, "400 Bad Request" );
206
244
  return;
207
245
  }
208
246
  else {
209
247
  args.params = _fields || {};
210
- args.files = _files || {};
248
+
249
+ // restore original formidable v1 API for our files
250
+ args.files = {};
251
+ if (_files) {
252
+ for (var key in _files) {
253
+ var file = _files[key];
254
+ args.files[key] = {
255
+ path: file.filepath,
256
+ type: file.mimetype,
257
+ name: file.originalFilename,
258
+ size: file.size,
259
+ mtime: file.mtime || file.lastModifiedDate
260
+ };
261
+ }
262
+ }
263
+
211
264
  self.filterHTTPRequest(args);
212
265
  }
213
266
  } );
@@ -230,7 +283,7 @@ module.exports = class Request {
230
283
  });
231
284
  if (total_bytes > bytesMax) {
232
285
  self.logError(413, "Error processing data from: " + ip + ": " + request.url + ": Max data size exceeded",
233
- { ips: ips, uri: request.url, headers: request.headers }
286
+ { id: args.id, ips: ips, uri: request.url, headers: request.headers }
234
287
  );
235
288
  request.socket.end();
236
289
 
@@ -253,7 +306,7 @@ module.exports = class Request {
253
306
  }
254
307
  catch (e) {
255
308
  self.logError(400, "Error processing data from: " + ip + ": " + request.url + ": Failed to parse JSON: " + e,
256
- { ips: ips, uri: request.url, headers: request.headers, body: body.toString() }
309
+ { id: args.id, ips: ips, uri: request.url, headers: request.headers, body: body.toString() }
257
310
  );
258
311
  self.sendHTTPResponse( args, "400 Bad Request", {}, "400 Bad Request" );
259
312
  return;
@@ -300,7 +353,7 @@ module.exports = class Request {
300
353
  // use async to allow filters to run in sequence
301
354
  async.eachSeries( filters,
302
355
  function(filter, callback) {
303
- self.logDebug(8, "Invoking filter for request: " + args.request.method + ' ' + uri + ": " + filter.name);
356
+ self.logDebug(8, "Invoking filter for request: " + args.request.method + ' ' + uri + ": " + filter.name, { id: args.id });
304
357
 
305
358
  args.perf.begin('filter');
306
359
  filter.callback( args, function() {
@@ -381,7 +434,7 @@ module.exports = class Request {
381
434
  }
382
435
 
383
436
  if (handler) {
384
- this.logDebug(6, "Invoking handler for request: " + args.request.method + ' ' + uri + ": " + handler.name);
437
+ this.logDebug(6, "Invoking handler for request: " + args.request.method + ' ' + uri + ": " + handler.name, { id: args.id });
385
438
 
386
439
  // Check ACL here
387
440
  if (handler.acl) {
@@ -392,6 +445,7 @@ module.exports = class Request {
392
445
  else {
393
446
  // nope
394
447
  this.logError(403, "Forbidden: IP addresses rejected by ACL: " + args.ips.join(', '), {
448
+ id: args.id,
395
449
  acl: handler.acl.toString(),
396
450
  useragent: args.request.headers['user-agent'] || '',
397
451
  referrer: args.request.headers['referer'] || '',
package/lib/response.js CHANGED
@@ -28,6 +28,7 @@ module.exports = class Response {
28
28
  socket_data.total_elapsed = (new Date()).getTime() - socket_data.time_start;
29
29
  socket_data.url = this.getSelfURL(request, request.url) || request.url;
30
30
  socket_data.ips = args.ips;
31
+ socket_data.req_id = args.id;
31
32
  if (this.config.get('http_log_socket_errors')) {
32
33
  this.logError('socket', "Socket closed unexpectedly: " + socket_data.id, socket_data);
33
34
  }
@@ -98,7 +99,7 @@ module.exports = class Response {
98
99
 
99
100
  response.on('finish', function() {
100
101
  // response actually completed writing
101
- self.logDebug(9, "Response finished writing to socket");
102
+ self.logDebug(9, "Response finished writing to socket", { id: args.id });
102
103
 
103
104
  // guess number of bytes in response header, minus data payload
104
105
  args.perf.count('bytes_out', ("HTTP " + args.http_code + " OK\r\n").length);
@@ -120,6 +121,7 @@ module.exports = class Response {
120
121
  // socket closed during active response
121
122
  if (self.config.get('http_log_socket_errors')) {
122
123
  self.logError('socket', "Socket connection terminated unexpectedly during response", {
124
+ id: args.id,
123
125
  ips: args.ips,
124
126
  useragent: request.headers['user-agent'] || '',
125
127
  referrer: request.headers['referer'] || '',
@@ -136,6 +138,7 @@ module.exports = class Response {
136
138
  if (is_stream) {
137
139
  body.on('error', function(err) {
138
140
  self.logError('stream', "Stream error serving response: " + request.url + ": " + err.message, {
141
+ id: args.id,
139
142
  ips: args.ips,
140
143
  useragent: request.headers['user-agent'] || '',
141
144
  referrer: request.headers['referer'] || '',
@@ -260,7 +263,7 @@ module.exports = class Response {
260
263
  response.end();
261
264
  }
262
265
  }
263
- this.logDebug(9, "Request complete");
266
+ this.logDebug(9, "Request complete", { id: args.id });
264
267
  }
265
268
  }
266
269
 
@@ -301,6 +304,7 @@ module.exports = class Response {
301
304
  // write to access log
302
305
  if (this.logRequests && args.request.url.match(this.regexLogRequests)) {
303
306
  this.logTransaction( 'HTTP ' + args.http_code + ' ' + args.http_status, args.request.url, {
307
+ id: args.id,
304
308
  proto: args.request.headers['ssl'] ? 'https' : socket_data.proto,
305
309
  ips: args.ips,
306
310
  host: args.request.headers['host'] || '',
@@ -312,6 +316,7 @@ module.exports = class Response {
312
316
  // keep a list of the most recent N requests
313
317
  if (this.keepRecentRequests) {
314
318
  this.recent.unshift({
319
+ id: args.id,
315
320
  when: (new Date()).getTime() / 1000,
316
321
  proto: args.request.headers['ssl'] ? 'https' : socket_data.proto,
317
322
  port: socket_data.port,
package/lib/static.js CHANGED
@@ -164,13 +164,13 @@ module.exports = class Static {
164
164
  parseByteRange(req, stat) {
165
165
  // parse byte range header from request
166
166
  // Example header: Range: bytes=31-49
167
- const byteRange = {
168
- from: 0,
169
- to: 0
170
- }
167
+ const byteRange = {
168
+ from: 0,
169
+ to: 0
170
+ }
171
171
 
172
- let rangeHeader = req.headers['range'];
173
- const flavor = 'bytes=';
172
+ let rangeHeader = req.headers['range'];
173
+ const flavor = 'bytes=';
174
174
 
175
175
  if (rangeHeader && rangeHeader.startsWith(flavor) && !rangeHeader.includes(',')) {
176
176
  // Parse
@@ -193,7 +193,7 @@ module.exports = class Static {
193
193
  }
194
194
  }
195
195
 
196
- return null;
197
- }
196
+ return null;
197
+ }
198
198
 
199
199
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixl-server-web",
3
- "version": "1.2.5",
3
+ "version": "1.3.3",
4
4
  "description": "A web server component for the pixl-server framework.",
5
5
  "author": "Joseph Huckaby <jhuckaby@gmail.com>",
6
6
  "homepage": "https://github.com/jhuckaby/pixl-server-web",
@@ -24,7 +24,7 @@
24
24
  "pixl-perf": "^1.0.0",
25
25
  "pixl-acl": "^1.0.1",
26
26
  "class-plus": "^1.0.0",
27
- "formidable": "1.2.1",
27
+ "formidable": "2.0.1",
28
28
  "errno": "0.1.7",
29
29
  "stream-meter": "1.0.4",
30
30
  "async": "3.2.0",
package/test/test.js CHANGED
@@ -186,6 +186,20 @@ module.exports = {
186
186
  );
187
187
  },
188
188
 
189
+ function testBadRequest(test) {
190
+ // test bad HTTP GET request to webserver backend
191
+ // this still resolves to the root dir index due to the ../
192
+ request.get( 'http://127.0.0.1:3020/%0ASet-Cookie%3Acrlfinjection/../',
193
+ function(err, resp, data, perf) {
194
+ test.ok( !err, "No error from PixlRequest: " + err );
195
+ test.ok( !!resp, "Got resp from PixlRequest" );
196
+ test.ok( resp.statusCode == 200, "Got 200 response: " + resp.statusCode );
197
+ test.ok( resp.headers['via'] == "WebServerTest 1.0", "Correct Via header: " + resp.headers['via'] );
198
+ test.done();
199
+ }
200
+ );
201
+ },
202
+
189
203
  // query string
190
204
  function testQueryString(test) {
191
205
  // test simple HTTP GET request with query string
@@ -707,6 +721,22 @@ module.exports = {
707
721
  );
708
722
  },
709
723
 
724
+ // Error (Back-end Timeout)
725
+ function testBackEndTimeout(test) {
726
+ var self = this;
727
+ var web = this.web_server;
728
+ web.config.set('http_request_timeout', 0.5); // 500ms
729
+
730
+ request.get( 'http://127.0.0.1:3020/sleep?ms=750', {},
731
+ function(err, resp, data, perf) {
732
+ web.config.set('http_request_timeout', 0); // reset timeout
733
+ test.ok( !err, "Unexpected error from PixlRequest: " + err );
734
+ test.ok( resp.statusCode == 408, "Unexpected HTTP response code: " + resp.statusCode );
735
+ test.done();
736
+ }
737
+ );
738
+ },
739
+
710
740
  // static file get
711
741
  // check ttl, check gzip
712
742
  function testStaticTextRequest(test) {
package/web_server.js CHANGED
@@ -56,7 +56,8 @@ module.exports = Class({
56
56
  http_queue_skip_uri_match: false,
57
57
  http_clean_headers: false,
58
58
  http_log_socket_errors: true,
59
- http_full_uri_match: false
59
+ http_full_uri_match: false,
60
+ http_request_timeout: 0
60
61
  },
61
62
 
62
63
  conns: null,
@@ -82,6 +83,7 @@ class WebServer extends Component {
82
83
 
83
84
  // setup connections and handlers
84
85
  this.conns = {};
86
+ this.requests = {};
85
87
  this.uriFilters = [];
86
88
  this.uriHandlers = [];
87
89
  this.methodHandlers = [];
@@ -354,13 +356,31 @@ class WebServer extends Component {
354
356
  if (this.http) {
355
357
  this.logDebug(2, "Shutting down HTTP server");
356
358
 
359
+ for (var id in this.requests) {
360
+ var args = this.requests[id];
361
+ this.logDebug(4, "Request still active: " + args.id, {
362
+ id: args.id,
363
+ ips: args.ips,
364
+ uri: args.request ? args.request.url : '',
365
+ headers: args.request ? args.request.headers : {},
366
+ socket: (args.request && args.request.socket && args.request.socket._pixl_data) ? args.request.socket._pixl_data.id : '',
367
+ stats: args.state,
368
+ date: args.date,
369
+ age: (Date.now() / 1000) - args.date
370
+ });
371
+ if (args.callback) {
372
+ args.callback();
373
+ delete args.callback;
374
+ }
375
+ } // foreach req
376
+
357
377
  for (var id in this.conns) {
358
- this.logDebug(9, "Closing HTTP connection: " + id);
378
+ this.logDebug(4, "Closing HTTP connection: " + id);
359
379
  // this.conns[id].destroy();
360
380
  this.conns[id].end();
361
381
  this.conns[id].unref();
362
382
  this.numConns--;
363
- }
383
+ } // foreach conn
364
384
 
365
385
  this.http.close( function() { self.logDebug(3, "HTTP server has shut down."); } );
366
386
 
@@ -369,6 +389,7 @@ class WebServer extends Component {
369
389
  }
370
390
  // delete this.http;
371
391
 
392
+ this.requests = {};
372
393
  this.queue.kill();
373
394
  }
374
395