koa-classic-server 4.2.1 → 5.0.0
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 +77 -32
- package/index.cjs +564 -150
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -6,8 +6,9 @@ traditional web server, but intentionally its own thing.
|
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/koa-classic-server)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
|
-
[]()
|
|
10
|
+
[]()
|
|
11
|
+
[]()
|
|
11
12
|
|
|
12
13
|
One rule drives every default:
|
|
13
14
|
|
|
@@ -25,7 +26,7 @@ Hardening is opt-in.
|
|
|
25
26
|
npm install koa-classic-server
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
Requires **Node ≥
|
|
29
|
+
Requires **Node ≥ 20** and **Koa ≥ 3.1.2**. (Koa 2 and Node 18 were supported through v4.x; both were dropped in v5.0.0.)
|
|
29
30
|
|
|
30
31
|
### Import
|
|
31
32
|
|
|
@@ -92,7 +93,7 @@ const ejs = require('ejs');
|
|
|
92
93
|
app.use(koaClassicServer(path.join(__dirname, 'views'), {
|
|
93
94
|
hideExtension: { ext: '.ejs' }, // GET /about → views/about.ejs ; GET /about.ejs → 301 /about
|
|
94
95
|
template: {
|
|
95
|
-
ext: ['ejs'],
|
|
96
|
+
ext: ['.ejs'], // leading dot optional since v5: '.ejs' (preferred) ≡ 'ejs'
|
|
96
97
|
// signature: (ctx, next, filePath, rawBuffer, signal)
|
|
97
98
|
render: async (ctx, next, filePath, rawBuffer, signal) => {
|
|
98
99
|
ctx.type = 'html';
|
|
@@ -134,6 +135,11 @@ app.use(koaClassicServer(root, {
|
|
|
134
135
|
app.use(koaClassicServer(root, { compression: false }));
|
|
135
136
|
```
|
|
136
137
|
|
|
138
|
+
To size compression quality and the server-side caches to your host's RAM and CPU
|
|
139
|
+
(small VPS, weak CPU, big dedicated box, behind a CDN), see the
|
|
140
|
+
**[Performance Tuning Guide](./docs/PERFORMANCE_TUNING.md)** — it includes
|
|
141
|
+
copy-paste profiles.
|
|
142
|
+
|
|
137
143
|
### Hide sensitive files
|
|
138
144
|
|
|
139
145
|
Dot-files are **served by default** (the operator's directory is the source of truth). For a
|
|
@@ -252,21 +258,32 @@ app.use(koaClassicServer(path.join(__dirname, 'public'), {
|
|
|
252
258
|
|
|
253
259
|
---
|
|
254
260
|
|
|
255
|
-
## What's new in
|
|
256
|
-
|
|
257
|
-
**
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
261
|
+
## What's new in v5
|
|
262
|
+
|
|
263
|
+
**v5.0.0** is the configuration-correctness release: config mistakes that used to
|
|
264
|
+
misbehave silently at request time now fail fast at startup, and the two extension
|
|
265
|
+
options finally share one coherent, forgiving syntax.
|
|
266
|
+
|
|
267
|
+
- **`hideExtension.redirect` is validated at startup.** Accepted values are the real
|
|
268
|
+
redirect codes — `300, 301, 302, 303, 305, 307, 308` — and anything else **throws at
|
|
269
|
+
factory time** with the valid list in the message. Until v4 a wrong value failed
|
|
270
|
+
silently in production: a non-redirect integer (`200`, `404`, `999`, …) was quietly
|
|
271
|
+
sent as `302`, and a non-integer value produced a **500 on every redirect**.
|
|
272
|
+
- **The leading dot is optional in `template.ext` and `hideExtension.ext`** — `'.ejs'`
|
|
273
|
+
and `'ejs'` are equivalent everywhere; the preferred, documented form is **`'.ejs'`**.
|
|
274
|
+
This also fixes a long-standing trap: a dotted `template.ext` entry (`['.ejs']`) used
|
|
275
|
+
to never match, so the render never ran and the **template source was served raw**.
|
|
276
|
+
It now works as intended.
|
|
277
|
+
- **`template.ext` matches by suffix** (as `hideExtension.ext` always did), so compound
|
|
278
|
+
extensions are supported: `['.html.ejs']` targets only `*.html.ejs` files. Entries
|
|
279
|
+
that cannot name a suffix (empty, `'.'`, non-string) are dropped with a one-time
|
|
280
|
+
warning.
|
|
281
|
+
- **A non-function `template.render` now warns** (one-time) instead of silently
|
|
282
|
+
disabling template rendering.
|
|
283
|
+
|
|
284
|
+
The v4 highlights — canonical trailing slash, bounded compression with streamed-output
|
|
285
|
+
caching, configurable compression quality, custom error pages — are all still here.
|
|
286
|
+
Full details in the **[changelog](./docs/CHANGELOG.md)**.
|
|
270
287
|
|
|
271
288
|
---
|
|
272
289
|
|
|
@@ -291,7 +308,10 @@ koaClassicServer(rootDir, {
|
|
|
291
308
|
urlsReserved: [], // e.g. ['/api'] — first-level, passed to next()
|
|
292
309
|
useOriginalUrl: true, // false when an upstream middleware rewrites ctx.url
|
|
293
310
|
|
|
294
|
-
hideExtension: {
|
|
311
|
+
hideExtension: { // clean URLs
|
|
312
|
+
ext: '.ejs', // suffix to hide — leading dot optional (v5), compound ('.tar.gz') ok
|
|
313
|
+
redirect: 301, // v5: must be 300|301|302|303|305|307|308, else throws at startup
|
|
314
|
+
},
|
|
295
315
|
|
|
296
316
|
hidden: { // everything visible by default
|
|
297
317
|
dotFiles: { default: 'visible', whitelist: [], blacklist: [] },
|
|
@@ -308,11 +328,27 @@ koaClassicServer(rootDir, {
|
|
|
308
328
|
minFileSize: 1024,
|
|
309
329
|
maxFileSize: 10485760, // 10 MB buffered-path cap (false = no cap)
|
|
310
330
|
mimeTypes: [], // override the compressible-type list
|
|
331
|
+
buffered: { brotliQuality: 11, gzipLevel: 9 }, // v4.3: quality when compressing once, then caching
|
|
332
|
+
streaming: { brotliQuality: 4, gzipLevel: 6 }, // v4.3: quality when compressing per request (above maxFileSize)
|
|
311
333
|
},
|
|
312
334
|
|
|
313
335
|
serverCache: { // in-memory server-side caches
|
|
314
|
-
rawFile:
|
|
315
|
-
|
|
336
|
+
rawFile: { // cache raw file buffers (skip disk reads)
|
|
337
|
+
enabled: false,
|
|
338
|
+
maxSize: 52428800, // total RAM cap, bytes (50 MB)
|
|
339
|
+
maxFileSize: 1048576, // larger files are never cached, bytes (1 MB)
|
|
340
|
+
maxAge: 0, // ms before an entry counts as stale (0 = off)
|
|
341
|
+
warnInterval: 60000, // ms between "maxSize reached" warnings (0 = always, false = never)
|
|
342
|
+
},
|
|
343
|
+
compressedFile: { // cache br/gzip responses (compress once)
|
|
344
|
+
enabled: true,
|
|
345
|
+
maxSize: 104857600, // total RAM cap, bytes (100 MB)
|
|
346
|
+
maxEntrySize: undefined, // v4.3: per-entry cap on the compressed output, bytes
|
|
347
|
+
// default: maxSize / 4; false = no per-entry cap;
|
|
348
|
+
// oversized entries are served, just never cached
|
|
349
|
+
maxAge: 0, // ms before an entry counts as stale (0 = off)
|
|
350
|
+
warnInterval: 60000, // ms between "maxSize reached" warnings (0 = always, false = never)
|
|
351
|
+
},
|
|
316
352
|
},
|
|
317
353
|
|
|
318
354
|
symlinks: 'follow', // 'follow' | 'follow-within-root' | 'deny'
|
|
@@ -325,7 +361,8 @@ koaClassicServer(rootDir, {
|
|
|
325
361
|
},
|
|
326
362
|
|
|
327
363
|
template: {
|
|
328
|
-
ext: [], // e.g. ['ejs']
|
|
364
|
+
ext: [], // e.g. ['.ejs'] — dot optional (v5); suffix match, so
|
|
365
|
+
// compound entries ('.html.ejs') target *.html.ejs only
|
|
329
366
|
renderTimeout: 30000, // ms; on timeout the request fails closed (0 = off)
|
|
330
367
|
render: async (ctx, next, filePath, rawBuffer, signal) => { /* ... */ },
|
|
331
368
|
},
|
|
@@ -351,24 +388,31 @@ are intentionally left to a reverse proxy or an upstream middleware.
|
|
|
351
388
|
|
|
352
389
|
---
|
|
353
390
|
|
|
354
|
-
## Migrating from
|
|
391
|
+
## Migrating from v4
|
|
392
|
+
|
|
393
|
+
If your `hideExtension.redirect` is already a real redirect code (or unset) and your
|
|
394
|
+
`template.ext` entries are dot-less, **v5 changes nothing** — everything below is either a
|
|
395
|
+
config bug surfacing earlier or a previously broken form starting to work.
|
|
355
396
|
|
|
356
|
-
| What |
|
|
397
|
+
| What | v4 | v5 |
|
|
357
398
|
|---|---|---|
|
|
358
|
-
| `
|
|
359
|
-
| `
|
|
360
|
-
|
|
|
361
|
-
| `
|
|
362
|
-
|
|
|
399
|
+
| `hideExtension.redirect: 200` (any non-redirect integer) | silently sent as `302` | **throws at startup** — valid: `300, 301, 302, 303, 305, 307, 308` |
|
|
400
|
+
| `hideExtension.redirect: '301'` (non-integer) | **500 on every redirect** | **throws at startup** |
|
|
401
|
+
| `template.ext: ['.ejs']` (with dot) | never matched — template source served raw | **renders** — `'.ejs'` ≡ `'ejs'`, dotted form preferred |
|
|
402
|
+
| `template.ext: ['.html.ejs']` (compound) | never matched | targets `*.html.ejs` files only |
|
|
403
|
+
| `hideExtension.ext: 'ejs'` (no dot) | worked, with a warning | works — warning gone, both forms legal |
|
|
404
|
+
| `template.render: <non-function>` | silently dropped (sources served raw) | one-time warning (startup error planned for v6) |
|
|
363
405
|
|
|
364
|
-
The
|
|
406
|
+
The v3 → v4 notes (trailing slash, `compression.maxFileSize`, strict `options` validation)
|
|
407
|
+
remain in the **[changelog](./docs/CHANGELOG.md)**.
|
|
365
408
|
|
|
366
409
|
---
|
|
367
410
|
|
|
368
411
|
## Testing
|
|
369
412
|
|
|
370
413
|
```bash
|
|
371
|
-
npm test # full suite (lints first)
|
|
414
|
+
npm test # full suite (lints first)
|
|
415
|
+
npm run test:ci # suite without benchmarks — 1239 tests
|
|
372
416
|
npm run test:security # security tests only
|
|
373
417
|
npm run test:performance # benchmarks
|
|
374
418
|
```
|
|
@@ -379,6 +423,7 @@ npm run test:performance # benchmarks
|
|
|
379
423
|
|
|
380
424
|
- **[DOCUMENTATION.md](./docs/DOCUMENTATION.md)** — full API reference
|
|
381
425
|
- **[SECURITY_HARDENING.md](./docs/SECURITY_HARDENING.md)** — hardening guide (canonical)
|
|
426
|
+
- **[PERFORMANCE_TUNING.md](./docs/PERFORMANCE_TUNING.md)** — sizing caches & compression for your host's RAM/CPU
|
|
382
427
|
- **[CHANGELOG.md](./docs/CHANGELOG.md)** — version history
|
|
383
428
|
- **[TEMPLATE_ENGINE_GUIDE.md](./docs/template-engine/TEMPLATE_ENGINE_GUIDE.md)** — EJS/Pug/Handlebars/Nunjucks
|
|
384
429
|
|