woodland 18.2.4 → 18.2.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/LICENSE +27 -27
- package/README.md +251 -251
- package/dist/cli.cjs +59 -59
- package/dist/woodland.cjs +945 -944
- package/dist/woodland.js +921 -920
- package/package.json +77 -77
- package/tpl/autoindex.html +41 -41
package/LICENSE
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
Copyright (c)
|
|
2
|
-
All rights reserved.
|
|
3
|
-
|
|
4
|
-
Redistribution and use in source and binary forms, with or without
|
|
5
|
-
modification, are permitted provided that the following conditions are met:
|
|
6
|
-
|
|
7
|
-
* Redistributions of source code must retain the above copyright notice, this
|
|
8
|
-
list of conditions and the following disclaimer.
|
|
9
|
-
|
|
10
|
-
* Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
-
this list of conditions and the following disclaimer in the documentation
|
|
12
|
-
and/or other materials provided with the distribution.
|
|
13
|
-
|
|
14
|
-
* Neither the name of woodland nor the names of its
|
|
15
|
-
contributors may be used to endorse or promote products derived from
|
|
16
|
-
this software without specific prior written permission.
|
|
17
|
-
|
|
18
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
-
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
22
|
-
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
23
|
-
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
24
|
-
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
25
|
-
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
26
|
-
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
27
|
-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
1
|
+
Copyright (c) 2024, Jason Mulligan
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
* Neither the name of woodland nor the names of its
|
|
15
|
+
contributors may be used to endorse or promote products derived from
|
|
16
|
+
this software without specific prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
CHANGED
|
@@ -1,251 +1,251 @@
|
|
|
1
|
-
<img src="https://avoidwork.github.io/woodland/logo.svg" width="108" />
|
|
2
|
-
|
|
3
|
-
# Woodland
|
|
4
|
-
|
|
5
|
-
Lightweight HTTP framework with automatic headers. Routes can use parameter syntax, i.e. `/users/:id`, or `RegExp` syntax. Route parameters are not sanitized. If 2+ routes with parameters match a request the first route will be used to extract parameters. All HTTP methods are supported.
|
|
6
|
-
|
|
7
|
-
`CORS` (Cross Origin Resource Sharing) is automatically handled, and indicated with `cors` Boolean on the `request` Object for middleware.
|
|
8
|
-
|
|
9
|
-
Middleware arguments can be `req, res, next` or `error, req, res, next`. If no `Error` handling middleware is registered woodland will handle it.
|
|
10
|
-
|
|
11
|
-
## Using the factory
|
|
12
|
-
|
|
13
|
-
```javascript
|
|
14
|
-
import {createServer} from "node:http";
|
|
15
|
-
import {woodland} from "woodland";
|
|
16
|
-
|
|
17
|
-
const app = woodland({
|
|
18
|
-
defaultHeaders: {
|
|
19
|
-
"cache-control": "public, max-age=3600",
|
|
20
|
-
"content-type": "text/plain"
|
|
21
|
-
},
|
|
22
|
-
time: true
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
app.get("/", (req, res) => res.send("Custom greeting at '/:user', try it out!"));
|
|
26
|
-
app.get("/:user", (req, res) => res.send(`Hello ${req.params.user}!`));
|
|
27
|
-
createServer(app.route).listen(8000);
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Using the Class
|
|
31
|
-
|
|
32
|
-
```javascript
|
|
33
|
-
import {Woodland} from "woodland";
|
|
34
|
-
class MyFramework extends Woodland {};
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Testing
|
|
38
|
-
|
|
39
|
-
Woodland has 100% code coverage with its tests; the missing 0.22% is hard to reach conditions.
|
|
40
|
-
|
|
41
|
-
```console
|
|
42
|
-
--------------|---------|----------|---------|---------|-------------------------------
|
|
43
|
-
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
44
|
-
--------------|---------|----------|---------|---------|-------------------------------
|
|
45
|
-
All files | 99.78 | 76.02 | 98.57 | 100 |
|
|
46
|
-
woodland.cjs | 99.78 | 76.02 | 98.57 | 100 | ...214,254,275-285,314-328,...
|
|
47
|
-
--------------|---------|----------|---------|---------|-------------------------------
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Benchmark
|
|
51
|
-
Please benchmark `woodland` on your target hardware to understand the overhead which is expected to be <15% with etags disabled, or <25% with etags enabled. E.g. if `http` can handle 50k req/s, then `woodland` should handle 43k req/s.
|
|
52
|
-
|
|
53
|
-
1. Clone repository from [GitHub](https://github.com/avoidwork/woodland).
|
|
54
|
-
1. Install dependencies with `npm` or `yarn`.
|
|
55
|
-
1. Execute `benchmark` script with `npm` or `yarn`.
|
|
56
|
-
|
|
57
|
-
Results with node.js 20.8.0 & an Intel i9-12900HX (mobile) on Windows 11, with etags disabled.
|
|
58
|
-
|
|
59
|
-
```console
|
|
60
|
-
> node benchmark.js
|
|
61
|
-
|
|
62
|
-
http
|
|
63
|
-
┌─────────┬──────┬──────┬───────┬───────┬──────────┬──────────┬───────┐
|
|
64
|
-
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
|
|
65
|
-
├─────────┼──────┼──────┼───────┼───────┼──────────┼──────────┼───────┤
|
|
66
|
-
│ Latency │ 1 ms │ 8 ms │ 42 ms │ 47 ms │ 10.81 ms │ 10.26 ms │ 88 ms │
|
|
67
|
-
└─────────┴──────┴──────┴───────┴───────┴──────────┴──────────┴───────┘
|
|
68
|
-
┌───────────┬─────────┬─────────┬───────┬───────┬─────────┬─────────┬─────────┐
|
|
69
|
-
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
|
|
70
|
-
├───────────┼─────────┼─────────┼───────┼───────┼─────────┼─────────┼─────────┤
|
|
71
|
-
│ Req/Sec │ 75967 │ 75967 │ 88703 │ 93823 │ 88409.6 │ 4152.76 │ 75952 │
|
|
72
|
-
├───────────┼─────────┼─────────┼───────┼───────┼─────────┼─────────┼─────────┤
|
|
73
|
-
│ Bytes/Sec │ 15.4 MB │ 15.4 MB │ 18 MB │ 19 MB │ 17.9 MB │ 841 kB │ 15.4 MB │
|
|
74
|
-
└───────────┴─────────┴─────────┴───────┴───────┴─────────┴─────────┴─────────┘
|
|
75
|
-
|
|
76
|
-
woodland
|
|
77
|
-
┌─────────┬──────┬──────┬───────┬───────┬──────────┬──────────┬────────┐
|
|
78
|
-
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
|
|
79
|
-
├─────────┼──────┼──────┼───────┼───────┼──────────┼──────────┼────────┤
|
|
80
|
-
│ Latency │ 2 ms │ 9 ms │ 57 ms │ 67 ms │ 12.82 ms │ 13.04 ms │ 119 ms │
|
|
81
|
-
└─────────┴──────┴──────┴───────┴───────┴──────────┴──────────┴────────┘
|
|
82
|
-
┌───────────┬─────────┬─────────┬─────────┬─────────┬──────────┬─────────┬─────────┐
|
|
83
|
-
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
|
|
84
|
-
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
|
|
85
|
-
│ Req/Sec │ 66687 │ 66687 │ 75263 │ 76095 │ 75041.61 │ 1482.92 │ 66667 │
|
|
86
|
-
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
|
|
87
|
-
│ Bytes/Sec │ 14.1 MB │ 14.1 MB │ 15.9 MB │ 16.1 MB │ 15.8 MB │ 312 kB │ 14.1 MB │
|
|
88
|
-
└───────────┴─────────┴─────────┴─────────┴─────────┴──────────┴─────────┴─────────┘
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## API
|
|
93
|
-
### constructor ({...})
|
|
94
|
-
Returns a woodland instance. Enable directory browsing & traversal with `autoindex`. Create an automatic `x-response-time` response header with `time` & `digit`. Customize `etag` response header with `seed`.
|
|
95
|
-
|
|
96
|
-
#### Configuration
|
|
97
|
-
|
|
98
|
-
```json
|
|
99
|
-
{
|
|
100
|
-
"autoindex": false,
|
|
101
|
-
"cacheSize": 1000,
|
|
102
|
-
"cacheTTL": 300000,
|
|
103
|
-
"charset": "utf-8",
|
|
104
|
-
"corsExpose": "",
|
|
105
|
-
"defaultHeaders": {},
|
|
106
|
-
"digit": 3,
|
|
107
|
-
"etags": true,
|
|
108
|
-
"indexes": [
|
|
109
|
-
"index.htm",
|
|
110
|
-
"index.html"
|
|
111
|
-
],
|
|
112
|
-
"logging": {
|
|
113
|
-
"enabled": true,
|
|
114
|
-
"format": "%h %l %u %t \"%r\" %>s %b",
|
|
115
|
-
"level": "info"
|
|
116
|
-
},
|
|
117
|
-
"origins": [
|
|
118
|
-
"*"
|
|
119
|
-
],
|
|
120
|
-
"silent": false,
|
|
121
|
-
"time": false
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### allowed (method, uri, override = false)
|
|
126
|
-
Calls `routes()` and returns a `Boolean` to indicate if `method` is allowed for `uri`.
|
|
127
|
-
|
|
128
|
-
### allows (uri, override = false)
|
|
129
|
-
Returns a `String` for the `Allow` header. Caches value, & will update cache if `override` is `true`.
|
|
130
|
-
|
|
131
|
-
### always (path, fn)
|
|
132
|
-
Registers middleware for a route for all HTTP methods; runs first. `path` is a regular expression (as a string), and if not passed it defaults to `/.*`.
|
|
133
|
-
|
|
134
|
-
Execute `ignore(fn)` if you do not want the middleware included for calculating the `Allow` header.
|
|
135
|
-
|
|
136
|
-
### ignore (fn)
|
|
137
|
-
Ignores `fn` for calculating the return of `allows()`.
|
|
138
|
-
|
|
139
|
-
### decorate (req, res)
|
|
140
|
-
Decorates `allow, body, cors, host, ip, params, & parsed` on `req` and `error(status[, body, headers]), header(key, value), json(body[, status, headers]), locals{} & redirect(url[, perm = false])` on `res`.
|
|
141
|
-
|
|
142
|
-
### etag (...args)
|
|
143
|
-
Returns a String to be used as an etag response header value.
|
|
144
|
-
|
|
145
|
-
### list (method = "get", type = "array")
|
|
146
|
-
Returns an `Array` or `Object` of routes for the specified method.
|
|
147
|
-
|
|
148
|
-
### log (msg = "", level = "debug")
|
|
149
|
-
Logs to `stdout` or `stderr` depending on the `level`, & what the minimum log level is set to.
|
|
150
|
-
|
|
151
|
-
### onsend (req, res, body, status, headers)
|
|
152
|
-
**Override** to customize response `body`, `status`, or `headers`. Must return `[body, status, headers]`!
|
|
153
|
-
|
|
154
|
-
### route (req, res)
|
|
155
|
-
Function for `http.createServer()` or `https.createServer()`.
|
|
156
|
-
|
|
157
|
-
### routes (uri, method, override = false)
|
|
158
|
-
Returns an `Array` of middleware for the request. Caches value, & will update cache if `override` is `true`.
|
|
159
|
-
|
|
160
|
-
### serve (req, res, localFilePath, folderPath, indexes = this.indexes)
|
|
161
|
-
Serve static files on disk. Use a route parameter or remove `folderPath` from `req.parsed.pathname` to create `localFilePath`.
|
|
162
|
-
|
|
163
|
-
#### Without `autoindex`
|
|
164
|
-
```javascript
|
|
165
|
-
app.use("/files/:file", (req, res) => app.serve(req, res, req.params.file, path.join(__dirname, "files")));
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
#### With `autoindex`
|
|
169
|
-
```javascript
|
|
170
|
-
app.use("/files(/.*)?", (req, res) => app.serve(req, res, req.parsed.pathname.replace(/^\/files\/?/, ""), join(__dirname, "files")));
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### staticFiles (root = "/")
|
|
174
|
-
Serve static files on disk. Calls `this.serve()`.
|
|
175
|
-
|
|
176
|
-
### use ([path = "/.*",] ...fn[, method = "GET"])
|
|
177
|
-
Registers middleware for a route. `path` is a regular expression (as a string), and if not passed it defaults to `/.*`. See `always()` if you want the middleware to be used for all HTTP methods.
|
|
178
|
-
|
|
179
|
-
All HTTP methods are available on the prototype (partial application of the third argument), e.g. `get([path,] ...fn)` & `options([path,] ...fn)`.
|
|
180
|
-
|
|
181
|
-
## Command Line Interface (CLI)
|
|
182
|
-
When woodland is installed as a global module you can serve the contents of a folder by executing `woodland` in a shell. Optional parameters are `--ip=127.0.0.1` & `--port=8000`.
|
|
183
|
-
|
|
184
|
-
```console
|
|
185
|
-
Node.js v20.8.0
|
|
186
|
-
PS C:\Users\jason\Projects> npm install -g woodland
|
|
187
|
-
|
|
188
|
-
changed 6 packages in 1s
|
|
189
|
-
PS C:\Users\jason\Projects> woodland
|
|
190
|
-
id=woodland, hostname=localhost, ip=127.0.0.1, port=8000
|
|
191
|
-
127.0.0.1 - [7/Oct/2023:15:18:18 -0400] "GET / HTTP/1.1" 200 1327
|
|
192
|
-
127.0.0.1 - [7/Oct/2023:15:18:26 -0400] "GET /woodland/ HTTP/1.1" 200 2167
|
|
193
|
-
127.0.0.1 - [7/Oct/2023:15:18:29 -0400] "GET /woodland/dist/ HTTP/1.1" 200 913
|
|
194
|
-
127.0.0.1 - [7/Oct/2023:15:18:32 -0400] "GET /woodland/dist/woodland.js HTTP/1.1" 200 26385
|
|
195
|
-
127.0.0.1 - [7/Oct/2023:15:18:47 -0400] "GET /woodland/benchmark.js HTTP/1.1" 200 1657
|
|
196
|
-
127.0.0.1 - [7/Oct/2023:15:18:58 -0400] "GET /woodland/sample.js HTTP/1.1" 200 845
|
|
197
|
-
127.0.0.1 - [7/Oct/2023:15:19:07 -0400] "GET /woodland/sample.js HTTP/1.1" 304 0
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
## Event Handlers
|
|
201
|
-
Event Emitter syntax for the following events:
|
|
202
|
-
|
|
203
|
-
```javascript
|
|
204
|
-
app.on("connect", (req, res) => res.header("x-custom-header", "abc-def"));
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### connect (req, res)
|
|
208
|
-
Executes after the connection has been decorated, but before the middleware executes.
|
|
209
|
-
|
|
210
|
-
### error (req, res, err)
|
|
211
|
-
Executes after the response has been sent.
|
|
212
|
-
|
|
213
|
-
### finish (req, res)
|
|
214
|
-
Executes after the response has been sent.
|
|
215
|
-
|
|
216
|
-
## Helpers
|
|
217
|
-
`req` & `res` are decorated with helper functions to simplify responding.
|
|
218
|
-
|
|
219
|
-
### res.error(status[, body, headers])
|
|
220
|
-
Sends an error response.
|
|
221
|
-
|
|
222
|
-
### res.header(key, value)
|
|
223
|
-
Shorthand of `res.setHeader()`.
|
|
224
|
-
|
|
225
|
-
### res.json(body, [status = 200, headers])
|
|
226
|
-
Sends a JSON response.
|
|
227
|
-
|
|
228
|
-
### res.last(req, res, next)
|
|
229
|
-
Last middleware of the route for the HTTP method as a way to "skip" to the middleware which sends a response.
|
|
230
|
-
|
|
231
|
-
### res.redirect(uri[, perm = false])
|
|
232
|
-
Sends a redirection response.
|
|
233
|
-
|
|
234
|
-
### res.send(body, [status = 200, headers = {}])
|
|
235
|
-
Sends a response. `Range` header is ignored on `stream` responses.
|
|
236
|
-
|
|
237
|
-
### res.set(headers = {})
|
|
238
|
-
Shorthand of `res.setHeaders()` which accepts `Object`, `Map`, or `Headers` instances.
|
|
239
|
-
|
|
240
|
-
### res.status(arg)
|
|
241
|
-
Sets the response `statusCode` property.
|
|
242
|
-
|
|
243
|
-
## Logging
|
|
244
|
-
Woodland defaults to [Common Log Format](https://en.wikipedia.org/wiki/Common_Log_Format) but supports [Common Log Format with Virtual Host](https://httpd.apache.org/docs/trunk/mod/mod_log_config.html), & [NCSA extended/combined log format](https://httpd.apache.org/docs/trunk/mod/mod_log_config.html) with an `info` level by default. You can change the `stdout` output by changing `logging.format` with valid placeholders.
|
|
245
|
-
|
|
246
|
-
You can disable woodland's logging by configuration with `{logging: {enabled: false}}`.
|
|
247
|
-
|
|
248
|
-
## License
|
|
249
|
-
Copyright (c)
|
|
250
|
-
|
|
251
|
-
Licensed under the BSD-3 license.
|
|
1
|
+
<img src="https://avoidwork.github.io/woodland/logo.svg" width="108" />
|
|
2
|
+
|
|
3
|
+
# Woodland
|
|
4
|
+
|
|
5
|
+
Lightweight HTTP framework with automatic headers. Routes can use parameter syntax, i.e. `/users/:id`, or `RegExp` syntax. Route parameters are not sanitized. If 2+ routes with parameters match a request the first route will be used to extract parameters. All HTTP methods are supported.
|
|
6
|
+
|
|
7
|
+
`CORS` (Cross Origin Resource Sharing) is automatically handled, and indicated with `cors` Boolean on the `request` Object for middleware.
|
|
8
|
+
|
|
9
|
+
Middleware arguments can be `req, res, next` or `error, req, res, next`. If no `Error` handling middleware is registered woodland will handle it.
|
|
10
|
+
|
|
11
|
+
## Using the factory
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import {createServer} from "node:http";
|
|
15
|
+
import {woodland} from "woodland";
|
|
16
|
+
|
|
17
|
+
const app = woodland({
|
|
18
|
+
defaultHeaders: {
|
|
19
|
+
"cache-control": "public, max-age=3600",
|
|
20
|
+
"content-type": "text/plain"
|
|
21
|
+
},
|
|
22
|
+
time: true
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
app.get("/", (req, res) => res.send("Custom greeting at '/:user', try it out!"));
|
|
26
|
+
app.get("/:user", (req, res) => res.send(`Hello ${req.params.user}!`));
|
|
27
|
+
createServer(app.route).listen(8000);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Using the Class
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
import {Woodland} from "woodland";
|
|
34
|
+
class MyFramework extends Woodland {};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Testing
|
|
38
|
+
|
|
39
|
+
Woodland has 100% code coverage with its tests; the missing 0.22% is hard to reach conditions.
|
|
40
|
+
|
|
41
|
+
```console
|
|
42
|
+
--------------|---------|----------|---------|---------|-------------------------------
|
|
43
|
+
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
44
|
+
--------------|---------|----------|---------|---------|-------------------------------
|
|
45
|
+
All files | 99.78 | 76.02 | 98.57 | 100 |
|
|
46
|
+
woodland.cjs | 99.78 | 76.02 | 98.57 | 100 | ...214,254,275-285,314-328,...
|
|
47
|
+
--------------|---------|----------|---------|---------|-------------------------------
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Benchmark
|
|
51
|
+
Please benchmark `woodland` on your target hardware to understand the overhead which is expected to be <15% with etags disabled, or <25% with etags enabled. E.g. if `http` can handle 50k req/s, then `woodland` should handle 43k req/s.
|
|
52
|
+
|
|
53
|
+
1. Clone repository from [GitHub](https://github.com/avoidwork/woodland).
|
|
54
|
+
1. Install dependencies with `npm` or `yarn`.
|
|
55
|
+
1. Execute `benchmark` script with `npm` or `yarn`.
|
|
56
|
+
|
|
57
|
+
Results with node.js 20.8.0 & an Intel i9-12900HX (mobile) on Windows 11, with etags disabled.
|
|
58
|
+
|
|
59
|
+
```console
|
|
60
|
+
> node benchmark.js
|
|
61
|
+
|
|
62
|
+
http
|
|
63
|
+
┌─────────┬──────┬──────┬───────┬───────┬──────────┬──────────┬───────┐
|
|
64
|
+
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
|
|
65
|
+
├─────────┼──────┼──────┼───────┼───────┼──────────┼──────────┼───────┤
|
|
66
|
+
│ Latency │ 1 ms │ 8 ms │ 42 ms │ 47 ms │ 10.81 ms │ 10.26 ms │ 88 ms │
|
|
67
|
+
└─────────┴──────┴──────┴───────┴───────┴──────────┴──────────┴───────┘
|
|
68
|
+
┌───────────┬─────────┬─────────┬───────┬───────┬─────────┬─────────┬─────────┐
|
|
69
|
+
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
|
|
70
|
+
├───────────┼─────────┼─────────┼───────┼───────┼─────────┼─────────┼─────────┤
|
|
71
|
+
│ Req/Sec │ 75967 │ 75967 │ 88703 │ 93823 │ 88409.6 │ 4152.76 │ 75952 │
|
|
72
|
+
├───────────┼─────────┼─────────┼───────┼───────┼─────────┼─────────┼─────────┤
|
|
73
|
+
│ Bytes/Sec │ 15.4 MB │ 15.4 MB │ 18 MB │ 19 MB │ 17.9 MB │ 841 kB │ 15.4 MB │
|
|
74
|
+
└───────────┴─────────┴─────────┴───────┴───────┴─────────┴─────────┴─────────┘
|
|
75
|
+
|
|
76
|
+
woodland
|
|
77
|
+
┌─────────┬──────┬──────┬───────┬───────┬──────────┬──────────┬────────┐
|
|
78
|
+
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
|
|
79
|
+
├─────────┼──────┼──────┼───────┼───────┼──────────┼──────────┼────────┤
|
|
80
|
+
│ Latency │ 2 ms │ 9 ms │ 57 ms │ 67 ms │ 12.82 ms │ 13.04 ms │ 119 ms │
|
|
81
|
+
└─────────┴──────┴──────┴───────┴───────┴──────────┴──────────┴────────┘
|
|
82
|
+
┌───────────┬─────────┬─────────┬─────────┬─────────┬──────────┬─────────┬─────────┐
|
|
83
|
+
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
|
|
84
|
+
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
|
|
85
|
+
│ Req/Sec │ 66687 │ 66687 │ 75263 │ 76095 │ 75041.61 │ 1482.92 │ 66667 │
|
|
86
|
+
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
|
|
87
|
+
│ Bytes/Sec │ 14.1 MB │ 14.1 MB │ 15.9 MB │ 16.1 MB │ 15.8 MB │ 312 kB │ 14.1 MB │
|
|
88
|
+
└───────────┴─────────┴─────────┴─────────┴─────────┴──────────┴─────────┴─────────┘
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## API
|
|
93
|
+
### constructor ({...})
|
|
94
|
+
Returns a woodland instance. Enable directory browsing & traversal with `autoindex`. Create an automatic `x-response-time` response header with `time` & `digit`. Customize `etag` response header with `seed`.
|
|
95
|
+
|
|
96
|
+
#### Configuration
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"autoindex": false,
|
|
101
|
+
"cacheSize": 1000,
|
|
102
|
+
"cacheTTL": 300000,
|
|
103
|
+
"charset": "utf-8",
|
|
104
|
+
"corsExpose": "",
|
|
105
|
+
"defaultHeaders": {},
|
|
106
|
+
"digit": 3,
|
|
107
|
+
"etags": true,
|
|
108
|
+
"indexes": [
|
|
109
|
+
"index.htm",
|
|
110
|
+
"index.html"
|
|
111
|
+
],
|
|
112
|
+
"logging": {
|
|
113
|
+
"enabled": true,
|
|
114
|
+
"format": "%h %l %u %t \"%r\" %>s %b",
|
|
115
|
+
"level": "info"
|
|
116
|
+
},
|
|
117
|
+
"origins": [
|
|
118
|
+
"*"
|
|
119
|
+
],
|
|
120
|
+
"silent": false,
|
|
121
|
+
"time": false
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### allowed (method, uri, override = false)
|
|
126
|
+
Calls `routes()` and returns a `Boolean` to indicate if `method` is allowed for `uri`.
|
|
127
|
+
|
|
128
|
+
### allows (uri, override = false)
|
|
129
|
+
Returns a `String` for the `Allow` header. Caches value, & will update cache if `override` is `true`.
|
|
130
|
+
|
|
131
|
+
### always (path, fn)
|
|
132
|
+
Registers middleware for a route for all HTTP methods; runs first. `path` is a regular expression (as a string), and if not passed it defaults to `/.*`.
|
|
133
|
+
|
|
134
|
+
Execute `ignore(fn)` if you do not want the middleware included for calculating the `Allow` header.
|
|
135
|
+
|
|
136
|
+
### ignore (fn)
|
|
137
|
+
Ignores `fn` for calculating the return of `allows()`.
|
|
138
|
+
|
|
139
|
+
### decorate (req, res)
|
|
140
|
+
Decorates `allow, body, cors, host, ip, params, & parsed` on `req` and `error(status[, body, headers]), header(key, value), json(body[, status, headers]), locals{} & redirect(url[, perm = false])` on `res`.
|
|
141
|
+
|
|
142
|
+
### etag (...args)
|
|
143
|
+
Returns a String to be used as an etag response header value.
|
|
144
|
+
|
|
145
|
+
### list (method = "get", type = "array")
|
|
146
|
+
Returns an `Array` or `Object` of routes for the specified method.
|
|
147
|
+
|
|
148
|
+
### log (msg = "", level = "debug")
|
|
149
|
+
Logs to `stdout` or `stderr` depending on the `level`, & what the minimum log level is set to.
|
|
150
|
+
|
|
151
|
+
### onsend (req, res, body, status, headers)
|
|
152
|
+
**Override** to customize response `body`, `status`, or `headers`. Must return `[body, status, headers]`!
|
|
153
|
+
|
|
154
|
+
### route (req, res)
|
|
155
|
+
Function for `http.createServer()` or `https.createServer()`.
|
|
156
|
+
|
|
157
|
+
### routes (uri, method, override = false)
|
|
158
|
+
Returns an `Array` of middleware for the request. Caches value, & will update cache if `override` is `true`.
|
|
159
|
+
|
|
160
|
+
### serve (req, res, localFilePath, folderPath, indexes = this.indexes)
|
|
161
|
+
Serve static files on disk. Use a route parameter or remove `folderPath` from `req.parsed.pathname` to create `localFilePath`.
|
|
162
|
+
|
|
163
|
+
#### Without `autoindex`
|
|
164
|
+
```javascript
|
|
165
|
+
app.use("/files/:file", (req, res) => app.serve(req, res, req.params.file, path.join(__dirname, "files")));
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### With `autoindex`
|
|
169
|
+
```javascript
|
|
170
|
+
app.use("/files(/.*)?", (req, res) => app.serve(req, res, req.parsed.pathname.replace(/^\/files\/?/, ""), join(__dirname, "files")));
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### staticFiles (root = "/")
|
|
174
|
+
Serve static files on disk. Calls `this.serve()`.
|
|
175
|
+
|
|
176
|
+
### use ([path = "/.*",] ...fn[, method = "GET"])
|
|
177
|
+
Registers middleware for a route. `path` is a regular expression (as a string), and if not passed it defaults to `/.*`. See `always()` if you want the middleware to be used for all HTTP methods.
|
|
178
|
+
|
|
179
|
+
All HTTP methods are available on the prototype (partial application of the third argument), e.g. `get([path,] ...fn)` & `options([path,] ...fn)`.
|
|
180
|
+
|
|
181
|
+
## Command Line Interface (CLI)
|
|
182
|
+
When woodland is installed as a global module you can serve the contents of a folder by executing `woodland` in a shell. Optional parameters are `--ip=127.0.0.1` & `--port=8000`.
|
|
183
|
+
|
|
184
|
+
```console
|
|
185
|
+
Node.js v20.8.0
|
|
186
|
+
PS C:\Users\jason\Projects> npm install -g woodland
|
|
187
|
+
|
|
188
|
+
changed 6 packages in 1s
|
|
189
|
+
PS C:\Users\jason\Projects> woodland
|
|
190
|
+
id=woodland, hostname=localhost, ip=127.0.0.1, port=8000
|
|
191
|
+
127.0.0.1 - [7/Oct/2023:15:18:18 -0400] "GET / HTTP/1.1" 200 1327
|
|
192
|
+
127.0.0.1 - [7/Oct/2023:15:18:26 -0400] "GET /woodland/ HTTP/1.1" 200 2167
|
|
193
|
+
127.0.0.1 - [7/Oct/2023:15:18:29 -0400] "GET /woodland/dist/ HTTP/1.1" 200 913
|
|
194
|
+
127.0.0.1 - [7/Oct/2023:15:18:32 -0400] "GET /woodland/dist/woodland.js HTTP/1.1" 200 26385
|
|
195
|
+
127.0.0.1 - [7/Oct/2023:15:18:47 -0400] "GET /woodland/benchmark.js HTTP/1.1" 200 1657
|
|
196
|
+
127.0.0.1 - [7/Oct/2023:15:18:58 -0400] "GET /woodland/sample.js HTTP/1.1" 200 845
|
|
197
|
+
127.0.0.1 - [7/Oct/2023:15:19:07 -0400] "GET /woodland/sample.js HTTP/1.1" 304 0
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Event Handlers
|
|
201
|
+
Event Emitter syntax for the following events:
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
app.on("connect", (req, res) => res.header("x-custom-header", "abc-def"));
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### connect (req, res)
|
|
208
|
+
Executes after the connection has been decorated, but before the middleware executes.
|
|
209
|
+
|
|
210
|
+
### error (req, res, err)
|
|
211
|
+
Executes after the response has been sent.
|
|
212
|
+
|
|
213
|
+
### finish (req, res)
|
|
214
|
+
Executes after the response has been sent.
|
|
215
|
+
|
|
216
|
+
## Helpers
|
|
217
|
+
`req` & `res` are decorated with helper functions to simplify responding.
|
|
218
|
+
|
|
219
|
+
### res.error(status[, body, headers])
|
|
220
|
+
Sends an error response.
|
|
221
|
+
|
|
222
|
+
### res.header(key, value)
|
|
223
|
+
Shorthand of `res.setHeader()`.
|
|
224
|
+
|
|
225
|
+
### res.json(body, [status = 200, headers])
|
|
226
|
+
Sends a JSON response.
|
|
227
|
+
|
|
228
|
+
### res.last(req, res, next)
|
|
229
|
+
Last middleware of the route for the HTTP method as a way to "skip" to the middleware which sends a response.
|
|
230
|
+
|
|
231
|
+
### res.redirect(uri[, perm = false])
|
|
232
|
+
Sends a redirection response.
|
|
233
|
+
|
|
234
|
+
### res.send(body, [status = 200, headers = {}])
|
|
235
|
+
Sends a response. `Range` header is ignored on `stream` responses.
|
|
236
|
+
|
|
237
|
+
### res.set(headers = {})
|
|
238
|
+
Shorthand of `res.setHeaders()` which accepts `Object`, `Map`, or `Headers` instances.
|
|
239
|
+
|
|
240
|
+
### res.status(arg)
|
|
241
|
+
Sets the response `statusCode` property.
|
|
242
|
+
|
|
243
|
+
## Logging
|
|
244
|
+
Woodland defaults to [Common Log Format](https://en.wikipedia.org/wiki/Common_Log_Format) but supports [Common Log Format with Virtual Host](https://httpd.apache.org/docs/trunk/mod/mod_log_config.html), & [NCSA extended/combined log format](https://httpd.apache.org/docs/trunk/mod/mod_log_config.html) with an `info` level by default. You can change the `stdout` output by changing `logging.format` with valid placeholders.
|
|
245
|
+
|
|
246
|
+
You can disable woodland's logging by configuration with `{logging: {enabled: false}}`.
|
|
247
|
+
|
|
248
|
+
## License
|
|
249
|
+
Copyright (c) 2024 Jason Mulligan
|
|
250
|
+
|
|
251
|
+
Licensed under the BSD-3 license.
|