pixl-server-web 2.0.1 → 2.0.2
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 +106 -1
- package/lib/request.js +34 -0
- package/package.json +1 -1
- package/test/test.js +107 -1
- package/web_server.js +24 -0
package/README.md
CHANGED
|
@@ -34,6 +34,9 @@ This module is a component for use in [pixl-server](https://www.github.com/jhuck
|
|
|
34
34
|
* [http_enable_brotli](#http_enable_brotli)
|
|
35
35
|
* [http_brotli_opts](#http_brotli_opts)
|
|
36
36
|
* [http_default_acl](#http_default_acl)
|
|
37
|
+
* [http_blacklist](#http_blacklist)
|
|
38
|
+
* [http_rewrites](#http_rewrites)
|
|
39
|
+
* [http_redirects](#http_redirects)
|
|
37
40
|
* [http_log_requests](#http_log_requests)
|
|
38
41
|
* [http_log_request_details](#http_log_request_details)
|
|
39
42
|
* [http_log_body_max](#http_log_body_max)
|
|
@@ -438,6 +441,108 @@ This example would reject all incoming IP addresses from Apple and AT&T (who own
|
|
|
438
441
|
|
|
439
442
|
When a new incoming connection is established, the socket IP is immediately checked against the blacklist, and if matched, the socket is "hard closed". This is an early detection and rejection, before the HTTP request even comes in. In this case a HTTP response isn't sent back (as the socket is simply slammed shut). However, if you are using a load balancer or proxy, the user's true IP address might not be known until later on in the request cycle, once the HTTP headers are read in. At that point all the user's IPs are checked against the blacklist again, and if any of them match, a `HTTP 403 Forbidden` response is sent back.
|
|
440
443
|
|
|
444
|
+
## http_rewrites
|
|
445
|
+
|
|
446
|
+
If you need to rewrite certain incoming URLs on-the-fly, you can define rules in the `http_rewrites` object. The basic format is as follows: keys are regular expressions matched on incoming URI paths, and the values are the substitution strings to use as replacements. Here is a simple example:
|
|
447
|
+
|
|
448
|
+
```json
|
|
449
|
+
{
|
|
450
|
+
"http_rewrites": {
|
|
451
|
+
"^/rewrite/me": "/target/path"
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
This would match any incoming URI paths that start with `/rewrite/me` and replace that section of the path with `/target/path`. So for example a full URI path of `/rewrite/me/please?foo=bar` would rewrite to `/target/path/please?foo=bar`. Note that the suffix after the match was copied over, as well as the query string. Rewriting happens very early in the request cycle before any other processing occurs, including URI filters, method handers and URI handlers, so they all see the final transformed URI, and not the original.
|
|
457
|
+
|
|
458
|
+
Since URIs are matched using regular expressions, you can define capturing groups and refer to them in the target substitution string, using the standard `$1`, `$2`, `$3` syntax. Example:
|
|
459
|
+
|
|
460
|
+
```json
|
|
461
|
+
{
|
|
462
|
+
"http_rewrites": {
|
|
463
|
+
"^/rewrite/me(.*)$": "/target/path?oldpath=$1"
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
This example would grab everything after `/rewrite/me` and store it in a capture group, which is then expanded into the replacement string using the `$1` macro.
|
|
469
|
+
|
|
470
|
+
For even more control over your rewrites, you can specify them using an advanced syntax. Instead of the target path string, set the value to an object containing the following:
|
|
471
|
+
|
|
472
|
+
| Property Name | Type | Description |
|
|
473
|
+
|---------------|------|-------------|
|
|
474
|
+
| `url` | String | The target URI replacement string. |
|
|
475
|
+
| `headers` | Object | Optionally insert custom headers into the incoming request. |
|
|
476
|
+
| `last` | Boolean | Set this to `true` to ensure no futher rewrites happen on the request. |
|
|
477
|
+
|
|
478
|
+
Here is an example showing an advanced configuration:
|
|
479
|
+
|
|
480
|
+
```json
|
|
481
|
+
{
|
|
482
|
+
"http_rewrites": {
|
|
483
|
+
"^/rewrite/me": {
|
|
484
|
+
"url": "/target/path",
|
|
485
|
+
"headers": { "X-Rewritten": "Yes" },
|
|
486
|
+
"last": true
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
A URI may be rewritten multiple times if it matches multiple rules, which are applied in the order which they appear in your configuration. You can specify a `last` property to ensure that rule matching stops when the specified rule matches a request.
|
|
493
|
+
|
|
494
|
+
You can use the `headers` property to insert custom HTTP headers into the request. These will be accessible by downstream URI handlers, and they will also be logged if [http_log_requests](#http_log_requests) is enabled.
|
|
495
|
+
|
|
496
|
+
## http_redirects
|
|
497
|
+
|
|
498
|
+
If you need to redirect certain incoming requests to external URLs, you can define rules in the `http_redirects` object. When matched, these will interrupt the current request and return a redirect response to the client. The basic format is as follows: keys are regular expressions matched on incoming URI paths, and the values are the fully-qualified URLs to redirect to. Here is a simple example:
|
|
499
|
+
|
|
500
|
+
```json
|
|
501
|
+
{
|
|
502
|
+
"http_redirects": {
|
|
503
|
+
"^/redirect/me": "https://disney.com/"
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
This would match any incoming URI paths that start with `/redirect/me` and redirect the user to `https://disney.com/`. Redirects are matched during the URI handling portion of the request cycle, so things like requests and URI filters have already been handled. URI request handlers are not invoked if a redirect occurs.
|
|
509
|
+
|
|
510
|
+
Since URIs are matched using regular expressions, you can define capturing groups and refer to them in the target redirect URL, using the standard `$1`, `$2`, `$3` syntax. Example:
|
|
511
|
+
|
|
512
|
+
```json
|
|
513
|
+
{
|
|
514
|
+
"http_redirects": {
|
|
515
|
+
"^/github/(.*)$": "https://github.com/jhuckaby/$1"
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
This example would grab everything after `/github/` and store it in a capture group, which is then expanded into the replacement string using the `$1` macro. For example, `/github/pixl-server-web` would redirect to `https://github.com/jhuckaby/pixl-server-web`.
|
|
521
|
+
|
|
522
|
+
For even more control over your redirects, you can specify them using an advanced syntax. Instead of the target URL, set the value to an object containing the following:
|
|
523
|
+
|
|
524
|
+
| Property Name | Type | Description |
|
|
525
|
+
|---------------|------|-------------|
|
|
526
|
+
| `url` | String | The fully qualified URL to redirect to. |
|
|
527
|
+
| `headers` | Object | Optionally insert custom headers into the incoming request. |
|
|
528
|
+
| `status` | String | The HTTP response code and status to use, default is `302 Found`. |
|
|
529
|
+
|
|
530
|
+
Here is an example showing an advanced configuration:
|
|
531
|
+
|
|
532
|
+
```json
|
|
533
|
+
{
|
|
534
|
+
"http_redirects": {
|
|
535
|
+
"^/redirect/me": {
|
|
536
|
+
"url": "https://disney.com/",
|
|
537
|
+
"headers": { "X-Redirected": "Yes" },
|
|
538
|
+
"status": "301 Moved Permanently"
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
You can use the `headers` property to insert custom HTTP headers into the redirect response. Use the `status` to customize the HTTP response code and status (it defaults to `302 Found`).
|
|
545
|
+
|
|
441
546
|
## http_log_requests
|
|
442
547
|
|
|
443
548
|
This boolean allows you to enable transaction logging in the web server. It defaults to `false` (disabled). See [Transaction Logging](#transaction-logging) below for details.
|
|
@@ -450,7 +555,7 @@ This boolean adds verbose detail in the transaction log. It defaults to `false`
|
|
|
450
555
|
|
|
451
556
|
## http_log_body_max
|
|
452
557
|
|
|
453
|
-
This property sets the maximum allowed request and response body length that can be logged, when [http_log_request_details](#http_log_request_details) is enabled. If the request or response body length exceeds this amount, they will not be included in the transaction log.
|
|
558
|
+
This property sets the maximum allowed request and response body length that can be logged, when [http_log_request_details](#http_log_request_details) is enabled. It defaults to `32768` (32K). If the request or response body length exceeds this amount, they will not be included in the transaction log.
|
|
454
559
|
|
|
455
560
|
**Note:** This property only has effect if [http_log_request_details](#http_log_request_details) is enabled.
|
|
456
561
|
|
package/lib/request.js
CHANGED
|
@@ -175,6 +175,21 @@ module.exports = class Request {
|
|
|
175
175
|
});
|
|
176
176
|
this.logDebug(9, "Incoming HTTP Headers:", request.headers);
|
|
177
177
|
|
|
178
|
+
// url rewrites
|
|
179
|
+
for (var idx = 0, len = this.rewrites.length; idx < len; idx++) {
|
|
180
|
+
var rewrite = this.rewrites[idx];
|
|
181
|
+
if (request.url.match(rewrite.regexp)) {
|
|
182
|
+
request.url = request.url.replace(rewrite.regexp, rewrite.url);
|
|
183
|
+
this.logDebug(8, "URL rewritten to: " + request.url);
|
|
184
|
+
if (rewrite.headers) {
|
|
185
|
+
for (var key in rewrite.headers) {
|
|
186
|
+
request.headers[key] = rewrite.headers[key];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (rewrite.last) idx = len;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
178
193
|
// detect front-end https
|
|
179
194
|
if (!request.headers.ssl && this.ssl_header_detect) {
|
|
180
195
|
for (var key in this.ssl_header_detect) {
|
|
@@ -427,6 +442,7 @@ module.exports = class Request {
|
|
|
427
442
|
// all filters complete
|
|
428
443
|
// if a filter handled the response, we're done
|
|
429
444
|
if (err === "ABORT") {
|
|
445
|
+
self.deleteUploadTempFiles(args);
|
|
430
446
|
if (args.callback) {
|
|
431
447
|
args.callback();
|
|
432
448
|
delete args.callback;
|
|
@@ -447,6 +463,24 @@ module.exports = class Request {
|
|
|
447
463
|
if (!this.config.get('http_full_uri_match')) uri = uri.replace(/\?.*$/, '');
|
|
448
464
|
var handler = null;
|
|
449
465
|
|
|
466
|
+
// handle redirects first
|
|
467
|
+
for (var idx = 0, len = this.redirects.length; idx < len; idx++) {
|
|
468
|
+
var redirect = this.redirects[idx];
|
|
469
|
+
var matches = args.request.url.match(redirect.regexp);
|
|
470
|
+
if (matches) {
|
|
471
|
+
// redirect now
|
|
472
|
+
var headers = redirect.headers || {};
|
|
473
|
+
|
|
474
|
+
// allow regexp-style placeholder substitution in target url
|
|
475
|
+
headers.Location = redirect.url.replace(/\$(\d+)/g, function(m_all, m_g1) { return matches[ parseInt(m_g1) ]; });
|
|
476
|
+
|
|
477
|
+
this.logDebug(8, "Redirecting to URL: " + headers.Location);
|
|
478
|
+
this.sendHTTPResponse( args, redirect.status || "302 Found", headers, null );
|
|
479
|
+
this.deleteUploadTempFiles(args);
|
|
480
|
+
return;
|
|
481
|
+
} // matched
|
|
482
|
+
} // foreach redirect
|
|
483
|
+
|
|
450
484
|
args.state = 'processing';
|
|
451
485
|
args.perf.begin('process');
|
|
452
486
|
|
package/package.json
CHANGED
package/test/test.js
CHANGED
|
@@ -56,6 +56,18 @@ var server = new PixlServer({
|
|
|
56
56
|
|
|
57
57
|
"http_blacklist": ["5.6.7.0/24"],
|
|
58
58
|
|
|
59
|
+
"http_rewrites": {
|
|
60
|
+
"^/rewrite(.*)$": "/json$1"
|
|
61
|
+
},
|
|
62
|
+
"http_redirects": {
|
|
63
|
+
"^/disney": "https://disney.com/",
|
|
64
|
+
"^/pixar(.*)$": {
|
|
65
|
+
"url": "https://pixar.com$1",
|
|
66
|
+
"headers": { "X-Animal": "Frog" },
|
|
67
|
+
"status": "301 Moved Permanently"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
|
|
59
71
|
"https": 1,
|
|
60
72
|
"https_port": 3021,
|
|
61
73
|
"https_alt_ports": [3121],
|
|
@@ -197,6 +209,100 @@ module.exports = {
|
|
|
197
209
|
);
|
|
198
210
|
},
|
|
199
211
|
|
|
212
|
+
function testSimpleURLRewrite(test) {
|
|
213
|
+
// test simple rewrite
|
|
214
|
+
request.json( 'http://127.0.0.1:3020/rewrite', false,
|
|
215
|
+
{
|
|
216
|
+
headers: {
|
|
217
|
+
'X-Test': "Test"
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
function(err, resp, json, perf) {
|
|
221
|
+
test.ok( !err, "No error from PixlRequest: " + err );
|
|
222
|
+
test.ok( !!resp, "Got resp from PixlRequest" );
|
|
223
|
+
test.ok( resp.statusCode == 200, "Got 200 response: " + resp.statusCode );
|
|
224
|
+
test.ok( resp.headers['via'] == "WebServerTest 1.0", "Correct Via header: " + resp.headers['via'] );
|
|
225
|
+
test.ok( !!json, "Got JSON in response" );
|
|
226
|
+
test.ok( json.code == 0, "Correct code in JSON response: " + json.code );
|
|
227
|
+
test.ok( !!json.user, "Found user object in JSON response" );
|
|
228
|
+
test.ok( json.user.Name == "Joe", "Correct user name in JSON response: " + json.user.Name );
|
|
229
|
+
|
|
230
|
+
// request headers will be echoed back
|
|
231
|
+
test.ok( !!json.headers, "Found headers echoed in JSON response" );
|
|
232
|
+
test.ok( json.headers['x-test'] == "Test", "Found Test header echoed in JSON response" );
|
|
233
|
+
|
|
234
|
+
test.done();
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
function testAdvancedURLRewrite(test) {
|
|
240
|
+
// test advanced rewrite
|
|
241
|
+
request.json( 'http://127.0.0.1:3020/rewrite?foo=bar1234&baz=bop%20pog&animal=frog&animal=dog', false,
|
|
242
|
+
{
|
|
243
|
+
headers: {
|
|
244
|
+
'X-Test': "Test"
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
function(err, resp, json, perf) {
|
|
248
|
+
test.ok( !err, "No error from PixlRequest: " + err );
|
|
249
|
+
test.ok( !!resp, "Got resp from PixlRequest" );
|
|
250
|
+
test.ok( resp.statusCode == 200, "Got 200 response: " + resp.statusCode );
|
|
251
|
+
test.ok( resp.headers['via'] == "WebServerTest 1.0", "Correct Via header: " + resp.headers['via'] );
|
|
252
|
+
test.ok( !!json, "Got JSON in response" );
|
|
253
|
+
test.ok( json.code == 0, "Correct code in JSON response: " + json.code );
|
|
254
|
+
test.ok( !!json.user, "Found user object in JSON response" );
|
|
255
|
+
test.ok( json.user.Name == "Joe", "Correct user name in JSON response: " + json.user.Name );
|
|
256
|
+
|
|
257
|
+
test.ok( !!json.query, "Found query object in JSON response" );
|
|
258
|
+
test.ok( json.query.foo == "bar1234", "Query contains correct foo key" );
|
|
259
|
+
test.ok( json.query.baz == "bop pog", "Query contains correct baz key (URL encoding)" );
|
|
260
|
+
|
|
261
|
+
// dupes should become array by default
|
|
262
|
+
test.ok( typeof(json.query.animal) == 'object', "Query param animal is an object" );
|
|
263
|
+
test.ok( json.query.animal.length == 2, "Query param animal has length 2" );
|
|
264
|
+
test.ok( json.query.animal[0] === 'frog', "First animal is frog" );
|
|
265
|
+
test.ok( json.query.animal[1] === 'dog', "Second animal is dog" );
|
|
266
|
+
|
|
267
|
+
// request headers will be echoed back
|
|
268
|
+
test.ok( !!json.headers, "Found headers echoed in JSON response" );
|
|
269
|
+
test.ok( json.headers['x-test'] == "Test", "Found Test header echoed in JSON response" );
|
|
270
|
+
|
|
271
|
+
test.done();
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
function testSimpleURLRedirect(test) {
|
|
277
|
+
// simple 302
|
|
278
|
+
request.get( 'http://127.0.0.1:3020/disney',
|
|
279
|
+
function(err, resp, data, perf) {
|
|
280
|
+
test.ok( !err, "No error from PixlRequest: " + err );
|
|
281
|
+
test.ok( !!resp, "Got resp from PixlRequest" );
|
|
282
|
+
test.ok( resp.statusCode == 302, "Got 302 response: " + resp.statusCode );
|
|
283
|
+
test.ok( !!resp.headers['location'], "Got Location header" );
|
|
284
|
+
test.ok( !!resp.headers['location'].match(/disney\.com/), "Correct Location header");
|
|
285
|
+
test.done();
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
function testAdvancedURLRedirect(test) {
|
|
291
|
+
// more complex redirect config (301, custom header)
|
|
292
|
+
request.get( 'http://127.0.0.1:3020/pixar/toads',
|
|
293
|
+
function(err, resp, data, perf) {
|
|
294
|
+
test.ok( !err, "No error from PixlRequest: " + err );
|
|
295
|
+
test.ok( !!resp, "Got resp from PixlRequest" );
|
|
296
|
+
test.ok( resp.statusCode == 301, "Got 301 response: " + resp.statusCode );
|
|
297
|
+
test.ok( !!resp.headers['location'], "Got Location header" );
|
|
298
|
+
test.ok( !!resp.headers['location'].match(/pixar\.com\/toads/), "Correct Location header");
|
|
299
|
+
test.ok( !!resp.headers['x-animal'], "Got x-animal header" );
|
|
300
|
+
test.ok( !!resp.headers['x-animal'].match(/frog/i), "Correct x-animal header");
|
|
301
|
+
test.done();
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
},
|
|
305
|
+
|
|
200
306
|
function testHTTPAltPort(test) {
|
|
201
307
|
// test simple HTTP GET request to webserver backend, alternate port
|
|
202
308
|
request.json( 'http://127.0.0.1:3120/json', false,
|
|
@@ -1364,7 +1470,7 @@ module.exports = {
|
|
|
1364
1470
|
},
|
|
1365
1471
|
|
|
1366
1472
|
// redirect
|
|
1367
|
-
function
|
|
1473
|
+
function testRedirectHandler(test) {
|
|
1368
1474
|
request.get( 'http://127.0.0.1:3020/redirect',
|
|
1369
1475
|
function(err, resp, data, perf) {
|
|
1370
1476
|
test.ok( !err, "No error from PixlRequest: " + err );
|
package/web_server.js
CHANGED
|
@@ -219,6 +219,30 @@ class WebServer extends Component {
|
|
|
219
219
|
if (this.reqMaxDumpEnabled && this.reqMaxDumpDir && !fs.existsSync(this.reqMaxDumpDir)) {
|
|
220
220
|
fs.mkdirSync( this.reqMaxDumpDir, { mode: 0o777, recursive: true } );
|
|
221
221
|
}
|
|
222
|
+
|
|
223
|
+
// url rewrites
|
|
224
|
+
this.rewrites = [];
|
|
225
|
+
if (this.config.get('http_rewrites')) {
|
|
226
|
+
var rewrite_map = this.config.get('http_rewrites');
|
|
227
|
+
for (var key in rewrite_map) {
|
|
228
|
+
var rewrite = rewrite_map[key];
|
|
229
|
+
if (typeof(rewrite) == 'string') rewrite = { url: rewrite_map[key] };
|
|
230
|
+
rewrite.regexp = new RegExp(key);
|
|
231
|
+
this.rewrites.push(rewrite);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// url redirects
|
|
236
|
+
this.redirects = [];
|
|
237
|
+
if (this.config.get('http_redirects')) {
|
|
238
|
+
var redir_map = this.config.get('http_redirects');
|
|
239
|
+
for (var key in redir_map) {
|
|
240
|
+
var redirect = redir_map[key];
|
|
241
|
+
if (typeof(redirect) == 'string') redirect = { url: redir_map[key] };
|
|
242
|
+
redirect.regexp = new RegExp(key);
|
|
243
|
+
this.redirects.push(redirect);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
222
246
|
}
|
|
223
247
|
|
|
224
248
|
startAll(callback) {
|