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 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": "/let/log",
139
+ "log_dir": "/var/log",
136
140
  "debug_level": 9,
137
141
 
138
142
  "WebServer": {
139
143
  "http_port": 80,
140
- "http_htdocs_dir": "/let/www/html"
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 `/let/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.
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. `/let/www/html`.
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 list of hosts to allow on incoming requests. Specifically, this matches the incoming HTTP `Host` request header, 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 (and disallow requests to the IP address). Here is how to set this up:
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/, '/let/www/custom' );
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 `/let/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 `/let/www/custom/foo/file1.txt` on disk.
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/, '/let/www/custom', {
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 `/let/www/html` issue this command:
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 /let/www/html -d mydomain.com
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: `/let/log/letsencrypt/letsencrypt.log`
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": "/let/log/web-server-dumps",
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 `/let/log/web-server-dumps` directory every 10 seconds, while one or more maximum limits are maxed out.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixl-server-web",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
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",
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
- return listener.address();
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: 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.close( function() { self.logDebug(3, "HTTP server on port " + info.port + " has shut down.", info); } );
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 = {};