pixl-server-web 1.3.6 → 1.3.9

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
@@ -20,6 +20,7 @@ This module is a component for use in [pixl-server](https://www.github.com/jhuck
20
20
  * [http_regex_json](#http_regex_json)
21
21
  * [http_response_headers](#http_response_headers)
22
22
  * [http_timeout](#http_timeout)
23
+ * [http_request_timeout](#http_request_timeout)
23
24
  * [http_keep_alives](#http_keep_alives)
24
25
  + [default](#default)
25
26
  + [request](#request)
@@ -42,6 +43,9 @@ This module is a component for use in [pixl-server](https://www.github.com/jhuck
42
43
  * [http_clean_headers](#http_clean_headers)
43
44
  * [http_log_socket_errors](#http_log_socket_errors)
44
45
  * [http_full_uri_match](#http_full_uri_match)
46
+ * [http_req_max_dump_enabled](#http_req_max_dump_enabled)
47
+ * [http_req_max_dump_dir](#http_req_max_dump_dir)
48
+ * [http_req_max_dump_debounce](#http_req_max_dump_debounce)
45
49
  * [https](#https)
46
50
  * [https_port](#https_port)
47
51
  * [https_cert_file](#https_cert_file)
@@ -53,6 +57,7 @@ This module is a component for use in [pixl-server](https://www.github.com/jhuck
53
57
  - [Custom URI Handlers](#custom-uri-handlers)
54
58
  * [Access Control Lists](#access-control-lists)
55
59
  * [Internal File Redirects](#internal-file-redirects)
60
+ * [Static Directory Handlers](#static-directory-handlers)
56
61
  * [Sending Responses](#sending-responses)
57
62
  + [Standard Response](#standard-response)
58
63
  + [Custom Response](#custom-response)
@@ -72,6 +77,7 @@ This module is a component for use in [pixl-server](https://www.github.com/jhuck
72
77
  + [args.cookies](#argscookies)
73
78
  + [args.perf](#argsperf)
74
79
  + [args.server](#argsserver)
80
+ + [args.id](#argsid)
75
81
  * [Request Filters](#request-filters)
76
82
  - [Logging](#logging)
77
83
  - [Stats](#stats)
@@ -88,6 +94,7 @@ This module is a component for use in [pixl-server](https://www.github.com/jhuck
88
94
  * [Self-Referencing URLs](#self-referencing-urls)
89
95
  * [Custom Method Handlers](#custom-method-handlers)
90
96
  * [Let's Encrypt SSL Certificates](#lets-encrypt-ssl-certificates)
97
+ * [Request Max Dump](#request-max-dump)
91
98
  - [License](#license)
92
99
 
93
100
  # Usage
@@ -220,6 +227,34 @@ This param allows you to send back any additional custom HTTP headers with each
220
227
  }
221
228
  ```
222
229
 
230
+ ## http_code_response_headers
231
+
232
+ This property allows you to include *conditional* response headers, based on the HTTP response code. For example, you can instruct the web server to send back a custom header with `404` (File Not Found) responses, like this:
233
+
234
+ ```js
235
+ {
236
+ "http_code_response_headers": {
237
+ "404": {
238
+ "X-Message": "And don't come back!"
239
+ }
240
+ }
241
+ }
242
+ ```
243
+
244
+ An actual useful case would be to include a [Retry-After](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header with all `429` (Too Many Requests) responses, like this:
245
+
246
+ ```js
247
+ {
248
+ "http_code_response_headers": {
249
+ "429": {
250
+ "Retry-After": "10"
251
+ }
252
+ }
253
+ }
254
+ ```
255
+
256
+ This would give a hint to clients when they receive a `429` (Too Many Requests) response from the web server, that they should wait `10` seconds before trying again.
257
+
223
258
  ## http_timeout
224
259
 
225
260
  This sets the idle socket timeout for all incoming HTTP requests, in seconds. If omitted, the Node.js default is 120 seconds. Example:
@@ -464,6 +499,18 @@ When this boolean is set to `true`, [Custom URI Handlers](#custom-uri-handlers)
464
499
  }
465
500
  ```
466
501
 
502
+ ## http_req_max_dump_enabled
503
+
504
+ When this boolean is set to `true`, the [Request Max Dump](#request-max-dump) system is enabled. This will produce a JSON dump file when the web server is maxed out on requests.
505
+
506
+ ## http_req_max_dump_dir
507
+
508
+ When the [Request Max Dump](#request-max-dump) system is enabled, the `http_req_max_dump_dir` property sets the directory path where JSON dump files are dropped. The directory will be created if needed.
509
+
510
+ ## http_req_max_dump_debounce
511
+
512
+ When the [Request Max Dump](#request-max-dump) system is enabled, the `http_req_max_dump_debounce` property sets how many seconds should elapse between dumps, as to not overwhelm the filesystem.
513
+
467
514
  ## https
468
515
 
469
516
  This boolean allows you to enable HTTPS (SSL) support in the web server. It defaults to `false`. Note that you must also set `https_port`, and possibly `https_cert_file` and `https_key_file` for this to work.
@@ -984,31 +1031,51 @@ The result is an object in this format:
984
1031
  "stats": {
985
1032
  "total": {
986
1033
  "st": "mma",
987
- "min": 0.479,
988
- "max": 2.57,
989
- "total": 11.3748,
990
- "count": 11
1034
+ "min": 0.108,
1035
+ "max": 19.964,
1036
+ "total": 18719.696,
1037
+ "count": 2997,
1038
+ "avg": 6.246
1039
+ },
1040
+ "queue": {
1041
+ "st": "mma",
1042
+ "min": 3.707,
1043
+ "max": 10.917,
1044
+ "total": 8510.662,
1045
+ "count": 1373,
1046
+ "avg": 6.198
991
1047
  },
992
1048
  "read": {
993
1049
  "st": "mma",
994
- "min": 0.005,
995
- "max": 0.071,
996
- "total": 0.170,
997
- "count": 11
1050
+ "min": 0,
1051
+ "max": 0.134,
1052
+ "total": 2.533,
1053
+ "count": 1373,
1054
+ "avg": 0.001
998
1055
  },
1056
+ "filter": {
1057
+ "st": "mma",
1058
+ "min": 0,
1059
+ "max": 0,
1060
+ "total": 0,
1061
+ "count": 0,
1062
+ "avg": 0
1063
+ }
999
1064
  "process": {
1000
1065
  "st": "mma",
1001
- "min": 0.123,
1002
- "max": 0.625,
1003
- "total": 2.691,
1004
- "count": 11
1066
+ "min": 0.834,
1067
+ "max": 6.1,
1068
+ "total": 3513.736,
1069
+ "count": 1373,
1070
+ "avg": 2.559
1005
1071
  },
1006
1072
  "write": {
1007
1073
  "st": "mma",
1008
- "min": 0.313,
1009
- "max": 1.747,
1010
- "total": 7.679,
1011
- "count": 11
1074
+ "min": 0.08,
1075
+ "max": 8.85,
1076
+ "total": 6523.865,
1077
+ "count": 2997,
1078
+ "avg": 2.176
1012
1079
  },
1013
1080
  "bytes_in": 0,
1014
1081
  "bytes_out": 1175,
@@ -1107,7 +1174,9 @@ The `stats` object contains real-time performance metrics, representing one whol
1107
1174
  | Property | Type | Description |
1108
1175
  |----------|------|-------------|
1109
1176
  | `total` | Min/Max/Avg | Total request elapsed time. |
1177
+ | `queue` | Min/Max/Avg | Total request time in queue. |
1110
1178
  | `read` | Min/Max/Avg | Total request read time. |
1179
+ | `filter` | Min/Max/Avg | Total request filter time. |
1111
1180
  | `process` | Min/Max/Avg | Total request process time (i.e. custom URI handler). |
1112
1181
  | `write` | Min/Max/Avg | Total request write time. |
1113
1182
  | `bytes_in` | Simple Counter | Total bytes received in the last full second. |
@@ -1344,6 +1413,65 @@ Toss that command into a shell script in `/etc/cron.daily/` and it'll run daily
1344
1413
 
1345
1414
  Certbot produces its own log file here: `/var/log/letsencrypt/letsencrypt.log`
1346
1415
 
1416
+ ## Request Max Dump
1417
+
1418
+ For debugging and troubleshooting purposes, pixl-server-web can optionally generate a "dump" file when it reaches certain traffic limits. Specifically, when one of these events occur:
1419
+
1420
+ - When the [http_max_connections](#http_max_connections) limit is reached.
1421
+ - When the [http_max_queue_length](#http_max_queue_length) limit is reached.
1422
+ - When the [http_max_queue_active](#http_max_queue_active) limit is reached.
1423
+
1424
+ To enable this feature, set the [http_req_max_dump_enabled](#http_req_max_dump_enabled) configuration property to `true`, the [http_req_max_dump_dir](#http_req_max_dump_dir) property to a path on your filesystem to hold your dump files (this will be created if needed), and [http_req_max_dump_debounce](#http_req_max_dump_debounce) to the maximum frequency you want files dumped (in seconds). Example:
1425
+
1426
+ ```json
1427
+ "http_req_max_dump_enabled": true,
1428
+ "http_req_max_dump_dir": "/var/log/web-server-dumps",
1429
+ "http_req_max_dump_debounce": 10
1430
+ ```
1431
+
1432
+ This would generate dump files in the `/var/log/web-server-dumps` directory every 10 seconds, while one or more maximum limits are maxed out.
1433
+
1434
+ The dump files themselves are in JSON format, and contain everything from the [Stats API](#stats), as well as a list of all active and pending requests. For each request, the following information is dumped:
1435
+
1436
+ ```json
1437
+ "r2945": {
1438
+ "uri": "/api/test/sleep?ms=1",
1439
+ "ip": "127.0.0.1",
1440
+ "ips": [
1441
+ "127.0.0.1"
1442
+ ],
1443
+ "headers": {
1444
+ "accept-encoding": "gzip, deflate, br",
1445
+ "user-agent": "Mozilla/5.0; wperf/1.0.4",
1446
+ "host": "localhost:3012",
1447
+ "connection": "keep-alive"
1448
+ },
1449
+ "state": "writing",
1450
+ "date": 1644617758.688,
1451
+ "elapsed": 0.009999990463256836
1452
+ }
1453
+ ```
1454
+
1455
+ Here is a description of each property:
1456
+
1457
+ | Property Name | Type | Description |
1458
+ |---------------|------|-------------|
1459
+ | `uri` | String | The request URI path (sans protocol and hostname). |
1460
+ | `ip` | String | The client's public IP address (may be a load balancer or proxy). |
1461
+ | `ips` | All the client IPs as an array (includes those from proxy headers). |
1462
+ | `headers` | String | All the incoming HTTP request headers from the client (lower-cased keys). |
1463
+ | `state` | String | The state of the request, will be one of `queued`, `reading`, `filtering`, `processing` or `writing`. |
1464
+ | `date` | String | The timestamp of the start of the request, in Epoch seconds. |
1465
+ | `elapsed` | String | The elapsed time of the request in seconds. |
1466
+
1467
+ Each dump file is given a unique filename using the current server hostname, the pixl-server-web process PID, and a high-resolution timestamp in [Base36](https://en.wikipedia.org/wiki/Base36) format. Example:
1468
+
1469
+ ```
1470
+ req-dump-joemax.local-67463-kziyy7eq.json
1471
+ req-dump-joemax.local-67463-kziyy86i.json
1472
+ req-dump-joemax.local-67463-kziyy8yc.json
1473
+ ```
1474
+
1347
1475
  # License
1348
1476
 
1349
1477
  **The MIT License (MIT)**
package/lib/http.js CHANGED
@@ -63,6 +63,7 @@ module.exports = class HTTP {
63
63
  socket.end();
64
64
  socket.unref();
65
65
  socket.destroy(); // hard close
66
+ self.dumpAllRequests();
66
67
  return;
67
68
  }
68
69
  if (self.server.shut) {
package/lib/https.js CHANGED
@@ -61,6 +61,7 @@ module.exports = class HTTP2 {
61
61
  socket.end();
62
62
  socket.unref();
63
63
  socket.destroy(); // hard close
64
+ self.dumpAllRequests();
64
65
  return;
65
66
  }
66
67
  if (self.server.shut) {
package/lib/request.js CHANGED
@@ -16,20 +16,21 @@ module.exports = class Request {
16
16
  id: this.getNextId('r'),
17
17
  date: Date.now() / 1000,
18
18
  request: request,
19
- response: response
19
+ response: response,
20
+ state: 'queued',
21
+ perf: new Perf()
20
22
  };
21
23
 
24
+ args.perf.begin();
25
+
26
+ var ips = args.ips = this.getAllClientIPs(request);
27
+ var ip = args.ip = this.getPublicIP(ips);
28
+
22
29
  if (this.server.shut) {
23
30
  // server is shutting down, deny new requests
24
- var ips = args.ips = this.getAllClientIPs(request);
25
- var ip = args.ip = this.getPublicIP(ips);
26
-
27
31
  this.logError(503, "Server is shutting down, denying request from: " + ip,
28
32
  { id: args.id, ips: ips, uri: request.url, headers: request.headers }
29
33
  );
30
-
31
- args.perf = new Perf();
32
- args.perf.begin();
33
34
  this.sendHTTPResponse( args, "503 Service Unavailable", {}, "503 Service Unavailable (server shutting down)" );
34
35
  return;
35
36
  }
@@ -38,15 +39,13 @@ module.exports = class Request {
38
39
  if (this.queueSkipMatch && request.url.match(this.queueSkipMatch)) {
39
40
  this.logDebug(8, "Bumping request to front of queue: " + request.url);
40
41
  this.requests[ args.id ] = args;
42
+ args.perf.begin('queue');
41
43
  this.queue.unshift(args);
42
44
  return;
43
45
  }
44
46
 
45
47
  if (this.maxQueueActive && (this.queue.running() >= this.maxQueueActive)) {
46
48
  // queue is maxed out on active reqs, reject request immediately
47
- var ips = args.ips = this.getAllClientIPs(request);
48
- var ip = args.ip = this.getPublicIP(ips);
49
-
50
49
  this.logError(429, "Queue is maxed out (" + this.queue.running() + " active reqs), denying new request from: " + ip, {
51
50
  id: args.id,
52
51
  ips: ips,
@@ -56,18 +55,13 @@ module.exports = class Request {
56
55
  active: this.queue.running(),
57
56
  sockets: this.numConns
58
57
  });
59
-
60
- args.perf = new Perf();
61
- args.perf.begin();
62
- this.sendHTTPResponse( args, "429 Too Many Requests", {}, "429 Too Many Requests (queue maxed out)" );
58
+ this.sendHTTPResponse( args, "429 Too Many Requests", {}, "429 Too Many Requests (queue active maxed out)" );
59
+ this.dumpAllRequests();
63
60
  return;
64
61
  }
65
62
 
66
63
  if (this.maxQueueLength && (this.queue.length() >= this.maxQueueLength)) {
67
64
  // queue is maxed out on pending reqs, reject request immediately
68
- var ips = args.ips = this.getAllClientIPs(request);
69
- var ip = args.ip = this.getPublicIP(ips);
70
-
71
65
  this.logError(429, "Queue is maxed out (" + this.queue.length() + " pending reqs), denying new request from: " + ip, {
72
66
  id: args.id,
73
67
  ips: ips,
@@ -77,14 +71,14 @@ module.exports = class Request {
77
71
  active: this.queue.running(),
78
72
  sockets: this.numConns
79
73
  });
80
-
81
- args.perf = new Perf();
82
- args.perf.begin();
83
- this.sendHTTPResponse( args, "429 Too Many Requests", {}, "429 Too Many Requests (queue maxed out)" );
74
+ this.sendHTTPResponse( args, "429 Too Many Requests", {}, "429 Too Many Requests (queue pending maxed out)" );
75
+ this.dumpAllRequests();
84
76
  return;
85
77
  }
86
78
 
87
79
  this.requests[ args.id ] = args;
80
+
81
+ args.perf.begin('queue');
88
82
  this.queue.push(args);
89
83
  }
90
84
 
@@ -93,7 +87,10 @@ module.exports = class Request {
93
87
  // (async dequeue handler function)
94
88
  var self = this;
95
89
  var request = args.request;
96
- var response = args.response;
90
+ var ips = args.ips;
91
+ var ip = args.ip;
92
+
93
+ args.perf.end('queue');
97
94
 
98
95
  // all requests will end up in this callback here
99
96
  args.callback = function() {
@@ -135,9 +132,6 @@ module.exports = class Request {
135
132
  return;
136
133
  }
137
134
 
138
- var ips = this.getAllClientIPs(request);
139
- var ip = this.getPublicIP(ips);
140
-
141
135
  this.logDebug(8, "New HTTP request: " + request.method + " " + request.url + " (" + ips.join(', ') + ")", {
142
136
  id: args.id,
143
137
  socket: request.socket._pixl_data.id,
@@ -187,10 +181,6 @@ module.exports = class Request {
187
181
  } // foreach cookie
188
182
  } // headers.cookie
189
183
 
190
- // track performance of request
191
- args.perf = new Perf();
192
- args.perf.begin();
193
-
194
184
  if (this.server.shut) {
195
185
  // server is shutting down, deny new requests
196
186
  this.logError(503, "Server is shutting down, denying request from: " + ip,
package/lib/response.js CHANGED
@@ -79,6 +79,14 @@ module.exports = class Response {
79
79
  args.http_code = http_code;
80
80
  args.http_status = http_status;
81
81
 
82
+ // merge in conditional headers based on response code
83
+ var code_headers = this.config.get('http_code_response_headers');
84
+ if (code_headers && (http_code in code_headers)) {
85
+ for (var key in code_headers[http_code]) {
86
+ headers[key] = code_headers[http_code][key];
87
+ }
88
+ }
89
+
82
90
  // use duck typing to see if we have a stream, buffer or string
83
91
  var is_stream = (body && body.pipe);
84
92
  var is_buffer = (body && body.fill);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixl-server-web",
3
- "version": "1.3.6",
3
+ "version": "1.3.9",
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",
@@ -27,7 +27,7 @@
27
27
  "formidable": "2.0.1",
28
28
  "errno": "0.1.7",
29
29
  "stream-meter": "1.0.4",
30
- "async": "3.2.0",
30
+ "async": "3.2.2",
31
31
  "mime": "2.5.2"
32
32
  },
33
33
  "devDependencies": {
package/test/test.js CHANGED
@@ -1373,6 +1373,42 @@ module.exports = {
1373
1373
  );
1374
1374
  },
1375
1375
 
1376
+ function testConditionalResponseHeaders(test) {
1377
+ // test response headers per http code
1378
+ var self = this;
1379
+ var web = this.web_server;
1380
+
1381
+ web.config.set('http_code_response_headers', {
1382
+ "403": { 'X-Test-Cond': "Tree Frogs" }
1383
+ });
1384
+
1385
+ request.get( 'http://127.0.0.1:3020/server-status', // ACL'ed endpoint
1386
+ {
1387
+ headers: {
1388
+ "X-Forwarded-For": "1.2.3.4" // external IP
1389
+ }
1390
+ },
1391
+ function(err, resp, data, perf) {
1392
+ test.ok( !err, "No error from PixlRequest: " + err );
1393
+ test.ok( !!resp, "Got resp from PixlRequest" );
1394
+ test.ok( resp.statusCode == 403, "Got 403 response: " + resp.statusCode );
1395
+ test.ok( resp.headers['x-test-cond'] === "Tree Frogs", "Unexpected header: " + resp.headers['X-Test-Cond'] );
1396
+
1397
+ // make sure basic 200 doesn't have header
1398
+ request.json( 'http://127.0.0.1:3020/json', false, {},
1399
+ function(err, resp, json, perf) {
1400
+ test.ok( !err, "No error from PixlRequest: " + err );
1401
+ test.ok( !!resp, "Got resp from PixlRequest" );
1402
+ test.ok( resp.statusCode == 200, "Got 200 response: " + resp.statusCode );
1403
+ test.ok( !resp.headers['x-test-cond'], "Unexpected X-Test-Cond header for HTTP 200!" );
1404
+ web.config.set('http_code_response_headers', null); // reset config
1405
+ test.done();
1406
+ }
1407
+ );
1408
+ }
1409
+ );
1410
+ },
1411
+
1376
1412
  // get stats
1377
1413
  function testStats(test) {
1378
1414
  // test stats API (this also tests ACL pass)
package/web_server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Simple HTTP / HTTPS Web Server
2
2
  // A component for the pixl-server daemon framework.
3
- // Copyright (c) 2015 - 2021 Joseph Huckaby
3
+ // Copyright (c) 2015 - 2022 Joseph Huckaby
4
4
  // Released under the MIT License
5
5
 
6
6
  const fs = require('fs');
@@ -25,39 +25,45 @@ module.exports = Class({
25
25
  version: require( __dirname + '/package.json' ).version,
26
26
 
27
27
  defaultConfig: {
28
- http_private_ip_ranges: ['127.0.0.1', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '::1/128', 'fd00::/8', '169.254.0.0/16', 'fe80::/10'],
29
- http_regex_text: "(text|javascript|json|css|html)",
30
- http_regex_json: "(javascript|js|json)",
31
- http_keep_alives: "default",
32
- http_timeout: 120,
33
- http_static_index: "index.html",
34
- http_static_ttl: 0,
35
- http_max_upload_size: 32 * 1024 * 1024,
36
- http_temp_dir: os.tmpdir(),
37
- http_gzip_opts: {
38
- level: zlib.constants.Z_DEFAULT_COMPRESSION,
39
- memLevel: 8
28
+ "http_private_ip_ranges": ['127.0.0.1', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '::1/128', 'fd00::/8', '169.254.0.0/16', 'fe80::/10'],
29
+ "http_regex_text": "(text|javascript|json|css|html)",
30
+ "http_regex_json": "(javascript|js|json)",
31
+ "http_keep_alives": "default",
32
+ "http_timeout": 120,
33
+ "http_static_index": "index.html",
34
+ "http_static_ttl": 0,
35
+ "http_max_upload_size": 32 * 1024 * 1024,
36
+ "http_temp_dir": os.tmpdir(),
37
+ "http_gzip_opts": {
38
+ "level": zlib.constants.Z_DEFAULT_COMPRESSION,
39
+ "memLevel": 8
40
40
  },
41
- http_brotli_opts: {
42
- chunkSize: 16 * 1024,
43
- mode: "text",
44
- level: 4
41
+ "http_brotli_opts": {
42
+ "chunkSize": 16 * 1024,
43
+ "mode": "text",
44
+ "level": 4
45
45
  },
46
- http_compress_text: false,
47
- http_enable_brotli: false,
48
- http_default_acl: ['127.0.0.1', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '::1/128', 'fd00::/8', '169.254.0.0/16', 'fe80::/10'],
49
- http_log_requests: false,
50
- http_recent_requests: 10,
51
- http_max_connections: 0,
52
- http_max_requests_per_connection: 0,
53
- http_max_concurrent_requests: 0,
54
- http_max_queue_length: 0,
55
- http_max_queue_active: 0,
56
- http_queue_skip_uri_match: false,
57
- http_clean_headers: false,
58
- http_log_socket_errors: true,
59
- http_full_uri_match: false,
60
- http_request_timeout: 0
46
+ "http_compress_text": false,
47
+ "http_enable_brotli": false,
48
+ "http_default_acl": ['127.0.0.1', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '::1/128', 'fd00::/8', '169.254.0.0/16', 'fe80::/10'],
49
+ "http_log_requests": false,
50
+ "http_recent_requests": 10,
51
+ "http_max_connections": 0,
52
+ "http_max_requests_per_connection": 0,
53
+ "http_max_concurrent_requests": 0,
54
+ "http_max_queue_length": 0,
55
+ "http_max_queue_active": 0,
56
+ "http_queue_skip_uri_match": false,
57
+ "http_clean_headers": false,
58
+ "http_log_socket_errors": true,
59
+ "http_full_uri_match": false,
60
+ "http_request_timeout": 0,
61
+
62
+ "http_req_max_dump_enabled": false,
63
+ "http_req_max_dump_dir": "",
64
+ "http_req_max_dump_debounce": 10,
65
+
66
+ "http_code_response_headers": null
61
67
  },
62
68
 
63
69
  conns: null,
@@ -169,6 +175,16 @@ class WebServer extends Component {
169
175
  }
170
176
  }
171
177
 
178
+ // initialize request max dump system, if enabled
179
+ this.reqMaxDumpEnabled = this.config.get('http_req_max_dump_enabled');
180
+ this.reqMaxDumpDir = this.config.get('http_req_max_dump_dir');
181
+ this.reqMaxDumpDebounce = this.config.get('http_req_max_dump_debounce');
182
+ this.reqMaxDumpLast = 0;
183
+
184
+ if (this.reqMaxDumpEnabled && this.reqMaxDumpDir && !fs.existsSync(this.reqMaxDumpDir)) {
185
+ fs.mkdirSync( this.reqMaxDumpDir, { mode: 0o777, recursive: true } );
186
+ }
187
+
172
188
  // listen for tick events to swap stat buffers
173
189
  this.server.on( 'tick', this.tick.bind(this) );
174
190
 
@@ -184,6 +200,40 @@ class WebServer extends Component {
184
200
  } );
185
201
  }
186
202
 
203
+ dumpAllRequests(callback) {
204
+ // create dump file containing info on all active/pending requests
205
+ // this is called when requests or sockets are maxed out
206
+ // only write file every N seconds
207
+ var self = this;
208
+ var now = Date.now() / 1000;
209
+ if (now - this.reqMaxDumpLast < this.reqMaxDumpDebounce) return;
210
+ this.reqMaxDumpLast = now;
211
+
212
+ var dump_file = this.reqMaxDumpDir + '/req-dump-' + os.hostname() + '-' + process.pid + '-' + Date.now().toString(36) + '.json';
213
+ var json = this.getStats();
214
+ json.requests = {};
215
+
216
+ for (var id in this.requests) {
217
+ var args = this.requests[id];
218
+ var info = {
219
+ uri: args.request.url,
220
+ ip: args.ip,
221
+ ips: args.ips,
222
+ headers: args.request.headers,
223
+ state: args.state,
224
+ date: args.date,
225
+ elapsed: now - args.date
226
+ };
227
+ json.requests[id] = info;
228
+ }
229
+
230
+ this.logDebug(5, "Writing dump file: " + dump_file );
231
+ fs.writeFile( dump_file, JSON.stringify(json, null, "\t") + "\n", function(err) {
232
+ if (err) self.logError('dump', "Failed to write dump file: " + dump_file + ": " + err, err);
233
+ if (callback) callback(err);
234
+ } );
235
+ }
236
+
187
237
  deleteUploadTempFiles(args) {
188
238
  // delete leftover temp files created by Formidable
189
239
  for (var key in args.files) {
@@ -244,7 +294,7 @@ class WebServer extends Component {
244
294
  if (!stats.bytes_in) stats.bytes_in = 0;
245
295
  if (!stats.bytes_out) stats.bytes_out = 0;
246
296
 
247
- ['total', 'read', 'process', 'write'].forEach( function(key) {
297
+ ['total', 'queue', 'read', 'filter', 'process', 'write'].forEach( function(key) {
248
298
  if (!stats[key]) stats[key] = { "st": "mma", "min": 0, "max": 0, "total": 0, "count": 0 };
249
299
  } );
250
300