pixl-server-web 2.0.4 → 2.0.6
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 +36 -20
- package/lib/https.js +13 -4
- package/lib/request.js +1 -1
- package/package.json +1 -1
- package/web_server.js +58 -8
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ This module is a component for use in [pixl-server](https://www.github.com/jhuck
|
|
|
35
35
|
* [http_brotli_opts](#http_brotli_opts)
|
|
36
36
|
* [http_default_acl](#http_default_acl)
|
|
37
37
|
* [http_blacklist](#http_blacklist)
|
|
38
|
+
* [http_allow_hosts](#http_allow_hosts)
|
|
38
39
|
* [http_rewrites](#http_rewrites)
|
|
39
40
|
* [http_redirects](#http_redirects)
|
|
40
41
|
* [http_log_requests](#http_log_requests)
|
|
@@ -59,6 +60,8 @@ This module is a component for use in [pixl-server](https://www.github.com/jhuck
|
|
|
59
60
|
* [http_req_max_dump_debounce](#http_req_max_dump_debounce)
|
|
60
61
|
* [http_public_ip_offset](#http_public_ip_offset)
|
|
61
62
|
* [http_legacy_callback_support](#http_legacy_callback_support)
|
|
63
|
+
* [http_startup_message](#http_startup_message)
|
|
64
|
+
* [http_debug_ttl](#http_debug_ttl)
|
|
62
65
|
* [https](#https)
|
|
63
66
|
* [https_port](#https_port)
|
|
64
67
|
* [https_alt_ports](#https_alt_ports)
|
|
@@ -68,6 +71,7 @@ This module is a component for use in [pixl-server](https://www.github.com/jhuck
|
|
|
68
71
|
* [https_force](#https_force)
|
|
69
72
|
* [https_header_detect](#https_header_detect)
|
|
70
73
|
* [https_timeout](#https_timeout)
|
|
74
|
+
* [https_bind_address](#https_bind_address)
|
|
71
75
|
- [Custom URI Handlers](#custom-uri-handlers)
|
|
72
76
|
* [Access Control Lists](#access-control-lists)
|
|
73
77
|
* [Internal File Redirects](#internal-file-redirects)
|
|
@@ -132,12 +136,12 @@ let server = new PixlServer({
|
|
|
132
136
|
__version: "1.0",
|
|
133
137
|
|
|
134
138
|
config: {
|
|
135
|
-
"log_dir": "/
|
|
139
|
+
"log_dir": "/var/log",
|
|
136
140
|
"debug_level": 9,
|
|
137
141
|
|
|
138
142
|
"WebServer": {
|
|
139
143
|
"http_port": 80,
|
|
140
|
-
"http_htdocs_dir": "/
|
|
144
|
+
"http_htdocs_dir": "/var/www/html"
|
|
141
145
|
}
|
|
142
146
|
},
|
|
143
147
|
|
|
@@ -169,7 +173,7 @@ components: [
|
|
|
169
173
|
]
|
|
170
174
|
```
|
|
171
175
|
|
|
172
|
-
This example is a very simple web server configuration, which will listen on port 80 and serve static files out of `/
|
|
176
|
+
This example is a very simple web server configuration, which will listen on port 80 and serve static files out of `/var/www/html`. However, if the URI is `/my/custom/uri`, a custom callback function is fired and can serve up any response it wants. This is a great way to implement an API.
|
|
173
177
|
|
|
174
178
|
# Configuration
|
|
175
179
|
|
|
@@ -204,7 +208,7 @@ This example would cause the server to *only* listen on localhost, and not any e
|
|
|
204
208
|
|
|
205
209
|
## http_htdocs_dir
|
|
206
210
|
|
|
207
|
-
This is the path to the directory to serve static files out of, e.g. `/
|
|
211
|
+
This is the path to the directory to serve static files out of, e.g. `/var/www/html`.
|
|
208
212
|
|
|
209
213
|
## http_max_upload_size
|
|
210
214
|
|
|
@@ -443,24 +447,18 @@ When a new incoming connection is established, the socket IP is immediately chec
|
|
|
443
447
|
|
|
444
448
|
## http_allow_hosts
|
|
445
449
|
|
|
446
|
-
The `http_allow_hosts` property allows you to specify a
|
|
450
|
+
The `http_allow_hosts` property allows you to specify a limited set of hosts to allow for incoming requests. Specifically, this matches the incoming HTTP `Host` request header, or SNI (TLS handshake) host for HTTPS, and the value must match at least one entry in the array (case-insensitive). For example, if you are hosting your application behind a domain name, you may want to restrict incoming requests so that they must explicitly point to your domain name. Here is how to set this up:
|
|
447
451
|
|
|
448
452
|
```json
|
|
449
453
|
"http_allow_hosts": ["mydomain.com"]
|
|
450
454
|
```
|
|
451
455
|
|
|
452
|
-
In the above example, only requests to `mydomain.com` would be allowed. All other domains or IP addresses in the URL would be rejected with a `HTTP 403 Forbidden` error. Include multiple entries in the array for things like subdomains:
|
|
456
|
+
In the above example, only requests to `mydomain.com` would be allowed. All other domains or IP addresses in the URL would be rejected with a `HTTP 403 Forbidden` error (or in the case of SNI / TLS handshake the socket is simply closed). Include multiple entries in the array for things like subdomains:
|
|
453
457
|
|
|
454
458
|
```json
|
|
455
459
|
"http_allow_hosts": ["mydomain.com", "www.mydomain.com"]
|
|
456
460
|
```
|
|
457
461
|
|
|
458
|
-
Note that if your users have to specify a port number in the URL, this must be specified in the `http_allow_hosts` array as well (it matches the `Host` request header exactly). So for example, if you are hosting an app on a non-standard port number, but you want to restrict the host, include the port like this:
|
|
459
|
-
|
|
460
|
-
```json
|
|
461
|
-
"http_allow_hosts": ["mydomain.com:3000"]
|
|
462
|
-
```
|
|
463
|
-
|
|
464
462
|
If the `http_allow_hosts` array is empty or omitted entirely, all hosts are allowed. This is the default behavior.
|
|
465
463
|
|
|
466
464
|
## http_rewrites
|
|
@@ -748,6 +746,24 @@ This adds support for legacy applications, which require JSONP callback-style AP
|
|
|
748
746
|
|
|
749
747
|
Only enable this if you are supporting a legacy application which is hosted on a private, trusted network.
|
|
750
748
|
|
|
749
|
+
## http_startup_message
|
|
750
|
+
|
|
751
|
+
When set to `true` and running in debug or foreground mode (i.e. `--debug` or `--foreground` CLI flags on startup), this will emit a message to the console on startup detailing all the socket listeners, ports, and URL endpoints you can hit. Example conaole message:
|
|
752
|
+
|
|
753
|
+
```
|
|
754
|
+
Web Server Listeners:
|
|
755
|
+
|
|
756
|
+
Listening for HTTP on port 3020, network '::' (all)
|
|
757
|
+
--> http://192.168.3.25:3020/
|
|
758
|
+
|
|
759
|
+
Listening for HTTPS on port 3021, network '::' (all)
|
|
760
|
+
--> https://192.168.3.25:3021/
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
## http_debug_ttl
|
|
764
|
+
|
|
765
|
+
When set to `true` and running in debug mode (i.e. `--debug` CLI flag on startup), this will override the value of [http_static_ttl](#http_static_ttl) with `0`. Useful for local development, i.e. reloading your web app in the browser.
|
|
766
|
+
|
|
751
767
|
## https
|
|
752
768
|
|
|
753
769
|
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.
|
|
@@ -894,15 +910,15 @@ Note that the `Content-Type` response header is automatically set based on the t
|
|
|
894
910
|
If you would like to host static files in other places besides [http_htdocs_dir](#http_htdocs_dir), possibly with different options, then look no further than the `addDirectoryHandler()` method. This allows you to set up static file handling with a custom base URI, a custom base directory on disk, and apply other options as well. You can call this method as many times as you like to setup multiple static file directories. Example:
|
|
895
911
|
|
|
896
912
|
```js
|
|
897
|
-
server.WebServer.addDirectoryHandler( /^\/mycustomdir/, '/
|
|
913
|
+
server.WebServer.addDirectoryHandler( /^\/mycustomdir/, '/var/www/custom' );
|
|
898
914
|
```
|
|
899
915
|
|
|
900
|
-
The above example would catch all incoming requests starting with `/mycustomdir`, and serve up static files inside of the `/
|
|
916
|
+
The above example would catch all incoming requests starting with `/mycustomdir`, and serve up static files inside of the `/var/www/custom` directory on disk (and possibly nested directories as well). So a URL such as `http://MYSERVER/mycustomdir/foo/file1.txt` would map to the file `/var/www/custom/foo/file1.txt` on disk.
|
|
901
917
|
|
|
902
918
|
In this case a default TTL is applied to all files via [http_static_ttl](#http_static_ttl). If you would like to customize the TTL for your custom static directory, as well as specify other options, pass in an object as the 3rd argument to `addDirectoryHandler()`. Example of this:
|
|
903
919
|
|
|
904
920
|
```js
|
|
905
|
-
server.WebServer.addDirectoryHandler( /^\/mycustomdir/, '/
|
|
921
|
+
server.WebServer.addDirectoryHandler( /^\/mycustomdir/, '/var/www/custom', {
|
|
906
922
|
acl: true
|
|
907
923
|
ttl: 3600,
|
|
908
924
|
headers: {
|
|
@@ -1821,10 +1837,10 @@ curl -s https://dl.eff.org/certbot-auto > /usr/local/bin/certbot-auto
|
|
|
1821
1837
|
chmod a+x /usr/local/bin/certbot-auto
|
|
1822
1838
|
```
|
|
1823
1839
|
|
|
1824
|
-
We'll be using the [Webroot](https://certbot.eff.org/docs/using.html#webroot) method for authorization. Make sure you have a web server running on your server and listening on port 80 (only plain HTTP is required at this point). Assuming your web server's document root path is `/
|
|
1840
|
+
We'll be using the [Webroot](https://certbot.eff.org/docs/using.html#webroot) method for authorization. Make sure you have a web server running on your server and listening on port 80 (only plain HTTP is required at this point). Assuming your web server's document root path is `/var/www/html` issue this command:
|
|
1825
1841
|
|
|
1826
1842
|
```sh
|
|
1827
|
-
/usr/local/bin/certbot-auto certonly --webroot -w /
|
|
1843
|
+
/usr/local/bin/certbot-auto certonly --webroot -w /var/www/html -d mydomain.com
|
|
1828
1844
|
```
|
|
1829
1845
|
|
|
1830
1846
|
If you need certificates for multiple subdomains, you can repeat the `-d` flag, e.g. `-d mydomain.com -d www.mydomain.com`.
|
|
@@ -1878,7 +1894,7 @@ Toss that command into a shell script in `/etc/cron.daily/` and it'll run daily
|
|
|
1878
1894
|
/usr/local/bin/certbot-auto renew --post-hook "/opt/myapp/bin/control.sh restart" >/dev/null 2>&1
|
|
1879
1895
|
```
|
|
1880
1896
|
|
|
1881
|
-
Certbot produces its own log file here: `/
|
|
1897
|
+
Certbot produces its own log file here: `/var/log/letsencrypt/letsencrypt.log`
|
|
1882
1898
|
|
|
1883
1899
|
## Request Max Dump
|
|
1884
1900
|
|
|
@@ -1892,11 +1908,11 @@ To enable this feature, set the [http_req_max_dump_enabled](#http_req_max_dump_e
|
|
|
1892
1908
|
|
|
1893
1909
|
```json
|
|
1894
1910
|
"http_req_max_dump_enabled": true,
|
|
1895
|
-
"http_req_max_dump_dir": "/
|
|
1911
|
+
"http_req_max_dump_dir": "/var/log/web-server-dumps",
|
|
1896
1912
|
"http_req_max_dump_debounce": 10
|
|
1897
1913
|
```
|
|
1898
1914
|
|
|
1899
|
-
This would generate dump files in the `/
|
|
1915
|
+
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.
|
|
1900
1916
|
|
|
1901
1917
|
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, an object like the following is provided:
|
|
1902
1918
|
|
package/lib/https.js
CHANGED
|
@@ -56,7 +56,7 @@ module.exports = class HTTP2 {
|
|
|
56
56
|
|
|
57
57
|
if (max_conns && (self.numConns >= max_conns)) {
|
|
58
58
|
// reached maximum concurrent connections, abort new ones
|
|
59
|
-
self.logError('maxconns', "Maximum concurrent connections reached, denying request from: " + ip, { ip: ip, port: port, max: max_conns });
|
|
59
|
+
self.logError('maxconns', "Maximum concurrent connections reached, denying request from: " + ip, { host: socket.servername || '', ip: ip, port: port, max: max_conns });
|
|
60
60
|
socket.end();
|
|
61
61
|
socket.unref();
|
|
62
62
|
socket.destroy(); // hard close
|
|
@@ -65,14 +65,23 @@ module.exports = class HTTP2 {
|
|
|
65
65
|
}
|
|
66
66
|
if (self.server.shut) {
|
|
67
67
|
// server is shutting down, abort new connections
|
|
68
|
-
self.logError('shutdown', "Server is shutting down, denying connection from: " + ip, { ip: ip, port: port});
|
|
68
|
+
self.logError('shutdown', "Server is shutting down, denying connection from: " + ip, { host: socket.servername || '', ip: ip, port: port});
|
|
69
69
|
socket.end();
|
|
70
70
|
socket.unref();
|
|
71
71
|
socket.destroy(); // hard close
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
if (ip && self.aclBlacklist.checkAny(ip)) {
|
|
75
|
-
self.logError('blacklist', "IP is blacklisted, denying connection from: " + ip, { ip: ip, port: port });
|
|
75
|
+
self.logError('blacklist', "IP is blacklisted, denying connection from: " + ip, { host: socket.servername || '', ip: ip, port: port });
|
|
76
|
+
socket.end();
|
|
77
|
+
socket.unref();
|
|
78
|
+
socket.destroy(); // hard close
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// SNI: check allow list at socket connect time
|
|
83
|
+
if (self.httpsAllowHosts.length && !self.httpsAllowHosts.includes( ('' + socket.servername).toLowerCase() )) {
|
|
84
|
+
self.logError('allowhosts', "SNI host not allowed: " + (socket.servername || 'n/a'), { host: socket.servername || '', ip: ip, port: port });
|
|
76
85
|
socket.end();
|
|
77
86
|
socket.unref();
|
|
78
87
|
socket.destroy(); // hard close
|
|
@@ -82,7 +91,7 @@ module.exports = class HTTP2 {
|
|
|
82
91
|
var id = self.getNextId('cs');
|
|
83
92
|
self.conns[ id ] = socket;
|
|
84
93
|
self.numConns++;
|
|
85
|
-
self.logDebug(8, "New incoming HTTPS (SSL) connection: " + id, { ip: ip, port: port, num_conns: self.numConns });
|
|
94
|
+
self.logDebug(8, "New incoming HTTPS (SSL) connection: " + id, { host: socket.servername || '', ip: ip, port: port, num_conns: self.numConns });
|
|
86
95
|
|
|
87
96
|
// Disable the Nagle algorithm.
|
|
88
97
|
socket.setNoDelay( true );
|
package/lib/request.js
CHANGED
|
@@ -72,7 +72,7 @@ module.exports = class Request {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// custom host allow list
|
|
75
|
-
if (this.allowHosts.length && !this.allowHosts.includes( ('' + request.headers['host']).toLowerCase() )) {
|
|
75
|
+
if (this.allowHosts.length && !this.allowHosts.includes( ('' + request.headers['host']).toLowerCase().replace(/\:\d+$/, '') )) {
|
|
76
76
|
this.logError(403, "Forbidden: Host not allowed: " + (request.headers['host'] || 'n/a'), {
|
|
77
77
|
id: args.id,
|
|
78
78
|
host: request.headers['host'] || '',
|
package/package.json
CHANGED
package/web_server.js
CHANGED
|
@@ -114,6 +114,12 @@ class WebServer extends Component {
|
|
|
114
114
|
// listen for tick events to swap stat buffers
|
|
115
115
|
this.server.on( 'tick', this.tick.bind(this) );
|
|
116
116
|
|
|
117
|
+
// show post-startup foreground console message if applicable
|
|
118
|
+
if ((this.server.debug || this.server.foreground) && this.config.get('http_startup_message')) {
|
|
119
|
+
// add a slight delay to increase chances of user seeing it in the console
|
|
120
|
+
this.server.on('ready', function() { setTimeout( self.postStartupMessage.bind(self), 250 ); } );
|
|
121
|
+
}
|
|
122
|
+
|
|
117
123
|
// start listeners
|
|
118
124
|
this.startAll(callback);
|
|
119
125
|
}
|
|
@@ -247,6 +253,52 @@ class WebServer extends Component {
|
|
|
247
253
|
|
|
248
254
|
// custom host list
|
|
249
255
|
this.allowHosts = (this.config.get('http_allow_hosts') || []).map( function(host) { return host.toLowerCase(); } );
|
|
256
|
+
this.httpsAllowHosts = (this.config.get('https_allow_hosts') || this.config.get('http_allow_hosts') || []).map( function(host) { return host.toLowerCase(); } );
|
|
257
|
+
|
|
258
|
+
// set static TTL to 0 in debug mode
|
|
259
|
+
if (this.server.debug && this.config.get('http_debug_ttl')) {
|
|
260
|
+
this.logDebug(5, "Setting static TTL to 0 for debug mode");
|
|
261
|
+
this.config.set('http_static_ttl', 0);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
postStartupMessage() {
|
|
266
|
+
// show startup message in console (debug / foreground only)
|
|
267
|
+
var self = this;
|
|
268
|
+
var stats = this.getStats();
|
|
269
|
+
var text = '';
|
|
270
|
+
|
|
271
|
+
text += "\nWeb Server Listeners:\n";
|
|
272
|
+
|
|
273
|
+
stats.listeners.forEach( function(info) {
|
|
274
|
+
// {"address":"::1","family":"IPv6","port":3013,"ssl":true}
|
|
275
|
+
var type = info.ssl ? 'HTTPS' : 'HTTP';
|
|
276
|
+
var host = info.address;
|
|
277
|
+
text += `\n\tListening for ${type} on port ${info.port}, network '${info.address}'`;
|
|
278
|
+
|
|
279
|
+
switch (info.address) {
|
|
280
|
+
case '::1':
|
|
281
|
+
case '127.0.0.1':
|
|
282
|
+
text += ' (localhost)';
|
|
283
|
+
host = 'localhost';
|
|
284
|
+
break;
|
|
285
|
+
|
|
286
|
+
case '::':
|
|
287
|
+
case '0.0.0.0':
|
|
288
|
+
text += ' (all)';
|
|
289
|
+
host = self.server.ip; // best guess (ipv4)
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
text += "\n";
|
|
294
|
+
var url = (info.ssl ? 'https://' : 'http://') + host;
|
|
295
|
+
if (info.ssl && (info.port != 443)) url += ':' + info.port;
|
|
296
|
+
if (!info.ssl & (info.port != 80)) url += ':' + info.port;
|
|
297
|
+
url += '/';
|
|
298
|
+
text += "\t--> " + url + "\n";
|
|
299
|
+
} );
|
|
300
|
+
|
|
301
|
+
console.log(text);
|
|
250
302
|
}
|
|
251
303
|
|
|
252
304
|
startAll(callback) {
|
|
@@ -345,7 +397,9 @@ class WebServer extends Component {
|
|
|
345
397
|
var num_sockets = 0;
|
|
346
398
|
|
|
347
399
|
listener_info = this.listeners.map( function(listener) {
|
|
348
|
-
|
|
400
|
+
var info = listener.address() || { port: 'n/a' };
|
|
401
|
+
if (listener.setSecureContext) info.ssl = true;
|
|
402
|
+
return info;
|
|
349
403
|
} );
|
|
350
404
|
|
|
351
405
|
for (var key in this.conns) {
|
|
@@ -392,11 +446,6 @@ class WebServer extends Component {
|
|
|
392
446
|
}
|
|
393
447
|
}
|
|
394
448
|
|
|
395
|
-
var ports = [ this.config.get('http_port') ];
|
|
396
|
-
if (this.config.get('https')) {
|
|
397
|
-
ports.push( this.config.get('https_port') );
|
|
398
|
-
}
|
|
399
|
-
|
|
400
449
|
return {
|
|
401
450
|
server: {
|
|
402
451
|
uptime_sec: Math.floor(now / 1000) - this.server.started,
|
|
@@ -404,7 +453,7 @@ class WebServer extends Component {
|
|
|
404
453
|
ip: this.server.ip,
|
|
405
454
|
name: this.server.__name,
|
|
406
455
|
version: this.server.__version,
|
|
407
|
-
ports:
|
|
456
|
+
ports: listener_info.map( function(info) { return info.port; } )
|
|
408
457
|
},
|
|
409
458
|
stats: stats,
|
|
410
459
|
listeners: listener_info,
|
|
@@ -542,7 +591,8 @@ class WebServer extends Component {
|
|
|
542
591
|
// close all listeners
|
|
543
592
|
this.listeners.forEach( function(listener) {
|
|
544
593
|
var info = listener.address() || { port: 'n/a' };
|
|
545
|
-
listener.
|
|
594
|
+
if (listener.setSecureContext) info.ssl = true;
|
|
595
|
+
listener.close( function() { self.logDebug(3, (info.ssl ? "HTTPS": "HTTP") + " server on port " + info.port + " has shut down.", info); } );
|
|
546
596
|
} );
|
|
547
597
|
|
|
548
598
|
this.requests = {};
|