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 +144 -16
- package/lib/http.js +1 -0
- package/lib/https.js +1 -0
- package/lib/request.js +19 -29
- package/lib/response.js +8 -0
- package/package.json +2 -2
- package/test/test.js +36 -0
- package/web_server.js +83 -33
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.
|
|
988
|
-
"max":
|
|
989
|
-
"total":
|
|
990
|
-
"count":
|
|
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
|
|
995
|
-
"max": 0.
|
|
996
|
-
"total":
|
|
997
|
-
"count":
|
|
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.
|
|
1002
|
-
"max":
|
|
1003
|
-
"total":
|
|
1004
|
-
"count":
|
|
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.
|
|
1009
|
-
"max":
|
|
1010
|
-
"total":
|
|
1011
|
-
"count":
|
|
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
package/lib/https.js
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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 -
|
|
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
|
|