pixl-server-web 1.3.20 → 1.3.22
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 +49 -2
- package/lib/http.js +20 -15
- package/lib/https.js +21 -16
- package/lib/request.js +7 -0
- package/lib/response.js +2 -0
- package/package.json +1 -1
- package/test/test.js +105 -1
- package/web_server.js +46 -19
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ This module is a component for use in [pixl-server](https://www.github.com/jhuck
|
|
|
8
8
|
- [Usage](#usage)
|
|
9
9
|
- [Configuration](#configuration)
|
|
10
10
|
* [http_port](#http_port)
|
|
11
|
+
* [http_alt_ports](#http_alt_ports)
|
|
11
12
|
* [http_bind_address](#http_bind_address)
|
|
12
13
|
* [http_htdocs_dir](#http_htdocs_dir)
|
|
13
14
|
* [http_max_upload_size](#http_max_upload_size)
|
|
@@ -47,12 +48,14 @@ This module is a component for use in [pixl-server](https://www.github.com/jhuck
|
|
|
47
48
|
* [http_clean_headers](#http_clean_headers)
|
|
48
49
|
* [http_log_socket_errors](#http_log_socket_errors)
|
|
49
50
|
* [http_full_uri_match](#http_full_uri_match)
|
|
51
|
+
* [http_flatten_query](#http_flatten_query)
|
|
50
52
|
* [http_req_max_dump_enabled](#http_req_max_dump_enabled)
|
|
51
53
|
* [http_req_max_dump_dir](#http_req_max_dump_dir)
|
|
52
54
|
* [http_req_max_dump_debounce](#http_req_max_dump_debounce)
|
|
53
55
|
* [http_public_ip_offset](#http_public_ip_offset)
|
|
54
56
|
* [https](#https)
|
|
55
57
|
* [https_port](#https_port)
|
|
58
|
+
* [https_alt_ports](#https_alt_ports)
|
|
56
59
|
* [https_cert_file](#https_cert_file)
|
|
57
60
|
* [https_key_file](#https_key_file)
|
|
58
61
|
* [https_ca_file](#https_ca_file)
|
|
@@ -167,11 +170,22 @@ The configuration for this component is set by passing in a `WebServer` key in t
|
|
|
167
170
|
|
|
168
171
|
## http_port
|
|
169
172
|
|
|
170
|
-
This is the port to listen on. The standard web port is 80, but note that only the root user can listen on ports below 1024.
|
|
173
|
+
This is the main port to listen on. The standard web port is 80, but note that only the root user can listen on ports below 1024.
|
|
174
|
+
|
|
175
|
+
## http_alt_ports
|
|
176
|
+
|
|
177
|
+
If you would like to have the server listen on additional ports, add them here as an array. Example:
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
{
|
|
181
|
+
"http_port": 80,
|
|
182
|
+
"http_alt_ports": [ 3000, 8080 ]
|
|
183
|
+
}
|
|
184
|
+
```
|
|
171
185
|
|
|
172
186
|
## http_bind_address
|
|
173
187
|
|
|
174
|
-
Optionally specify an exact local IP address to bind the
|
|
188
|
+
Optionally specify an exact local IP address to bind the listeners to. By default this binds to all available addresses on the machine. Example:
|
|
175
189
|
|
|
176
190
|
```js
|
|
177
191
|
{
|
|
@@ -518,6 +532,26 @@ When this boolean is set to `true`, [Custom URI Handlers](#custom-uri-handlers)
|
|
|
518
532
|
}
|
|
519
533
|
```
|
|
520
534
|
|
|
535
|
+
## http_flatten_query
|
|
536
|
+
|
|
537
|
+
By default, we use the Node.js core [Query String](https://nodejs.org/api/querystring.html) module to parse query strings. This module handles duplicate query params by converting them to arrays. For example, an incoming URI such as `/something?foo=bar1&foo=bar2&name=joe` would produce the following `args.query` object:
|
|
538
|
+
|
|
539
|
+
```js
|
|
540
|
+
{
|
|
541
|
+
"foo": ["bar1", "bar2"],
|
|
542
|
+
"name": "joe"
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
However, if you set `http_flatten_query` to `true` in your configuration, the web server will "flatten" query string parameters, so that duplicate keys will be combined into one, with the latter prevailing. Example:
|
|
547
|
+
|
|
548
|
+
```js
|
|
549
|
+
{
|
|
550
|
+
"foo": "bar2",
|
|
551
|
+
"name": "joe"
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
521
555
|
## http_req_max_dump_enabled
|
|
522
556
|
|
|
523
557
|
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.
|
|
@@ -551,6 +585,17 @@ This boolean allows you to enable HTTPS (SSL) support in the web server. It def
|
|
|
551
585
|
|
|
552
586
|
If HTTPS mode is enabled, this is the port to listen on for secure requests. The standard HTTPS port is 443.
|
|
553
587
|
|
|
588
|
+
## https_alt_ports
|
|
589
|
+
|
|
590
|
+
If you would like to have the server listen on additional HTTPS ports, add them here as an array. Example:
|
|
591
|
+
|
|
592
|
+
```js
|
|
593
|
+
{
|
|
594
|
+
"https_port": 443,
|
|
595
|
+
"https_alt_ports": [ 9000, 9001 ]
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
554
599
|
## https_cert_file
|
|
555
600
|
|
|
556
601
|
If HTTPS mode is enabled, this should point to your SSL certificate file on disk. The certificate file typically has a `.crt` filename extension, or possibly `cert.pem` if using [Let's Encrypt](https://letsencrypt.org/).
|
|
@@ -876,6 +921,8 @@ Duplicate query params become an array. For example, an incoming URI such as `/
|
|
|
876
921
|
}
|
|
877
922
|
```
|
|
878
923
|
|
|
924
|
+
See [http_flatten_query](#http_flatten_query) if you would rather the query string be flattened.
|
|
925
|
+
|
|
879
926
|
### args.params
|
|
880
927
|
|
|
881
928
|
If the request was a HTTP POST, this will contain all the post parameters as key/value pairs. This will take one of three forms, depending on the request's `Content-Type` header:
|
package/lib/http.js
CHANGED
|
@@ -8,10 +8,9 @@ const Perf = require('pixl-perf');
|
|
|
8
8
|
|
|
9
9
|
module.exports = class HTTP {
|
|
10
10
|
|
|
11
|
-
startHTTP(callback) {
|
|
11
|
+
startHTTP(port, callback) {
|
|
12
12
|
// start http server
|
|
13
13
|
var self = this;
|
|
14
|
-
var port = this.config.get('http_port');
|
|
15
14
|
var bind_addr = this.config.get('http_bind_address') || '';
|
|
16
15
|
var max_conns = this.config.get('http_max_connections') || 0;
|
|
17
16
|
var https_force = self.config.get('https_force') || false;
|
|
@@ -52,14 +51,14 @@ module.exports = class HTTP {
|
|
|
52
51
|
}
|
|
53
52
|
};
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
var listener = require('http').createServer( handler );
|
|
56
55
|
|
|
57
|
-
|
|
56
|
+
listener.on('connection', function(socket) {
|
|
58
57
|
var ip = socket.remoteAddress || '';
|
|
59
58
|
|
|
60
59
|
if (max_conns && (self.numConns >= max_conns)) {
|
|
61
60
|
// reached maximum concurrent connections, abort new ones
|
|
62
|
-
self.logError('maxconns', "Maximum concurrent connections reached, denying connection from: " + ip, { ip: ip, max: max_conns });
|
|
61
|
+
self.logError('maxconns', "Maximum concurrent connections reached, denying connection from: " + ip, { ip: ip, port: port, max: max_conns });
|
|
63
62
|
socket.end();
|
|
64
63
|
socket.unref();
|
|
65
64
|
socket.destroy(); // hard close
|
|
@@ -68,7 +67,7 @@ module.exports = class HTTP {
|
|
|
68
67
|
}
|
|
69
68
|
if (self.server.shut) {
|
|
70
69
|
// server is shutting down, abort new connections
|
|
71
|
-
self.logError('shutdown', "Server is shutting down, denying connection from: " + ip, { ip: ip });
|
|
70
|
+
self.logError('shutdown', "Server is shutting down, denying connection from: " + ip, { ip: ip, port: port });
|
|
72
71
|
socket.end();
|
|
73
72
|
socket.unref();
|
|
74
73
|
socket.destroy(); // hard close
|
|
@@ -78,7 +77,7 @@ module.exports = class HTTP {
|
|
|
78
77
|
var id = self.getNextId('c');
|
|
79
78
|
self.conns[ id ] = socket;
|
|
80
79
|
self.numConns++;
|
|
81
|
-
self.logDebug(8, "New incoming HTTP connection: " + id, { ip: ip, num_conns: self.numConns });
|
|
80
|
+
self.logDebug(8, "New incoming HTTP connection: " + id, { ip: ip, port: port, num_conns: self.numConns });
|
|
82
81
|
|
|
83
82
|
// Disable the Nagle algorithm.
|
|
84
83
|
socket.setNoDelay( true );
|
|
@@ -86,6 +85,7 @@ module.exports = class HTTP {
|
|
|
86
85
|
// add our own metadata to socket
|
|
87
86
|
socket._pixl_data = {
|
|
88
87
|
id: id,
|
|
88
|
+
port: port,
|
|
89
89
|
proto: 'http',
|
|
90
90
|
port: port,
|
|
91
91
|
time_start: (new Date()).getTime(),
|
|
@@ -101,6 +101,7 @@ module.exports = class HTTP {
|
|
|
101
101
|
var msg = "Socket preliminary timeout waiting for initial request (" + socket_prelim_timeout + " seconds)";
|
|
102
102
|
var err_args = {
|
|
103
103
|
ip: ip,
|
|
104
|
+
port: port,
|
|
104
105
|
pending: self.queue.length(),
|
|
105
106
|
active: self.queue.running(),
|
|
106
107
|
sockets: self.numConns
|
|
@@ -132,6 +133,7 @@ module.exports = class HTTP {
|
|
|
132
133
|
self.logError(err.code || 'socket', "Socket error: " + id + ": " + msg, {
|
|
133
134
|
ip: ip,
|
|
134
135
|
ips: args.ips,
|
|
136
|
+
port: port,
|
|
135
137
|
state: args.state,
|
|
136
138
|
method: args.request.method,
|
|
137
139
|
uri: args.request.url,
|
|
@@ -156,6 +158,7 @@ module.exports = class HTTP {
|
|
|
156
158
|
var now = (new Date()).getTime();
|
|
157
159
|
self.logDebug(8, "HTTP connection has closed: " + id, {
|
|
158
160
|
ip: ip,
|
|
161
|
+
port: port,
|
|
159
162
|
total_elapsed: now - socket._pixl_data.time_start,
|
|
160
163
|
num_requests: socket._pixl_data.num_requests,
|
|
161
164
|
bytes_in: socket._pixl_data.bytes_in,
|
|
@@ -167,7 +170,7 @@ module.exports = class HTTP {
|
|
|
167
170
|
} );
|
|
168
171
|
} );
|
|
169
172
|
|
|
170
|
-
|
|
173
|
+
listener.on('clientError', function(err, socket) {
|
|
171
174
|
// https://nodejs.org/api/http.html#http_event_clienterror
|
|
172
175
|
if (!socket._pixl_data) socket._pixl_data = {};
|
|
173
176
|
var args = socket._pixl_data.current || { request: {}, id: 'n/a' };
|
|
@@ -181,6 +184,7 @@ module.exports = class HTTP {
|
|
|
181
184
|
id: args.id,
|
|
182
185
|
ip: socket.remoteAddress,
|
|
183
186
|
ips: args.ips,
|
|
187
|
+
port: port,
|
|
184
188
|
state: args.state,
|
|
185
189
|
method: args.request.method,
|
|
186
190
|
uri: args.request.url,
|
|
@@ -208,36 +212,37 @@ module.exports = class HTTP {
|
|
|
208
212
|
}
|
|
209
213
|
});
|
|
210
214
|
|
|
211
|
-
|
|
215
|
+
listener.once('error', function(err) {
|
|
212
216
|
// fatal startup error on HTTP server, probably EADDRINUSE
|
|
213
|
-
self.logError('startup', "Failed to start HTTP listener: " + err.message);
|
|
217
|
+
self.logError('startup', "Failed to start HTTP listener on port: " + port + ": " + err.message);
|
|
214
218
|
return callback(err);
|
|
215
219
|
} );
|
|
216
220
|
|
|
217
221
|
var listen_opts = { port: port };
|
|
218
222
|
if (bind_addr) listen_opts.host = bind_addr;
|
|
219
223
|
|
|
220
|
-
|
|
224
|
+
listener.listen( listen_opts, function(err) {
|
|
221
225
|
if (err) {
|
|
222
|
-
self.logError('startup', "Failed to start HTTP listener: " + err.message);
|
|
226
|
+
self.logError('startup', "Failed to start HTTP listener on port: " + port + ": " + err.message);
|
|
223
227
|
return callback(err);
|
|
224
228
|
}
|
|
225
|
-
var info =
|
|
229
|
+
var info = listener.address();
|
|
226
230
|
self.logDebug(3, "Now listening for HTTP connections", info);
|
|
227
231
|
if (!port) {
|
|
228
232
|
port = info.port;
|
|
229
233
|
self.config.set('http_port', port);
|
|
230
234
|
self.logDebug(3, "Actual HTTP listener port chosen: " + port);
|
|
231
235
|
}
|
|
236
|
+
self.listeners.push(listener);
|
|
232
237
|
callback();
|
|
233
238
|
} );
|
|
234
239
|
|
|
235
240
|
// set idle socket timeout
|
|
236
241
|
if (this.config.get('http_timeout')) {
|
|
237
|
-
|
|
242
|
+
listener.setTimeout( this.config.get('http_timeout') * 1000 );
|
|
238
243
|
}
|
|
239
244
|
if (this.config.get('http_keep_alive_timeout')) {
|
|
240
|
-
|
|
245
|
+
listener.keepAliveTimeout = this.config.get('http_keep_alive_timeout') * 1000;
|
|
241
246
|
}
|
|
242
247
|
}
|
|
243
248
|
|
package/lib/https.js
CHANGED
|
@@ -18,10 +18,9 @@ const ACL = require('pixl-acl');
|
|
|
18
18
|
|
|
19
19
|
module.exports = class HTTP2 {
|
|
20
20
|
|
|
21
|
-
startHTTPS(callback) {
|
|
21
|
+
startHTTPS(port, callback) {
|
|
22
22
|
// start https server
|
|
23
23
|
var self = this;
|
|
24
|
-
var port = this.config.get('https_port');
|
|
25
24
|
var bind_addr = this.config.get('https_bind_address') || this.config.get('http_bind_address') || '';
|
|
26
25
|
var max_conns = this.config.get('https_max_connections') || this.config.get('http_max_connections') || 0;
|
|
27
26
|
var socket_prelim_timeout = self.config.get('https_socket_prelim_timeout') || self.config.get('http_socket_prelim_timeout') || 0;
|
|
@@ -50,14 +49,14 @@ module.exports = class HTTP2 {
|
|
|
50
49
|
// optional chain.pem or the like
|
|
51
50
|
opts.ca = fs.readFileSync( this.config.get('https_ca_file') );
|
|
52
51
|
}
|
|
53
|
-
|
|
52
|
+
var listener = require('https').createServer( opts, handler );
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
listener.on('secureConnection', function(socket) {
|
|
56
55
|
var ip = socket.remoteAddress || '';
|
|
57
56
|
|
|
58
57
|
if (max_conns && (self.numConns >= max_conns)) {
|
|
59
58
|
// reached maximum concurrent connections, abort new ones
|
|
60
|
-
self.logError('maxconns', "Maximum concurrent connections reached, denying request from: " + ip, { ip: ip, max: max_conns });
|
|
59
|
+
self.logError('maxconns', "Maximum concurrent connections reached, denying request from: " + ip, { ip: ip, port: port, max: max_conns });
|
|
61
60
|
socket.end();
|
|
62
61
|
socket.unref();
|
|
63
62
|
socket.destroy(); // hard close
|
|
@@ -66,7 +65,7 @@ module.exports = class HTTP2 {
|
|
|
66
65
|
}
|
|
67
66
|
if (self.server.shut) {
|
|
68
67
|
// server is shutting down, abort new connections
|
|
69
|
-
self.logError('shutdown', "Server is shutting down, denying connection from: " + ip, { ip: ip });
|
|
68
|
+
self.logError('shutdown', "Server is shutting down, denying connection from: " + ip, { ip: ip, port: port});
|
|
70
69
|
socket.end();
|
|
71
70
|
socket.unref();
|
|
72
71
|
socket.destroy(); // hard close
|
|
@@ -76,7 +75,7 @@ module.exports = class HTTP2 {
|
|
|
76
75
|
var id = self.getNextId('cs');
|
|
77
76
|
self.conns[ id ] = socket;
|
|
78
77
|
self.numConns++;
|
|
79
|
-
self.logDebug(8, "New incoming HTTPS (SSL) connection: " + id, { ip: ip, num_conns: self.numConns });
|
|
78
|
+
self.logDebug(8, "New incoming HTTPS (SSL) connection: " + id, { ip: ip, port: port, num_conns: self.numConns });
|
|
80
79
|
|
|
81
80
|
// Disable the Nagle algorithm.
|
|
82
81
|
socket.setNoDelay( true );
|
|
@@ -84,6 +83,7 @@ module.exports = class HTTP2 {
|
|
|
84
83
|
// add our own metadata to socket
|
|
85
84
|
socket._pixl_data = {
|
|
86
85
|
id: id,
|
|
86
|
+
port: port,
|
|
87
87
|
proto: 'https',
|
|
88
88
|
port: port,
|
|
89
89
|
time_start: (new Date()).getTime(),
|
|
@@ -99,6 +99,7 @@ module.exports = class HTTP2 {
|
|
|
99
99
|
var msg = "Socket preliminary timeout waiting for initial request (" + socket_prelim_timeout + " seconds)";
|
|
100
100
|
var err_args = {
|
|
101
101
|
ip: ip,
|
|
102
|
+
port: port,
|
|
102
103
|
pending: self.queue.length(),
|
|
103
104
|
active: self.queue.running(),
|
|
104
105
|
sockets: self.numConns
|
|
@@ -130,6 +131,7 @@ module.exports = class HTTP2 {
|
|
|
130
131
|
self.logError(err.code || 'socket', "Socket error: " + id + ": " + msg, {
|
|
131
132
|
ip: ip,
|
|
132
133
|
ips: args.ips,
|
|
134
|
+
port: port,
|
|
133
135
|
state: args.state,
|
|
134
136
|
method: args.request.method,
|
|
135
137
|
uri: args.request.url,
|
|
@@ -154,6 +156,7 @@ module.exports = class HTTP2 {
|
|
|
154
156
|
var now = (new Date()).getTime();
|
|
155
157
|
self.logDebug(8, "HTTPS (SSL) connection has closed: " + id, {
|
|
156
158
|
ip: ip,
|
|
159
|
+
port: port,
|
|
157
160
|
total_elapsed: now - socket._pixl_data.time_start,
|
|
158
161
|
num_requests: socket._pixl_data.num_requests,
|
|
159
162
|
bytes_in: socket._pixl_data.bytes_in,
|
|
@@ -165,7 +168,7 @@ module.exports = class HTTP2 {
|
|
|
165
168
|
} );
|
|
166
169
|
} );
|
|
167
170
|
|
|
168
|
-
|
|
171
|
+
listener.on('clientError', function(err, socket) {
|
|
169
172
|
// https://nodejs.org/api/http.html#http_event_clienterror
|
|
170
173
|
if (!socket._pixl_data) socket._pixl_data = {};
|
|
171
174
|
var args = socket._pixl_data.current || { request: {}, id: 'n/a' };
|
|
@@ -179,6 +182,7 @@ module.exports = class HTTP2 {
|
|
|
179
182
|
id: args.id,
|
|
180
183
|
ip: socket.remoteAddress,
|
|
181
184
|
ips: args.ips,
|
|
185
|
+
port: port,
|
|
182
186
|
state: args.state,
|
|
183
187
|
method: args.request.method,
|
|
184
188
|
uri: args.request.url,
|
|
@@ -206,40 +210,41 @@ module.exports = class HTTP2 {
|
|
|
206
210
|
}
|
|
207
211
|
});
|
|
208
212
|
|
|
209
|
-
|
|
213
|
+
listener.once('error', function(err) {
|
|
210
214
|
// fatal startup error on HTTPS server, probably EADDRINUSE
|
|
211
|
-
self.logError('startup', "Failed to start HTTPS listener: " + err.message);
|
|
215
|
+
self.logError('startup', "Failed to start HTTPS listener on port: " + port + ": " + err.message);
|
|
212
216
|
return callback(err);
|
|
213
217
|
} );
|
|
214
218
|
|
|
215
219
|
var listen_opts = { port: port };
|
|
216
220
|
if (bind_addr) listen_opts.host = bind_addr;
|
|
217
221
|
|
|
218
|
-
|
|
222
|
+
listener.listen( listen_opts, function(err) {
|
|
219
223
|
if (err) {
|
|
220
|
-
self.logError('startup', "Failed to start HTTPS listener: " + err.message);
|
|
224
|
+
self.logError('startup', "Failed to start HTTPS listener on port: " + port + ": " + err.message);
|
|
221
225
|
return callback(err);
|
|
222
226
|
}
|
|
223
|
-
var info =
|
|
227
|
+
var info = listener.address();
|
|
224
228
|
self.logDebug(3, "Now listening for HTTPS connections", info);
|
|
225
229
|
if (!port) {
|
|
226
230
|
port = info.port;
|
|
227
231
|
self.config.set('https_port', port);
|
|
228
232
|
self.logDebug(3, "Actual HTTPS listener port chosen: " + port);
|
|
229
233
|
}
|
|
234
|
+
self.listeners.push(listener);
|
|
230
235
|
callback();
|
|
231
236
|
} );
|
|
232
237
|
|
|
233
238
|
// set idle socket timeout
|
|
234
239
|
var timeout_sec = this.config.get('https_timeout') || this.config.get('http_timeout') || 0;
|
|
235
240
|
if (timeout_sec) {
|
|
236
|
-
|
|
241
|
+
listener.setTimeout( timeout_sec * 1000 );
|
|
237
242
|
}
|
|
238
243
|
if (this.config.get('https_keep_alive_timeout')) {
|
|
239
|
-
|
|
244
|
+
listener.keepAliveTimeout = this.config.get('https_keep_alive_timeout') * 1000;
|
|
240
245
|
}
|
|
241
246
|
else if (this.config.get('http_keep_alive_timeout')) {
|
|
242
|
-
|
|
247
|
+
listener.keepAliveTimeout = this.config.get('http_keep_alive_timeout') * 1000;
|
|
243
248
|
}
|
|
244
249
|
}
|
|
245
250
|
|
package/lib/request.js
CHANGED
|
@@ -175,6 +175,13 @@ module.exports = class Request {
|
|
|
175
175
|
query = Querystring.parse( RegExp.$1 );
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// optionally flatten query (latter prevails)
|
|
179
|
+
if (this.config.get('http_flatten_query')) {
|
|
180
|
+
for (var key in query) {
|
|
181
|
+
if (typeof(query[key]) == 'object') query[key] = query[key].pop();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
178
185
|
// determine how to process request body
|
|
179
186
|
var params = {};
|
|
180
187
|
var files = {};
|
package/lib/response.js
CHANGED
|
@@ -326,6 +326,8 @@ module.exports = class Response {
|
|
|
326
326
|
id: args.id,
|
|
327
327
|
proto: args.request.headers['ssl'] ? 'https' : socket_data.proto,
|
|
328
328
|
ips: args.ips,
|
|
329
|
+
port: socket_data.port,
|
|
330
|
+
socket: socket_data.id,
|
|
329
331
|
host: args.request.headers['host'] || '',
|
|
330
332
|
ua: args.request.headers['user-agent'] || '',
|
|
331
333
|
perf: metrics
|
package/package.json
CHANGED
package/test/test.js
CHANGED
|
@@ -33,6 +33,7 @@ var server = new PixlServer({
|
|
|
33
33
|
|
|
34
34
|
"WebServer": {
|
|
35
35
|
"http_port": 3020,
|
|
36
|
+
"http_alt_ports": [3120],
|
|
36
37
|
"http_htdocs_dir": __dirname,
|
|
37
38
|
"http_max_upload_size": 1024 * 10,
|
|
38
39
|
"http_static_ttl": 3600,
|
|
@@ -53,6 +54,7 @@ var server = new PixlServer({
|
|
|
53
54
|
|
|
54
55
|
"https": 1,
|
|
55
56
|
"https_port": 3021,
|
|
57
|
+
"https_alt_ports": [3121],
|
|
56
58
|
"https_cert_file": "ssl.crt",
|
|
57
59
|
"https_key_file": "ssl.key",
|
|
58
60
|
"https_force": 0,
|
|
@@ -191,6 +193,33 @@ module.exports = {
|
|
|
191
193
|
);
|
|
192
194
|
},
|
|
193
195
|
|
|
196
|
+
function testHTTPAltPort(test) {
|
|
197
|
+
// test simple HTTP GET request to webserver backend, alternate port
|
|
198
|
+
request.json( 'http://127.0.0.1:3120/json', false,
|
|
199
|
+
{
|
|
200
|
+
headers: {
|
|
201
|
+
'X-Test': "Test"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
function(err, resp, json, perf) {
|
|
205
|
+
test.ok( !err, "No error from PixlRequest: " + err );
|
|
206
|
+
test.ok( !!resp, "Got resp from PixlRequest" );
|
|
207
|
+
test.ok( resp.statusCode == 200, "Got 200 response: " + resp.statusCode );
|
|
208
|
+
test.ok( resp.headers['via'] == "WebServerTest 1.0", "Correct Via header: " + resp.headers['via'] );
|
|
209
|
+
test.ok( !!json, "Got JSON in response" );
|
|
210
|
+
test.ok( json.code == 0, "Correct code in JSON response: " + json.code );
|
|
211
|
+
test.ok( !!json.user, "Found user object in JSON response" );
|
|
212
|
+
test.ok( json.user.Name == "Joe", "Correct user name in JSON response: " + json.user.Name );
|
|
213
|
+
|
|
214
|
+
// request headers will be echoed back
|
|
215
|
+
test.ok( !!json.headers, "Found headers echoed in JSON response" );
|
|
216
|
+
test.ok( json.headers['x-test'] == "Test", "Found Test header echoed in JSON response" );
|
|
217
|
+
|
|
218
|
+
test.done();
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
},
|
|
222
|
+
|
|
194
223
|
function testBadRequest(test) {
|
|
195
224
|
// test bad HTTP GET request to webserver backend
|
|
196
225
|
// this still resolves to the root dir index due to the ../
|
|
@@ -208,7 +237,7 @@ module.exports = {
|
|
|
208
237
|
// query string
|
|
209
238
|
function testQueryString(test) {
|
|
210
239
|
// test simple HTTP GET request with query string
|
|
211
|
-
request.json( 'http://127.0.0.1:3020/json?foo=bar1234&baz=bop%20pog', false,
|
|
240
|
+
request.json( 'http://127.0.0.1:3020/json?foo=bar1234&baz=bop%20pog&animal=frog&animal=dog', false,
|
|
212
241
|
{
|
|
213
242
|
headers: {
|
|
214
243
|
'X-Test': "Test"
|
|
@@ -228,6 +257,12 @@ module.exports = {
|
|
|
228
257
|
test.ok( json.query.foo == "bar1234", "Query contains correct foo key" );
|
|
229
258
|
test.ok( json.query.baz == "bop pog", "Query contains correct baz key (URL encoding)" );
|
|
230
259
|
|
|
260
|
+
// dupes should become array by default
|
|
261
|
+
test.ok( typeof(json.query.animal) == 'object', "Query param animal is an object" );
|
|
262
|
+
test.ok( json.query.animal.length == 2, "Query param animal has length 2" );
|
|
263
|
+
test.ok( json.query.animal[0] === 'frog', "First animal is frog" );
|
|
264
|
+
test.ok( json.query.animal[1] === 'dog', "Second animal is dog" );
|
|
265
|
+
|
|
231
266
|
// request headers will be echoed back
|
|
232
267
|
test.ok( !!json.headers, "Found headers echoed in JSON response" );
|
|
233
268
|
test.ok( json.headers['x-test'] == "Test", "Found Test header echoed in JSON response" );
|
|
@@ -237,6 +272,46 @@ module.exports = {
|
|
|
237
272
|
);
|
|
238
273
|
},
|
|
239
274
|
|
|
275
|
+
function testQueryStringFlatten(test) {
|
|
276
|
+
// test simple HTTP GET request with query string dupes flattened
|
|
277
|
+
var web = this.web_server;
|
|
278
|
+
web.config.set('http_flatten_query', true);
|
|
279
|
+
|
|
280
|
+
request.json( 'http://127.0.0.1:3020/json?foo=bar1234&baz=bop%20pog&animal=frog&animal=dog', false,
|
|
281
|
+
{
|
|
282
|
+
headers: {
|
|
283
|
+
'X-Test': "Test"
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
function(err, resp, json, perf) {
|
|
287
|
+
test.ok( !err, "No error from PixlRequest: " + err );
|
|
288
|
+
test.ok( !!resp, "Got resp from PixlRequest" );
|
|
289
|
+
test.ok( resp.statusCode == 200, "Got 200 response: " + resp.statusCode );
|
|
290
|
+
test.ok( resp.headers['via'] == "WebServerTest 1.0", "Correct Via header: " + resp.headers['via'] );
|
|
291
|
+
test.ok( !!json, "Got JSON in response" );
|
|
292
|
+
test.ok( json.code == 0, "Correct code in JSON response: " + json.code );
|
|
293
|
+
test.ok( !!json.user, "Found user object in JSON response" );
|
|
294
|
+
test.ok( json.user.Name == "Joe", "Correct user name in JSON response: " + json.user.Name );
|
|
295
|
+
|
|
296
|
+
test.ok( !!json.query, "Found query object in JSON response" );
|
|
297
|
+
test.ok( json.query.foo == "bar1234", "Query contains correct foo key" );
|
|
298
|
+
test.ok( json.query.baz == "bop pog", "Query contains correct baz key (URL encoding)" );
|
|
299
|
+
|
|
300
|
+
test.ok( typeof(json.query.animal) == 'string', "Query param animal is a string" );
|
|
301
|
+
test.ok( json.query.animal === 'dog', "Animal is dog" );
|
|
302
|
+
|
|
303
|
+
// request headers will be echoed back
|
|
304
|
+
test.ok( !!json.headers, "Found headers echoed in JSON response" );
|
|
305
|
+
test.ok( json.headers['x-test'] == "Test", "Found Test header echoed in JSON response" );
|
|
306
|
+
|
|
307
|
+
// revert our hot config change
|
|
308
|
+
web.config.set('http_flatten_query', false);
|
|
309
|
+
|
|
310
|
+
test.done();
|
|
311
|
+
}
|
|
312
|
+
);
|
|
313
|
+
},
|
|
314
|
+
|
|
240
315
|
// Cookies in request
|
|
241
316
|
function testCookieRequest(test) {
|
|
242
317
|
// test simple HTTP GET request with cookies
|
|
@@ -1530,6 +1605,35 @@ module.exports = {
|
|
|
1530
1605
|
);
|
|
1531
1606
|
},
|
|
1532
1607
|
|
|
1608
|
+
function testHTTPSAltPort(test) {
|
|
1609
|
+
// test HTTPS GET request to webserver backend on alt port
|
|
1610
|
+
request.json( 'https://127.0.0.1:3121/json', false,
|
|
1611
|
+
{
|
|
1612
|
+
rejectUnauthorized: false, // self-signed cert
|
|
1613
|
+
headers: {
|
|
1614
|
+
'X-Test': "Test"
|
|
1615
|
+
}
|
|
1616
|
+
},
|
|
1617
|
+
function(err, resp, json, perf) {
|
|
1618
|
+
test.ok( !err, "No error from PixlRequest: " + err );
|
|
1619
|
+
test.ok( !!resp, "Got resp from PixlRequest" );
|
|
1620
|
+
test.ok( resp.statusCode == 200, "Got 200 response: " + resp.statusCode );
|
|
1621
|
+
test.ok( resp.headers['via'] == "WebServerTest 1.0", "Correct Via header: " + resp.headers['via'] );
|
|
1622
|
+
test.ok( !!json, "Got JSON in response" );
|
|
1623
|
+
test.ok( json.code == 0, "Correct code in JSON response: " + json.code );
|
|
1624
|
+
test.ok( !!json.user, "Found user object in JSON response" );
|
|
1625
|
+
test.ok( json.user.Name == "Joe", "Correct user name in JSON response: " + json.user.Name );
|
|
1626
|
+
|
|
1627
|
+
// request headers will be echoed back
|
|
1628
|
+
test.ok( !!json.headers, "Found headers echoed in JSON response" );
|
|
1629
|
+
test.ok( json.headers['x-test'] == "Test", "Found Test header echoed in JSON response" );
|
|
1630
|
+
test.ok( !!json.headers.ssl, "SSL pseudo-header present in echo" );
|
|
1631
|
+
|
|
1632
|
+
test.done();
|
|
1633
|
+
}
|
|
1634
|
+
);
|
|
1635
|
+
},
|
|
1636
|
+
|
|
1533
1637
|
function testHTTPSPost(test) {
|
|
1534
1638
|
request.post( 'https://127.0.0.1:3021/json',
|
|
1535
1639
|
{
|
package/web_server.js
CHANGED
|
@@ -60,6 +60,7 @@ module.exports = Class({
|
|
|
60
60
|
"http_clean_headers": false,
|
|
61
61
|
"http_log_socket_errors": true,
|
|
62
62
|
"http_full_uri_match": false,
|
|
63
|
+
"http_flatten_query": false,
|
|
63
64
|
"http_request_timeout": 0,
|
|
64
65
|
"http_req_max_dump_enabled": false,
|
|
65
66
|
"http_req_max_dump_dir": "",
|
|
@@ -90,6 +91,7 @@ class WebServer extends Component {
|
|
|
90
91
|
this.logDebug(2, "pixl-server-web v" + this.version + " starting up");
|
|
91
92
|
|
|
92
93
|
// setup connections and handlers
|
|
94
|
+
this.listeners = [];
|
|
93
95
|
this.conns = {};
|
|
94
96
|
this.requests = {};
|
|
95
97
|
this.uriFilters = [];
|
|
@@ -193,16 +195,41 @@ class WebServer extends Component {
|
|
|
193
195
|
// listen for tick events to swap stat buffers
|
|
194
196
|
this.server.on( 'tick', this.tick.bind(this) );
|
|
195
197
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
198
|
+
this.startAll(callback);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
startAll(callback) {
|
|
202
|
+
// start all HTTP(s) listeners
|
|
203
|
+
var self = this;
|
|
204
|
+
var tasks = [];
|
|
205
|
+
|
|
206
|
+
// always start plain HTTP on base port
|
|
207
|
+
tasks.push([ this.config.get('http_port'), 'startHTTP' ]);
|
|
208
|
+
|
|
209
|
+
// optional additional ports
|
|
210
|
+
(this.config.get('http_alt_ports') || []).forEach( function(port) {
|
|
211
|
+
tasks.push([ port, 'startHTTP' ]);
|
|
205
212
|
} );
|
|
213
|
+
|
|
214
|
+
// optional HTTPS
|
|
215
|
+
if (this.config.get('https')) {
|
|
216
|
+
tasks.push([ this.config.get('https_port'), 'startHTTPS' ]);
|
|
217
|
+
|
|
218
|
+
// optional additional ports
|
|
219
|
+
(this.config.get('https_alt_ports') || []).forEach( function(port) {
|
|
220
|
+
tasks.push([ port, 'startHTTPS' ]);
|
|
221
|
+
} );
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// start all listeners in parallel
|
|
225
|
+
async.each( tasks,
|
|
226
|
+
function(task, callback) {
|
|
227
|
+
var port = task[0];
|
|
228
|
+
var func = task[1];
|
|
229
|
+
self[func](port, callback);
|
|
230
|
+
},
|
|
231
|
+
callback
|
|
232
|
+
);
|
|
206
233
|
}
|
|
207
234
|
|
|
208
235
|
dumpAllRequests(callback) {
|
|
@@ -262,12 +289,13 @@ class WebServer extends Component {
|
|
|
262
289
|
getStats() {
|
|
263
290
|
// get current stats, merged with live socket and request info
|
|
264
291
|
var socket_info = {};
|
|
265
|
-
var listener_info =
|
|
292
|
+
var listener_info = [];
|
|
266
293
|
var now = (new Date()).getTime();
|
|
267
294
|
var num_sockets = 0;
|
|
268
295
|
|
|
269
|
-
|
|
270
|
-
|
|
296
|
+
listener_info = this.listeners.map( function(listener) {
|
|
297
|
+
return listener.address();
|
|
298
|
+
} );
|
|
271
299
|
|
|
272
300
|
for (var key in this.conns) {
|
|
273
301
|
var socket = this.conns[key];
|
|
@@ -431,7 +459,7 @@ class WebServer extends Component {
|
|
|
431
459
|
// shutdown http server
|
|
432
460
|
var self = this;
|
|
433
461
|
|
|
434
|
-
if (this.
|
|
462
|
+
if (this.listeners.length) {
|
|
435
463
|
this.logDebug(2, "Shutting down HTTP server");
|
|
436
464
|
|
|
437
465
|
for (var id in this.requests) {
|
|
@@ -460,12 +488,11 @@ class WebServer extends Component {
|
|
|
460
488
|
this.numConns--;
|
|
461
489
|
} // foreach conn
|
|
462
490
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
// delete this.http;
|
|
491
|
+
// close all listeners
|
|
492
|
+
this.listeners.forEach( function(listener) {
|
|
493
|
+
var info = listener.address() || { port: 'n/a' };
|
|
494
|
+
listener.close( function() { self.logDebug(3, "HTTP server on port " + info.port + " has shut down.", info); } );
|
|
495
|
+
} );
|
|
469
496
|
|
|
470
497
|
this.requests = {};
|
|
471
498
|
this.queue.kill();
|