pixl-server-web 1.2.5 → 1.3.3
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 +42 -17
- package/lib/http.js +2 -0
- package/lib/https.js +2 -0
- package/lib/request.js +67 -13
- package/lib/response.js +7 -2
- package/lib/static.js +8 -8
- package/package.json +2 -2
- package/test/test.js +30 -0
- package/web_server.js +24 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Overview
|
|
2
2
|
|
|
3
|
-
This module is a component for use in [pixl-server](https://www.
|
|
3
|
+
This module is a component for use in [pixl-server](https://www.github.com/jhuckaby/pixl-server). It implements a simple web server with support for both HTTP and HTTPS, serving static files, and hooks for adding custom URI handlers.
|
|
4
4
|
|
|
5
5
|
# Table of Contents
|
|
6
6
|
|
|
@@ -25,6 +25,7 @@ This module is a component for use in [pixl-server](https://www.npmjs.com/packag
|
|
|
25
25
|
+ [request](#request)
|
|
26
26
|
+ [close](#close)
|
|
27
27
|
* [http_keep_alive_timeout](#http_keep_alive_timeout)
|
|
28
|
+
* [http_socket_prelim_timeout](#http_socket_prelim_timeout)
|
|
28
29
|
* [http_max_requests_per_connection](#http_max_requests_per_connection)
|
|
29
30
|
* [http_gzip_opts](#http_gzip_opts)
|
|
30
31
|
* [http_enable_brotli](#http_enable_brotli)
|
|
@@ -136,7 +137,7 @@ server.startup( function() {
|
|
|
136
137
|
} );
|
|
137
138
|
```
|
|
138
139
|
|
|
139
|
-
Notice how we are loading the [pixl-server](https://www.
|
|
140
|
+
Notice how we are loading the [pixl-server](https://www.github.com/jhuckaby/pixl-server) parent module, and then specifying [pixl-server-web](https://www.github.com/jhuckaby/pixl-server-web) as a component:
|
|
140
141
|
|
|
141
142
|
```js
|
|
142
143
|
components: [
|
|
@@ -212,7 +213,7 @@ This param allows you to send back any additional custom HTTP headers with each
|
|
|
212
213
|
|
|
213
214
|
```js
|
|
214
215
|
{
|
|
215
|
-
http_response_headers: {
|
|
216
|
+
"http_response_headers": {
|
|
216
217
|
"X-My-Custom-Header": "12345",
|
|
217
218
|
"X-Another-Header": "Hello"
|
|
218
219
|
}
|
|
@@ -221,7 +222,27 @@ This param allows you to send back any additional custom HTTP headers with each
|
|
|
221
222
|
|
|
222
223
|
## http_timeout
|
|
223
224
|
|
|
224
|
-
This sets the idle socket timeout for all incoming HTTP requests. If omitted, the Node.js default is
|
|
225
|
+
This sets the idle socket timeout for all incoming HTTP requests, in seconds. If omitted, the Node.js default is 120 seconds. Example:
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
{
|
|
229
|
+
"http_timeout": 120
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
This only applies to reading from sockets when data is expected. It is an *idle read timeout* on the socket itself, and doesn't apply to request handlers.
|
|
234
|
+
|
|
235
|
+
## http_request_timeout
|
|
236
|
+
|
|
237
|
+
This property sets an actual hard request timeout for all incoming requests. If the total combined request processing, handling and response time exceeds this value, specified in seconds, then the request is aborted and a `HTTP 408 Request Timeout` response is sent back to the client. This defaults to `0` (disabled). Example use:
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
{
|
|
241
|
+
"http_request_timeout": 300
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Note that this includes request processing time (e.g. receiving uploaded data from a HTTP POST).
|
|
225
246
|
|
|
226
247
|
## http_keep_alives
|
|
227
248
|
|
|
@@ -259,19 +280,17 @@ This completely disables Keep-Alives for all connections. All requests result i
|
|
|
259
280
|
|
|
260
281
|
## http_keep_alive_timeout
|
|
261
282
|
|
|
262
|
-
This sets the HTTP Keep-Alive idle timeout for all sockets. If omitted, the Node.js default is 5 seconds. See [server.keepAliveTimeout](https://nodejs.org/api/http.html#http_server_keepalivetimeout) for details. Example:
|
|
283
|
+
This sets the HTTP Keep-Alive idle timeout for all sockets, measured in seconds. If omitted, the Node.js default is 5 seconds. See [server.keepAliveTimeout](https://nodejs.org/api/http.html#http_server_keepalivetimeout) for details. Example:
|
|
263
284
|
|
|
264
285
|
```js
|
|
265
286
|
{
|
|
266
|
-
"http_keep_alive_timeout":
|
|
287
|
+
"http_keep_alive_timeout": 5
|
|
267
288
|
}
|
|
268
289
|
```
|
|
269
290
|
|
|
270
|
-
This feature was introduced in Node.js version 8. Prior to that, the [http_timeout](#http_timeout) was used as the Keep-Alive timeout.
|
|
271
|
-
|
|
272
291
|
## http_socket_prelim_timeout
|
|
273
292
|
|
|
274
|
-
This sets a special preliminary timeout for brand new sockets when they are first connected. If an HTTP request doesn't come over the socket within this timeout (specified in seconds), then the socket is hard closed. This timeout should always be set lower than the [http_timeout](#http_timeout) if used. This defaults to `0` (disabled). Example use:
|
|
293
|
+
This sets a special preliminary timeout for brand new sockets when they are first connected, measured in seconds. If an HTTP request doesn't come over the socket within this timeout (specified in seconds), then the socket is hard closed. This timeout should always be set lower than the [http_timeout](#http_timeout) if used. This defaults to `0` (disabled). Example use:
|
|
275
294
|
|
|
276
295
|
```js
|
|
277
296
|
{
|
|
@@ -281,6 +300,8 @@ This sets a special preliminary timeout for brand new sockets when they are firs
|
|
|
281
300
|
|
|
282
301
|
The idea here is to prevent certain DDoS-style attacks, where an attacker opens a large amount of TCP connections without sending any requests over them.
|
|
283
302
|
|
|
303
|
+
**Note:** Do not enable this feature if you attach a WebSocket server such as [ws](https://github.com/websockets/ws).
|
|
304
|
+
|
|
284
305
|
## http_max_requests_per_connection
|
|
285
306
|
|
|
286
307
|
This allows you to set a maximum number of requests to allow per Keep-Alive connection. It defaults to `0` which means unlimited. If set, and the maximum is reached, a `Connection: close` header is returned, politely asking the client to close the connection. It does not actually hard-close the socket. Example:
|
|
@@ -520,7 +541,7 @@ server.WebServer.addURIHandler( /^\/custom\/match\/$/i, 'Custom2', function(args
|
|
|
520
541
|
|
|
521
542
|
Your handler function is passed exactly two arguments. First, an `args` object containing all kinds of useful information about the request (see [args](#args) below), and a callback function that you must call when the request is complete and you want to send a response.
|
|
522
543
|
|
|
523
|
-
If you specified a regular expression with
|
|
544
|
+
If you specified a regular expression with parenthesis groups for the URI, the matches array will be included in the `args` object as `args.matches`. Using this you can extract your matched groups from the URI, for e.g. `/^\/api\/(\w+)/`.
|
|
524
545
|
|
|
525
546
|
Note that by default, URIs are only matched on their path portion (i.e. sans query string). To include the query string in URI matches, set the [http_full_uri_match](#http_full_uri_match) configuration property to `true`.
|
|
526
547
|
|
|
@@ -788,12 +809,16 @@ var session_id = args.cookies['session_id'];
|
|
|
788
809
|
|
|
789
810
|
### args.perf
|
|
790
811
|
|
|
791
|
-
This is a reference to a [pixl-perf](https://www.
|
|
812
|
+
This is a reference to a [pixl-perf](https://www.github.com/jhuckaby/pixl-perf) object, which is used internally by the web server to track performance metrics for the request. The metrics may be logged at the end of each request (see [Logging](#logging) below) and included in the stats (see [Stats](#stats) below).
|
|
792
813
|
|
|
793
814
|
### args.server
|
|
794
815
|
|
|
795
816
|
This is a reference to the pixl-server object which handled the request.
|
|
796
817
|
|
|
818
|
+
### args.id
|
|
819
|
+
|
|
820
|
+
This is an internal ID string used by the server to track and log individual requests.
|
|
821
|
+
|
|
797
822
|
## Request Filters
|
|
798
823
|
|
|
799
824
|
Filters allow you to preprocess a request, before any handlers get their hands on it. They can pass data through, manipulate it, or even interrupt and abort requests. Filters are attached to particular URIs or URI patterns, and multiple may be applied to one request, depending on your rules. They can be asynchronous, and can also pass data between one another if desired.
|
|
@@ -897,7 +922,7 @@ Here are descriptions of the data JSON properties:
|
|
|
897
922
|
| `host` | The hostname from the request URL. |
|
|
898
923
|
| `perf` | Performance metrics, see below. |
|
|
899
924
|
|
|
900
|
-
The `perf` object contains performance metrics for the request, as returned from the [pixl-perf](https://www.
|
|
925
|
+
The `perf` object contains performance metrics for the request, as returned from the [pixl-perf](https://www.github.com/jhuckaby/pixl-perf) module. It includes a `scale` property denoting that all the metrics are displayed in milliseconds (i.e. `1000`). The metrics themselves are in the `perf` object, and counters such as the number of bytes in/out are in the `counters` object.
|
|
901
926
|
|
|
902
927
|
If you only want to log *some* requests, but not all of them, you can specify a regular expression in the [http_regex_log](#http_regex_log) configuration property, which is matched against the incoming request URIs. Example:
|
|
903
928
|
|
|
@@ -1111,7 +1136,7 @@ The `recent` array is a sorted list of the last 10 completed requests (most rece
|
|
|
1111
1136
|
| `host` | String | The hostname from the request URL. |
|
|
1112
1137
|
| `ips` | Array | The array of client IPs, including proxy IPs. |
|
|
1113
1138
|
| `ua` | String | The client's `User-Agent` string. |
|
|
1114
|
-
| `perf` | Object | A [pixl-perf](https://www.
|
|
1139
|
+
| `perf` | Object | A [pixl-perf](https://www.github.com/jhuckaby/pixl-perf) performance metrics object containing stats for the request. |
|
|
1115
1140
|
|
|
1116
1141
|
If you would like more than 10 requests, set the [http_recent_requests](#http_recent_requests) configuration property to the number you want.
|
|
1117
1142
|
|
|
@@ -1126,7 +1151,7 @@ The `queue` object contains information about the request queue. This includes
|
|
|
1126
1151
|
|
|
1127
1152
|
## Including Custom Stats
|
|
1128
1153
|
|
|
1129
|
-
To include your own application-level metrics in the `getStats()` output, a [pixl-perf](https://www.
|
|
1154
|
+
To include your own application-level metrics in the `getStats()` output, a [pixl-perf](https://www.github.com/jhuckaby/pixl-perf) performance tracker is made available to your URI handler code via `args.perf`. you can call `begin()` and `end()` on this object directly, to measure your own operations:
|
|
1130
1155
|
|
|
1131
1156
|
```js
|
|
1132
1157
|
server.WebServer.addURIHandler( '/my/custom/uri', 'Custom Name', function(args, callback) {
|
|
@@ -1147,7 +1172,7 @@ server.WebServer.addURIHandler( '/my/custom/uri', 'Custom Name', function(args,
|
|
|
1147
1172
|
|
|
1148
1173
|
Please do not call `begin()` or `end()` without arguments, as that will mess up the existing performance tracking. Also, make sure you prefix your perf keys so you don't collide with the built-in ones.
|
|
1149
1174
|
|
|
1150
|
-
Alternatively, you can use your own private [pixl-perf](https://www.
|
|
1175
|
+
Alternatively, you can use your own private [pixl-perf](https://www.github.com/jhuckaby/pixl-perf) object, and then "import" it into the `args.perf` object at the very end of your handler code, just before you fire the callback. Example:
|
|
1151
1176
|
|
|
1152
1177
|
```js
|
|
1153
1178
|
my_perf.end();
|
|
@@ -1156,7 +1181,7 @@ args.perf.import( my_perf, "app_" );
|
|
|
1156
1181
|
|
|
1157
1182
|
This would import all your metrics and prefix the keys with `app_`.
|
|
1158
1183
|
|
|
1159
|
-
See the [pixl-perf](https://www.
|
|
1184
|
+
See the [pixl-perf](https://www.github.com/jhuckaby/pixl-perf) documentation for more details on how to use the tracker.
|
|
1160
1185
|
|
|
1161
1186
|
## Stats URI Handler
|
|
1162
1187
|
|
|
@@ -1293,7 +1318,7 @@ Certbot produces its own log file here: `/var/log/letsencrypt/letsencrypt.log`
|
|
|
1293
1318
|
|
|
1294
1319
|
**The MIT License (MIT)**
|
|
1295
1320
|
|
|
1296
|
-
*Copyright (c) 2015 -
|
|
1321
|
+
*Copyright (c) 2015 - 2021 Joseph Huckaby.*
|
|
1297
1322
|
|
|
1298
1323
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1299
1324
|
of this software and associated documentation files (the "Software"), to deal
|
package/lib/http.js
CHANGED
|
@@ -161,6 +161,7 @@ module.exports = class HTTP {
|
|
|
161
161
|
});
|
|
162
162
|
delete self.conns[ id ];
|
|
163
163
|
self.numConns--;
|
|
164
|
+
socket._pixl_data.aborted = true;
|
|
164
165
|
} );
|
|
165
166
|
} );
|
|
166
167
|
|
|
@@ -175,6 +176,7 @@ module.exports = class HTTP {
|
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
var err_args = {
|
|
179
|
+
id: args.id,
|
|
178
180
|
ip: socket.remoteAddress,
|
|
179
181
|
ips: args.ips,
|
|
180
182
|
state: args.state,
|
package/lib/https.js
CHANGED
|
@@ -159,6 +159,7 @@ module.exports = class HTTP2 {
|
|
|
159
159
|
});
|
|
160
160
|
delete self.conns[ id ];
|
|
161
161
|
self.numConns--;
|
|
162
|
+
socket._pixl_data.aborted = true;
|
|
162
163
|
} );
|
|
163
164
|
} );
|
|
164
165
|
|
|
@@ -173,6 +174,7 @@ module.exports = class HTTP2 {
|
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
var err_args = {
|
|
177
|
+
id: args.id,
|
|
176
178
|
ip: socket.remoteAddress,
|
|
177
179
|
ips: args.ips,
|
|
178
180
|
state: args.state,
|
package/lib/request.js
CHANGED
|
@@ -13,6 +13,8 @@ module.exports = class Request {
|
|
|
13
13
|
enqueueHTTPRequest(request, response) {
|
|
14
14
|
// enqueue request for handling as soon as concurrency limits allow
|
|
15
15
|
var args = {
|
|
16
|
+
id: this.getNextId('r'),
|
|
17
|
+
date: Date.now() / 1000,
|
|
16
18
|
request: request,
|
|
17
19
|
response: response
|
|
18
20
|
};
|
|
@@ -23,7 +25,7 @@ module.exports = class Request {
|
|
|
23
25
|
var ip = args.ip = this.getPublicIP(ips);
|
|
24
26
|
|
|
25
27
|
this.logError(503, "Server is shutting down, denying request from: " + ip,
|
|
26
|
-
{ ips: ips, uri: request.url, headers: request.headers }
|
|
28
|
+
{ id: args.id, ips: ips, uri: request.url, headers: request.headers }
|
|
27
29
|
);
|
|
28
30
|
|
|
29
31
|
args.perf = new Perf();
|
|
@@ -35,6 +37,7 @@ module.exports = class Request {
|
|
|
35
37
|
// allow special URIs to skip the line
|
|
36
38
|
if (this.queueSkipMatch && request.url.match(this.queueSkipMatch)) {
|
|
37
39
|
this.logDebug(8, "Bumping request to front of queue: " + request.url);
|
|
40
|
+
this.requests[ args.id ] = args;
|
|
38
41
|
this.queue.unshift(args);
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
@@ -45,6 +48,7 @@ module.exports = class Request {
|
|
|
45
48
|
var ip = args.ip = this.getPublicIP(ips);
|
|
46
49
|
|
|
47
50
|
this.logError(429, "Queue is maxed out (" + this.queue.running() + " active reqs), denying new request from: " + ip, {
|
|
51
|
+
id: args.id,
|
|
48
52
|
ips: ips,
|
|
49
53
|
uri: request.url,
|
|
50
54
|
headers: request.headers,
|
|
@@ -65,6 +69,7 @@ module.exports = class Request {
|
|
|
65
69
|
var ip = args.ip = this.getPublicIP(ips);
|
|
66
70
|
|
|
67
71
|
this.logError(429, "Queue is maxed out (" + this.queue.length() + " pending reqs), denying new request from: " + ip, {
|
|
72
|
+
id: args.id,
|
|
68
73
|
ips: ips,
|
|
69
74
|
uri: request.url,
|
|
70
75
|
headers: request.headers,
|
|
@@ -79,15 +84,47 @@ module.exports = class Request {
|
|
|
79
84
|
return;
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
this.requests[ args.id ] = args;
|
|
82
88
|
this.queue.push(args);
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
parseHTTPRequest(args, callback) {
|
|
86
92
|
// handle raw http request
|
|
93
|
+
// (async dequeue handler function)
|
|
87
94
|
var self = this;
|
|
88
95
|
var request = args.request;
|
|
89
96
|
var response = args.response;
|
|
90
|
-
|
|
97
|
+
|
|
98
|
+
// all requests will end up in this callback here
|
|
99
|
+
args.callback = function() {
|
|
100
|
+
if (args.timer) { clearTimeout(args.timer); delete args.timer; }
|
|
101
|
+
delete self.requests[ args.id ];
|
|
102
|
+
callback();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// add timer for request timeout
|
|
106
|
+
if (this.config.get('http_request_timeout')) {
|
|
107
|
+
args.timer = setTimeout( function() {
|
|
108
|
+
// request took too long
|
|
109
|
+
delete args.timer;
|
|
110
|
+
|
|
111
|
+
self.logError(408, "Request timed out: " + self.config.get('http_request_timeout') + " seconds", {
|
|
112
|
+
id: args.id,
|
|
113
|
+
socket: request.socket._pixl_data.id,
|
|
114
|
+
ips: args.ips,
|
|
115
|
+
url: self.getSelfURL(args.request, request.url) || request.url,
|
|
116
|
+
state: args.state
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
self.sendHTTPResponse( args,
|
|
120
|
+
"408 Request Timeout",
|
|
121
|
+
{ 'Content-Type': "text/html" },
|
|
122
|
+
"408 Request Timeout: " + self.config.get('http_request_timeout') + " seconds.\n"
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
self.deleteUploadTempFiles(args);
|
|
126
|
+
}, this.config.get('http_request_timeout') * 1000 );
|
|
127
|
+
}
|
|
91
128
|
|
|
92
129
|
// check for early abort (client error)
|
|
93
130
|
if (request.socket._pixl_data.aborted) {
|
|
@@ -102,6 +139,7 @@ module.exports = class Request {
|
|
|
102
139
|
var ip = this.getPublicIP(ips);
|
|
103
140
|
|
|
104
141
|
this.logDebug(8, "New HTTP request: " + request.method + " " + request.url + " (" + ips.join(', ') + ")", {
|
|
142
|
+
id: args.id,
|
|
105
143
|
socket: request.socket._pixl_data.id,
|
|
106
144
|
version: request.httpVersion
|
|
107
145
|
});
|
|
@@ -156,7 +194,7 @@ module.exports = class Request {
|
|
|
156
194
|
if (this.server.shut) {
|
|
157
195
|
// server is shutting down, deny new requests
|
|
158
196
|
this.logError(503, "Server is shutting down, denying request from: " + ip,
|
|
159
|
-
{ ips: ips, uri: request.url, headers: request.headers }
|
|
197
|
+
{ id: args.id, ips: ips, uri: request.url, headers: request.headers }
|
|
160
198
|
);
|
|
161
199
|
this.sendHTTPResponse( args, "503 Service Unavailable", {}, "503 Service Unavailable (server shutting down)" );
|
|
162
200
|
return;
|
|
@@ -181,12 +219,12 @@ module.exports = class Request {
|
|
|
181
219
|
|
|
182
220
|
if (content_type.match(/(multipart|urlencoded)/i) && !content_encoding) {
|
|
183
221
|
// use formidable for the heavy lifting
|
|
184
|
-
var form =
|
|
222
|
+
var form = Formidable({
|
|
185
223
|
keepExtensions: true,
|
|
186
224
|
maxFieldsSize: self.config.get('http_max_upload_size'),
|
|
187
225
|
maxFileSize: self.config.get('http_max_upload_size'),
|
|
188
|
-
|
|
189
|
-
|
|
226
|
+
uploadDir: self.config.get('http_temp_dir'),
|
|
227
|
+
allowEmptyFiles: self.config.get('http_allow_empty_files') || false
|
|
190
228
|
});
|
|
191
229
|
|
|
192
230
|
form.on('progress', function(bytesReceived, bytesExpected) {
|
|
@@ -199,15 +237,30 @@ module.exports = class Request {
|
|
|
199
237
|
form.parse(request, function(err, _fields, _files) {
|
|
200
238
|
args.perf.end('read');
|
|
201
239
|
if (err) {
|
|
202
|
-
self.logError(400, "Error processing data from: " + ip + ": " + request.url + ": " + err,
|
|
203
|
-
{ ips: ips, uri: request.url, headers: request.headers }
|
|
240
|
+
self.logError(400, "Error processing data from: " + ip + ": " + request.url + ": " + (err.message || err),
|
|
241
|
+
{ id: args.id, ips: ips, uri: request.url, headers: request.headers }
|
|
204
242
|
);
|
|
205
243
|
self.sendHTTPResponse( args, "400 Bad Request", {}, "400 Bad Request" );
|
|
206
244
|
return;
|
|
207
245
|
}
|
|
208
246
|
else {
|
|
209
247
|
args.params = _fields || {};
|
|
210
|
-
|
|
248
|
+
|
|
249
|
+
// restore original formidable v1 API for our files
|
|
250
|
+
args.files = {};
|
|
251
|
+
if (_files) {
|
|
252
|
+
for (var key in _files) {
|
|
253
|
+
var file = _files[key];
|
|
254
|
+
args.files[key] = {
|
|
255
|
+
path: file.filepath,
|
|
256
|
+
type: file.mimetype,
|
|
257
|
+
name: file.originalFilename,
|
|
258
|
+
size: file.size,
|
|
259
|
+
mtime: file.mtime || file.lastModifiedDate
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
211
264
|
self.filterHTTPRequest(args);
|
|
212
265
|
}
|
|
213
266
|
} );
|
|
@@ -230,7 +283,7 @@ module.exports = class Request {
|
|
|
230
283
|
});
|
|
231
284
|
if (total_bytes > bytesMax) {
|
|
232
285
|
self.logError(413, "Error processing data from: " + ip + ": " + request.url + ": Max data size exceeded",
|
|
233
|
-
{ ips: ips, uri: request.url, headers: request.headers }
|
|
286
|
+
{ id: args.id, ips: ips, uri: request.url, headers: request.headers }
|
|
234
287
|
);
|
|
235
288
|
request.socket.end();
|
|
236
289
|
|
|
@@ -253,7 +306,7 @@ module.exports = class Request {
|
|
|
253
306
|
}
|
|
254
307
|
catch (e) {
|
|
255
308
|
self.logError(400, "Error processing data from: " + ip + ": " + request.url + ": Failed to parse JSON: " + e,
|
|
256
|
-
{ ips: ips, uri: request.url, headers: request.headers, body: body.toString() }
|
|
309
|
+
{ id: args.id, ips: ips, uri: request.url, headers: request.headers, body: body.toString() }
|
|
257
310
|
);
|
|
258
311
|
self.sendHTTPResponse( args, "400 Bad Request", {}, "400 Bad Request" );
|
|
259
312
|
return;
|
|
@@ -300,7 +353,7 @@ module.exports = class Request {
|
|
|
300
353
|
// use async to allow filters to run in sequence
|
|
301
354
|
async.eachSeries( filters,
|
|
302
355
|
function(filter, callback) {
|
|
303
|
-
self.logDebug(8, "Invoking filter for request: " + args.request.method + ' ' + uri + ": " + filter.name);
|
|
356
|
+
self.logDebug(8, "Invoking filter for request: " + args.request.method + ' ' + uri + ": " + filter.name, { id: args.id });
|
|
304
357
|
|
|
305
358
|
args.perf.begin('filter');
|
|
306
359
|
filter.callback( args, function() {
|
|
@@ -381,7 +434,7 @@ module.exports = class Request {
|
|
|
381
434
|
}
|
|
382
435
|
|
|
383
436
|
if (handler) {
|
|
384
|
-
this.logDebug(6, "Invoking handler for request: " + args.request.method + ' ' + uri + ": " + handler.name);
|
|
437
|
+
this.logDebug(6, "Invoking handler for request: " + args.request.method + ' ' + uri + ": " + handler.name, { id: args.id });
|
|
385
438
|
|
|
386
439
|
// Check ACL here
|
|
387
440
|
if (handler.acl) {
|
|
@@ -392,6 +445,7 @@ module.exports = class Request {
|
|
|
392
445
|
else {
|
|
393
446
|
// nope
|
|
394
447
|
this.logError(403, "Forbidden: IP addresses rejected by ACL: " + args.ips.join(', '), {
|
|
448
|
+
id: args.id,
|
|
395
449
|
acl: handler.acl.toString(),
|
|
396
450
|
useragent: args.request.headers['user-agent'] || '',
|
|
397
451
|
referrer: args.request.headers['referer'] || '',
|
package/lib/response.js
CHANGED
|
@@ -28,6 +28,7 @@ module.exports = class Response {
|
|
|
28
28
|
socket_data.total_elapsed = (new Date()).getTime() - socket_data.time_start;
|
|
29
29
|
socket_data.url = this.getSelfURL(request, request.url) || request.url;
|
|
30
30
|
socket_data.ips = args.ips;
|
|
31
|
+
socket_data.req_id = args.id;
|
|
31
32
|
if (this.config.get('http_log_socket_errors')) {
|
|
32
33
|
this.logError('socket', "Socket closed unexpectedly: " + socket_data.id, socket_data);
|
|
33
34
|
}
|
|
@@ -98,7 +99,7 @@ module.exports = class Response {
|
|
|
98
99
|
|
|
99
100
|
response.on('finish', function() {
|
|
100
101
|
// response actually completed writing
|
|
101
|
-
self.logDebug(9, "Response finished writing to socket");
|
|
102
|
+
self.logDebug(9, "Response finished writing to socket", { id: args.id });
|
|
102
103
|
|
|
103
104
|
// guess number of bytes in response header, minus data payload
|
|
104
105
|
args.perf.count('bytes_out', ("HTTP " + args.http_code + " OK\r\n").length);
|
|
@@ -120,6 +121,7 @@ module.exports = class Response {
|
|
|
120
121
|
// socket closed during active response
|
|
121
122
|
if (self.config.get('http_log_socket_errors')) {
|
|
122
123
|
self.logError('socket', "Socket connection terminated unexpectedly during response", {
|
|
124
|
+
id: args.id,
|
|
123
125
|
ips: args.ips,
|
|
124
126
|
useragent: request.headers['user-agent'] || '',
|
|
125
127
|
referrer: request.headers['referer'] || '',
|
|
@@ -136,6 +138,7 @@ module.exports = class Response {
|
|
|
136
138
|
if (is_stream) {
|
|
137
139
|
body.on('error', function(err) {
|
|
138
140
|
self.logError('stream', "Stream error serving response: " + request.url + ": " + err.message, {
|
|
141
|
+
id: args.id,
|
|
139
142
|
ips: args.ips,
|
|
140
143
|
useragent: request.headers['user-agent'] || '',
|
|
141
144
|
referrer: request.headers['referer'] || '',
|
|
@@ -260,7 +263,7 @@ module.exports = class Response {
|
|
|
260
263
|
response.end();
|
|
261
264
|
}
|
|
262
265
|
}
|
|
263
|
-
this.logDebug(9, "Request complete");
|
|
266
|
+
this.logDebug(9, "Request complete", { id: args.id });
|
|
264
267
|
}
|
|
265
268
|
}
|
|
266
269
|
|
|
@@ -301,6 +304,7 @@ module.exports = class Response {
|
|
|
301
304
|
// write to access log
|
|
302
305
|
if (this.logRequests && args.request.url.match(this.regexLogRequests)) {
|
|
303
306
|
this.logTransaction( 'HTTP ' + args.http_code + ' ' + args.http_status, args.request.url, {
|
|
307
|
+
id: args.id,
|
|
304
308
|
proto: args.request.headers['ssl'] ? 'https' : socket_data.proto,
|
|
305
309
|
ips: args.ips,
|
|
306
310
|
host: args.request.headers['host'] || '',
|
|
@@ -312,6 +316,7 @@ module.exports = class Response {
|
|
|
312
316
|
// keep a list of the most recent N requests
|
|
313
317
|
if (this.keepRecentRequests) {
|
|
314
318
|
this.recent.unshift({
|
|
319
|
+
id: args.id,
|
|
315
320
|
when: (new Date()).getTime() / 1000,
|
|
316
321
|
proto: args.request.headers['ssl'] ? 'https' : socket_data.proto,
|
|
317
322
|
port: socket_data.port,
|
package/lib/static.js
CHANGED
|
@@ -164,13 +164,13 @@ module.exports = class Static {
|
|
|
164
164
|
parseByteRange(req, stat) {
|
|
165
165
|
// parse byte range header from request
|
|
166
166
|
// Example header: Range: bytes=31-49
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
const byteRange = {
|
|
168
|
+
from: 0,
|
|
169
|
+
to: 0
|
|
170
|
+
}
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
let rangeHeader = req.headers['range'];
|
|
173
|
+
const flavor = 'bytes=';
|
|
174
174
|
|
|
175
175
|
if (rangeHeader && rangeHeader.startsWith(flavor) && !rangeHeader.includes(',')) {
|
|
176
176
|
// Parse
|
|
@@ -193,7 +193,7 @@ module.exports = class Static {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
198
|
|
|
199
199
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixl-server-web",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.3",
|
|
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",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"pixl-perf": "^1.0.0",
|
|
25
25
|
"pixl-acl": "^1.0.1",
|
|
26
26
|
"class-plus": "^1.0.0",
|
|
27
|
-
"formidable": "
|
|
27
|
+
"formidable": "2.0.1",
|
|
28
28
|
"errno": "0.1.7",
|
|
29
29
|
"stream-meter": "1.0.4",
|
|
30
30
|
"async": "3.2.0",
|
package/test/test.js
CHANGED
|
@@ -186,6 +186,20 @@ module.exports = {
|
|
|
186
186
|
);
|
|
187
187
|
},
|
|
188
188
|
|
|
189
|
+
function testBadRequest(test) {
|
|
190
|
+
// test bad HTTP GET request to webserver backend
|
|
191
|
+
// this still resolves to the root dir index due to the ../
|
|
192
|
+
request.get( 'http://127.0.0.1:3020/%0ASet-Cookie%3Acrlfinjection/../',
|
|
193
|
+
function(err, resp, data, perf) {
|
|
194
|
+
test.ok( !err, "No error from PixlRequest: " + err );
|
|
195
|
+
test.ok( !!resp, "Got resp from PixlRequest" );
|
|
196
|
+
test.ok( resp.statusCode == 200, "Got 200 response: " + resp.statusCode );
|
|
197
|
+
test.ok( resp.headers['via'] == "WebServerTest 1.0", "Correct Via header: " + resp.headers['via'] );
|
|
198
|
+
test.done();
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
},
|
|
202
|
+
|
|
189
203
|
// query string
|
|
190
204
|
function testQueryString(test) {
|
|
191
205
|
// test simple HTTP GET request with query string
|
|
@@ -707,6 +721,22 @@ module.exports = {
|
|
|
707
721
|
);
|
|
708
722
|
},
|
|
709
723
|
|
|
724
|
+
// Error (Back-end Timeout)
|
|
725
|
+
function testBackEndTimeout(test) {
|
|
726
|
+
var self = this;
|
|
727
|
+
var web = this.web_server;
|
|
728
|
+
web.config.set('http_request_timeout', 0.5); // 500ms
|
|
729
|
+
|
|
730
|
+
request.get( 'http://127.0.0.1:3020/sleep?ms=750', {},
|
|
731
|
+
function(err, resp, data, perf) {
|
|
732
|
+
web.config.set('http_request_timeout', 0); // reset timeout
|
|
733
|
+
test.ok( !err, "Unexpected error from PixlRequest: " + err );
|
|
734
|
+
test.ok( resp.statusCode == 408, "Unexpected HTTP response code: " + resp.statusCode );
|
|
735
|
+
test.done();
|
|
736
|
+
}
|
|
737
|
+
);
|
|
738
|
+
},
|
|
739
|
+
|
|
710
740
|
// static file get
|
|
711
741
|
// check ttl, check gzip
|
|
712
742
|
function testStaticTextRequest(test) {
|
package/web_server.js
CHANGED
|
@@ -56,7 +56,8 @@ module.exports = Class({
|
|
|
56
56
|
http_queue_skip_uri_match: false,
|
|
57
57
|
http_clean_headers: false,
|
|
58
58
|
http_log_socket_errors: true,
|
|
59
|
-
http_full_uri_match: false
|
|
59
|
+
http_full_uri_match: false,
|
|
60
|
+
http_request_timeout: 0
|
|
60
61
|
},
|
|
61
62
|
|
|
62
63
|
conns: null,
|
|
@@ -82,6 +83,7 @@ class WebServer extends Component {
|
|
|
82
83
|
|
|
83
84
|
// setup connections and handlers
|
|
84
85
|
this.conns = {};
|
|
86
|
+
this.requests = {};
|
|
85
87
|
this.uriFilters = [];
|
|
86
88
|
this.uriHandlers = [];
|
|
87
89
|
this.methodHandlers = [];
|
|
@@ -354,13 +356,31 @@ class WebServer extends Component {
|
|
|
354
356
|
if (this.http) {
|
|
355
357
|
this.logDebug(2, "Shutting down HTTP server");
|
|
356
358
|
|
|
359
|
+
for (var id in this.requests) {
|
|
360
|
+
var args = this.requests[id];
|
|
361
|
+
this.logDebug(4, "Request still active: " + args.id, {
|
|
362
|
+
id: args.id,
|
|
363
|
+
ips: args.ips,
|
|
364
|
+
uri: args.request ? args.request.url : '',
|
|
365
|
+
headers: args.request ? args.request.headers : {},
|
|
366
|
+
socket: (args.request && args.request.socket && args.request.socket._pixl_data) ? args.request.socket._pixl_data.id : '',
|
|
367
|
+
stats: args.state,
|
|
368
|
+
date: args.date,
|
|
369
|
+
age: (Date.now() / 1000) - args.date
|
|
370
|
+
});
|
|
371
|
+
if (args.callback) {
|
|
372
|
+
args.callback();
|
|
373
|
+
delete args.callback;
|
|
374
|
+
}
|
|
375
|
+
} // foreach req
|
|
376
|
+
|
|
357
377
|
for (var id in this.conns) {
|
|
358
|
-
this.logDebug(
|
|
378
|
+
this.logDebug(4, "Closing HTTP connection: " + id);
|
|
359
379
|
// this.conns[id].destroy();
|
|
360
380
|
this.conns[id].end();
|
|
361
381
|
this.conns[id].unref();
|
|
362
382
|
this.numConns--;
|
|
363
|
-
}
|
|
383
|
+
} // foreach conn
|
|
364
384
|
|
|
365
385
|
this.http.close( function() { self.logDebug(3, "HTTP server has shut down."); } );
|
|
366
386
|
|
|
@@ -369,6 +389,7 @@ class WebServer extends Component {
|
|
|
369
389
|
}
|
|
370
390
|
// delete this.http;
|
|
371
391
|
|
|
392
|
+
this.requests = {};
|
|
372
393
|
this.queue.kill();
|
|
373
394
|
}
|
|
374
395
|
|