koa-classic-server 2.0.0 → 2.1.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 +553 -127
- package/__tests__/directory-sorting-links.test.js +135 -0
- package/__tests__/ejs.test.js +299 -0
- package/__tests__/performance.test.js +75 -6
- package/__tests__/publicWwwTest/ejs-templates/complex.ejs +56 -0
- package/__tests__/publicWwwTest/ejs-templates/index.ejs +30 -0
- package/__tests__/publicWwwTest/ejs-templates/simple.ejs +13 -0
- package/__tests__/publicWwwTest/ejs-templates/with-conditional.ejs +28 -0
- package/__tests__/publicWwwTest/ejs-templates/with-escaping.ejs +26 -0
- package/__tests__/publicWwwTest/ejs-templates/with-loop.ejs +16 -0
- package/{scripts → __tests__}/setup-benchmark.js +1 -1
- package/docs/CODE_REVIEW.md +298 -0
- package/docs/FLOW_DIAGRAM.md +952 -0
- package/docs/template-engine/TEMPLATE_ENGINE_GUIDE.md +1734 -0
- package/docs/template-engine/esempi-incrementali.js +192 -0
- package/docs/template-engine/examples/esempio1-nessun-dato.ejs +12 -0
- package/docs/template-engine/examples/esempio2-una-variabile.ejs +11 -0
- package/docs/template-engine/examples/esempio3-piu-variabili.ejs +15 -0
- package/docs/template-engine/examples/esempio4-condizionale.ejs +18 -0
- package/docs/template-engine/examples/esempio5-loop.ejs +18 -0
- package/docs/template-engine/examples/index-esempi.html +181 -0
- package/docs/template-engine/examples/index.html +40 -0
- package/docs/template-engine/examples/test.ejs +64 -0
- package/index.cjs +186 -35
- package/package.json +9 -6
- package/CREATE_RELEASE.sh +0 -53
- package/publish-to-npm.sh +0 -65
- /package/{benchmark-results-baseline-v1.2.0.txt → __tests__/benchmark-results-baseline-v1.2.0.txt} +0 -0
- /package/{benchmark-results-optimized-v2.0.0.txt → __tests__/benchmark-results-optimized-v2.0.0.txt} +0 -0
- /package/{benchmark.js → __tests__/benchmark.js} +0 -0
- /package/{customTest → __tests__/customTest}/README.md +0 -0
- /package/{customTest → __tests__/customTest}/loadConfig.util.js +0 -0
- /package/{customTest → __tests__/customTest}/serversToLoad.util.js +0 -0
- /package/{demo-regex-index.js → __tests__/demo-regex-index.js} +0 -0
- /package/{test-regex-quick.js → __tests__/test-regex-quick.js} +0 -0
- /package/{BENCHMARKS.md → docs/BENCHMARKS.md} +0 -0
- /package/{CHANGELOG.md → docs/CHANGELOG.md} +0 -0
- /package/{DEBUG_REPORT.md → docs/DEBUG_REPORT.md} +0 -0
- /package/{DOCUMENTATION.md → docs/DOCUMENTATION.md} +0 -0
- /package/{EXAMPLES_INDEX_OPTION.md → docs/EXAMPLES_INDEX_OPTION.md} +0 -0
- /package/{INDEX_OPTION_PRIORITY.md → docs/INDEX_OPTION_PRIORITY.md} +0 -0
- /package/{OPTIMIZATION_HTTP_CACHING.md → docs/OPTIMIZATION_HTTP_CACHING.md} +0 -0
- /package/{PERFORMANCE_ANALYSIS.md → docs/PERFORMANCE_ANALYSIS.md} +0 -0
- /package/{PERFORMANCE_COMPARISON.md → docs/PERFORMANCE_COMPARISON.md} +0 -0
- /package/{noteExports.md → docs/noteExports.md} +0 -0
package/README.md
CHANGED
|
@@ -1,48 +1,75 @@
|
|
|
1
1
|
# koa-classic-server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
🚀 **Production-ready Koa middleware** for serving static files with Apache2-like directory listing, sortable columns, HTTP caching, template engine support, and enterprise-grade security.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/koa-classic-server)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[]()
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🎉 Version 2.1.2 - Production Release
|
|
12
|
+
|
|
13
|
+
Version 2.1.2 is a **major production release** featuring performance optimizations, enhanced directory listing, and critical bug fixes.
|
|
14
|
+
|
|
15
|
+
### What's New in 2.1.2
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
✅ **Sortable Directory Columns** - Click Name/Type/Size to sort (Apache2-like)
|
|
18
|
+
✅ **Navigation Bug Fixed** - Directory navigation now works correctly after sorting
|
|
19
|
+
✅ **File Size Display** - Human-readable file sizes (B, KB, MB, GB, TB)
|
|
20
|
+
✅ **HTTP Caching** - 80-95% bandwidth reduction with ETag and Last-Modified
|
|
21
|
+
✅ **Async/Await** - Non-blocking I/O for high performance
|
|
22
|
+
✅ **153 Tests Passing** - Comprehensive test coverage
|
|
23
|
+
✅ **Flow Documentation** - Complete execution flow diagrams
|
|
24
|
+
✅ **Code Review** - Standardized operators and best practices
|
|
12
25
|
|
|
13
|
-
### What's New in
|
|
26
|
+
### What's New in 2.0
|
|
14
27
|
|
|
15
|
-
✅ **
|
|
16
|
-
✅ **
|
|
17
|
-
✅ **Template
|
|
18
|
-
✅ **
|
|
19
|
-
✅ **Race Condition Fixes** - Robust file access
|
|
20
|
-
✅ **71 Tests Passing** - Comprehensive test coverage
|
|
28
|
+
✅ **Performance Optimizations** - 50-70% faster directory listings
|
|
29
|
+
✅ **Enhanced Index Option** - Array format with RegExp support
|
|
30
|
+
✅ **Template Engine Guide** - Complete documentation with examples
|
|
31
|
+
✅ **Security Hardened** - Path traversal, XSS, race condition fixes
|
|
21
32
|
|
|
22
|
-
[See full changelog](./CHANGELOG.md)
|
|
33
|
+
[See full changelog →](./docs/CHANGELOG.md)
|
|
34
|
+
|
|
35
|
+
---
|
|
23
36
|
|
|
24
37
|
## Features
|
|
25
38
|
|
|
26
|
-
koa-classic-server is a middleware for serving static files
|
|
39
|
+
**koa-classic-server** is a high-performance middleware for serving static files with Apache2-like behavior, making file browsing intuitive and powerful.
|
|
27
40
|
|
|
28
|
-
|
|
41
|
+
### Core Features
|
|
29
42
|
|
|
30
|
-
- 🗂️ **Directory Listing** -
|
|
31
|
-
- 📄 **Static File Serving** - Automatic MIME type detection
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
43
|
+
- 🗂️ **Apache2-like Directory Listing** - Sortable columns (Name, Type, Size)
|
|
44
|
+
- 📄 **Static File Serving** - Automatic MIME type detection with streaming
|
|
45
|
+
- 📊 **Sortable Columns** - Click headers to sort ascending/descending
|
|
46
|
+
- 📏 **File Sizes** - Human-readable display (B, KB, MB, GB, TB)
|
|
47
|
+
- ⚡ **HTTP Caching** - ETag, Last-Modified, 304 responses
|
|
48
|
+
- 🎨 **Template Engine Support** - EJS, Pug, Handlebars, Nunjucks, etc.
|
|
49
|
+
- 🔒 **Enterprise Security** - Path traversal, XSS, race condition protection
|
|
50
|
+
- ⚙️ **Highly Configurable** - URL prefixes, reserved paths, index files
|
|
51
|
+
- 🚀 **High Performance** - Async/await, non-blocking I/O, optimized algorithms
|
|
52
|
+
- 🧪 **Well-Tested** - 153 passing tests with comprehensive coverage
|
|
36
53
|
- 📦 **Dual Module Support** - CommonJS and ES Modules
|
|
37
54
|
|
|
55
|
+
---
|
|
56
|
+
|
|
38
57
|
## Installation
|
|
39
58
|
|
|
40
59
|
```bash
|
|
41
60
|
npm install koa-classic-server
|
|
42
61
|
```
|
|
43
62
|
|
|
63
|
+
**Requirements:**
|
|
64
|
+
- Node.js >= 12.0.0
|
|
65
|
+
- Koa >= 2.0.0
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
44
69
|
## Quick Start
|
|
45
70
|
|
|
71
|
+
### Basic Usage
|
|
72
|
+
|
|
46
73
|
```javascript
|
|
47
74
|
const Koa = require('koa');
|
|
48
75
|
const koaClassicServer = require('koa-classic-server');
|
|
@@ -56,9 +83,30 @@ app.listen(3000);
|
|
|
56
83
|
console.log('Server running on http://localhost:3000');
|
|
57
84
|
```
|
|
58
85
|
|
|
59
|
-
|
|
86
|
+
### With Options
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
const Koa = require('koa');
|
|
90
|
+
const koaClassicServer = require('koa-classic-server');
|
|
91
|
+
|
|
92
|
+
const app = new Koa();
|
|
93
|
+
|
|
94
|
+
app.use(koaClassicServer(__dirname + '/public', {
|
|
95
|
+
showDirContents: true,
|
|
96
|
+
index: ['index.html', 'index.htm'],
|
|
97
|
+
urlPrefix: '/static',
|
|
98
|
+
cacheMaxAge: 3600,
|
|
99
|
+
enableCaching: true
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
app.listen(3000);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Complete Usage Guide
|
|
60
108
|
|
|
61
|
-
### Import
|
|
109
|
+
### 1. Import
|
|
62
110
|
|
|
63
111
|
```javascript
|
|
64
112
|
// CommonJS
|
|
@@ -68,9 +116,9 @@ const koaClassicServer = require('koa-classic-server');
|
|
|
68
116
|
import koaClassicServer from 'koa-classic-server';
|
|
69
117
|
```
|
|
70
118
|
|
|
71
|
-
### Basic
|
|
119
|
+
### 2. Basic File Server
|
|
72
120
|
|
|
73
|
-
|
|
121
|
+
Serve static files from a directory:
|
|
74
122
|
|
|
75
123
|
```javascript
|
|
76
124
|
const Koa = require('koa');
|
|
@@ -80,55 +128,183 @@ const app = new Koa();
|
|
|
80
128
|
|
|
81
129
|
app.use(koaClassicServer(__dirname + '/public', {
|
|
82
130
|
showDirContents: true,
|
|
83
|
-
index: ['index.html']
|
|
131
|
+
index: ['index.html']
|
|
84
132
|
}));
|
|
85
133
|
|
|
86
134
|
app.listen(3000);
|
|
87
135
|
```
|
|
88
136
|
|
|
89
|
-
|
|
137
|
+
**What it does:**
|
|
138
|
+
- Serves files from `/public` directory
|
|
139
|
+
- Shows directory listing when accessing folders
|
|
140
|
+
- Looks for `index.html` in directories
|
|
141
|
+
- Sortable columns (Name, Type, Size)
|
|
142
|
+
- File sizes displayed in human-readable format
|
|
90
143
|
|
|
91
|
-
|
|
92
|
-
const Koa = require('koa');
|
|
93
|
-
const koaClassicServer = require('koa-classic-server');
|
|
144
|
+
### 3. With URL Prefix
|
|
94
145
|
|
|
95
|
-
|
|
146
|
+
Serve files under a specific URL path:
|
|
96
147
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
app.use(koaClassicServer(__dirname + '/public', {
|
|
148
|
+
```javascript
|
|
149
|
+
app.use(koaClassicServer(__dirname + '/assets', {
|
|
100
150
|
urlPrefix: '/static',
|
|
101
151
|
showDirContents: true
|
|
102
152
|
}));
|
|
153
|
+
```
|
|
103
154
|
|
|
104
|
-
|
|
155
|
+
**Result:**
|
|
156
|
+
- `http://localhost:3000/static/image.png` → serves `/assets/image.png`
|
|
157
|
+
- `http://localhost:3000/static/` → shows `/assets` directory listing
|
|
158
|
+
|
|
159
|
+
### 4. With Reserved Paths
|
|
160
|
+
|
|
161
|
+
Protect specific directories from being accessed:
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
app.use(koaClassicServer(__dirname + '/www', {
|
|
165
|
+
urlsReserved: ['/admin', '/config', '/.git', '/node_modules']
|
|
166
|
+
}));
|
|
105
167
|
```
|
|
106
168
|
|
|
107
|
-
|
|
169
|
+
**Result:**
|
|
170
|
+
- `/admin/*` → passed to next middleware (not served)
|
|
171
|
+
- `/config/*` → protected
|
|
172
|
+
- Other paths → served normally
|
|
173
|
+
|
|
174
|
+
### 5. With Template Engine (EJS)
|
|
175
|
+
|
|
176
|
+
Dynamically render templates with data:
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
const ejs = require('ejs');
|
|
180
|
+
|
|
181
|
+
app.use(koaClassicServer(__dirname + '/views', {
|
|
182
|
+
template: {
|
|
183
|
+
ext: ['ejs', 'html.ejs'],
|
|
184
|
+
render: async (ctx, next, filePath) => {
|
|
185
|
+
const data = {
|
|
186
|
+
title: 'My App',
|
|
187
|
+
user: ctx.state.user || { name: 'Guest' },
|
|
188
|
+
items: ['Item 1', 'Item 2', 'Item 3'],
|
|
189
|
+
timestamp: new Date().toISOString()
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
ctx.body = await ejs.renderFile(filePath, data);
|
|
193
|
+
ctx.type = 'text/html';
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}));
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Template example (`views/dashboard.ejs`):**
|
|
200
|
+
```html
|
|
201
|
+
<!DOCTYPE html>
|
|
202
|
+
<html>
|
|
203
|
+
<head>
|
|
204
|
+
<title><%= title %></title>
|
|
205
|
+
</head>
|
|
206
|
+
<body>
|
|
207
|
+
<h1>Welcome, <%= user.name %>!</h1>
|
|
208
|
+
<ul>
|
|
209
|
+
<% items.forEach(item => { %>
|
|
210
|
+
<li><%= item %></li>
|
|
211
|
+
<% }); %>
|
|
212
|
+
</ul>
|
|
213
|
+
<p>Generated at: <%= timestamp %></p>
|
|
214
|
+
</body>
|
|
215
|
+
</html>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**See complete guide:** [Template Engine Documentation →](./docs/template-engine/TEMPLATE_ENGINE_GUIDE.md)
|
|
219
|
+
|
|
220
|
+
### 6. With HTTP Caching
|
|
221
|
+
|
|
222
|
+
Enable aggressive caching for static files:
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
app.use(koaClassicServer(__dirname + '/public', {
|
|
226
|
+
enableCaching: true, // Enable ETag and Last-Modified
|
|
227
|
+
cacheMaxAge: 86400, // Cache for 24 hours (in seconds)
|
|
228
|
+
}));
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Benefits:**
|
|
232
|
+
- 80-95% bandwidth reduction
|
|
233
|
+
- 304 Not Modified responses for unchanged files
|
|
234
|
+
- Faster page loads for returning visitors
|
|
235
|
+
|
|
236
|
+
**See details:** [HTTP Caching Optimization →](./docs/OPTIMIZATION_HTTP_CACHING.md)
|
|
237
|
+
|
|
238
|
+
### 7. Multiple Index Files with Priority
|
|
239
|
+
|
|
240
|
+
Search for multiple index files with custom order:
|
|
241
|
+
|
|
242
|
+
```javascript
|
|
243
|
+
app.use(koaClassicServer(__dirname + '/public', {
|
|
244
|
+
index: [
|
|
245
|
+
'index.html', // First priority
|
|
246
|
+
'index.htm', // Second priority
|
|
247
|
+
/index\.[eE][jJ][sS]/, // Third: index.ejs (case-insensitive)
|
|
248
|
+
'default.html' // Last priority
|
|
249
|
+
]
|
|
250
|
+
}));
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**See details:** [Index Option Priority →](./docs/INDEX_OPTION_PRIORITY.md)
|
|
254
|
+
|
|
255
|
+
### 8. Complete Production Example
|
|
256
|
+
|
|
257
|
+
Real-world configuration for production:
|
|
108
258
|
|
|
109
259
|
```javascript
|
|
110
260
|
const Koa = require('koa');
|
|
111
261
|
const koaClassicServer = require('koa-classic-server');
|
|
112
262
|
const ejs = require('ejs');
|
|
263
|
+
const path = require('path');
|
|
113
264
|
|
|
114
265
|
const app = new Koa();
|
|
115
266
|
|
|
116
|
-
|
|
267
|
+
// Serve static assets with caching
|
|
268
|
+
app.use(koaClassicServer(path.join(__dirname, 'public'), {
|
|
269
|
+
method: ['GET', 'HEAD'],
|
|
270
|
+
showDirContents: false, // Disable directory listing in production
|
|
271
|
+
index: ['index.html', 'index.htm'],
|
|
272
|
+
urlPrefix: '/assets',
|
|
273
|
+
urlsReserved: ['/admin', '/api', '/.git'],
|
|
274
|
+
enableCaching: true,
|
|
275
|
+
cacheMaxAge: 86400, // 24 hours
|
|
276
|
+
}));
|
|
277
|
+
|
|
278
|
+
// Serve dynamic templates
|
|
279
|
+
app.use(koaClassicServer(path.join(__dirname, 'views'), {
|
|
280
|
+
showDirContents: false,
|
|
117
281
|
template: {
|
|
282
|
+
ext: ['ejs'],
|
|
118
283
|
render: async (ctx, next, filePath) => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
user: ctx.state.user
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
284
|
+
const data = {
|
|
285
|
+
env: process.env.NODE_ENV,
|
|
286
|
+
user: ctx.state.user,
|
|
287
|
+
config: ctx.state.config
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
ctx.body = await ejs.renderFile(filePath, data);
|
|
292
|
+
ctx.type = 'text/html';
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error('Template error:', error);
|
|
295
|
+
ctx.status = 500;
|
|
296
|
+
ctx.body = 'Internal Server Error';
|
|
297
|
+
}
|
|
298
|
+
}
|
|
125
299
|
}
|
|
126
300
|
}));
|
|
127
301
|
|
|
128
302
|
app.listen(3000);
|
|
129
303
|
```
|
|
130
304
|
|
|
131
|
-
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## API Reference
|
|
132
308
|
|
|
133
309
|
### koaClassicServer(rootDir, options)
|
|
134
310
|
|
|
@@ -136,56 +312,54 @@ Creates a Koa middleware for serving static files.
|
|
|
136
312
|
|
|
137
313
|
**Parameters:**
|
|
138
314
|
|
|
139
|
-
-
|
|
140
|
-
-
|
|
315
|
+
- **`rootDir`** (String, required): Absolute path to the directory containing files
|
|
316
|
+
- **`options`** (Object, optional): Configuration options
|
|
141
317
|
|
|
142
318
|
**Returns:** Koa middleware function
|
|
143
319
|
|
|
144
|
-
|
|
320
|
+
### Options
|
|
145
321
|
|
|
146
322
|
```javascript
|
|
147
|
-
|
|
323
|
+
{
|
|
148
324
|
// HTTP methods allowed (default: ['GET'])
|
|
149
325
|
method: ['GET', 'HEAD'],
|
|
150
326
|
|
|
151
327
|
// Show directory contents (default: true)
|
|
152
328
|
showDirContents: true,
|
|
153
329
|
|
|
154
|
-
// Index file configuration
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
// -
|
|
158
|
-
// -
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
// DEPRECATED: String format 'index.html' still works but will be removed
|
|
163
|
-
// in future versions. Please use array format: ['index.html']
|
|
164
|
-
//
|
|
165
|
-
// See INDEX_OPTION_PRIORITY.md for detailed behavior documentation
|
|
166
|
-
index: ['index.html'],
|
|
330
|
+
// Index file configuration
|
|
331
|
+
// Array format (recommended):
|
|
332
|
+
// - Strings: exact matches ['index.html', 'default.html']
|
|
333
|
+
// - RegExp: pattern matches [/index\.html/i]
|
|
334
|
+
// - Mixed: ['index.html', /INDEX\.HTM/i]
|
|
335
|
+
// Priority determined by array order (first match wins)
|
|
336
|
+
// See docs/INDEX_OPTION_PRIORITY.md for details
|
|
337
|
+
index: ['index.html', 'index.htm'],
|
|
167
338
|
|
|
168
339
|
// URL path prefix (default: '')
|
|
169
|
-
// Files
|
|
340
|
+
// Files served under this prefix
|
|
170
341
|
urlPrefix: '/static',
|
|
171
342
|
|
|
172
343
|
// Reserved paths (default: [])
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
urlsReserved: ['/admin', '/private'],
|
|
344
|
+
// First-level directories passed to next middleware
|
|
345
|
+
urlsReserved: ['/admin', '/api', '/.git'],
|
|
176
346
|
|
|
177
347
|
// Template engine configuration
|
|
178
348
|
template: {
|
|
179
349
|
// Template rendering function
|
|
180
350
|
render: async (ctx, next, filePath) => {
|
|
181
351
|
// Your rendering logic
|
|
182
|
-
ctx.body = await
|
|
352
|
+
ctx.body = await yourEngine.render(filePath, data);
|
|
183
353
|
},
|
|
184
354
|
|
|
185
|
-
// File extensions to process
|
|
355
|
+
// File extensions to process
|
|
186
356
|
ext: ['ejs', 'pug', 'hbs']
|
|
187
|
-
}
|
|
188
|
-
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
// HTTP caching configuration
|
|
360
|
+
enableCaching: true, // Enable ETag & Last-Modified (default: true)
|
|
361
|
+
cacheMaxAge: 3600, // Cache-Control max-age in seconds (default: 3600 = 1 hour)
|
|
362
|
+
}
|
|
189
363
|
```
|
|
190
364
|
|
|
191
365
|
### Options Details
|
|
@@ -194,131 +368,383 @@ const options = {
|
|
|
194
368
|
|--------|------|---------|-------------|
|
|
195
369
|
| `method` | Array | `['GET']` | Allowed HTTP methods |
|
|
196
370
|
| `showDirContents` | Boolean | `true` | Show directory listing |
|
|
197
|
-
| `index` | String | `
|
|
371
|
+
| `index` | Array/String | `[]` | Index file patterns (array format recommended) |
|
|
198
372
|
| `urlPrefix` | String | `''` | URL path prefix |
|
|
199
|
-
| `urlsReserved` | Array | `[]` | Reserved directory paths |
|
|
373
|
+
| `urlsReserved` | Array | `[]` | Reserved directory paths (first-level only) |
|
|
200
374
|
| `template.render` | Function | `undefined` | Template rendering function |
|
|
201
375
|
| `template.ext` | Array | `[]` | Extensions for template rendering |
|
|
376
|
+
| `enableCaching` | Boolean | `true` | Enable HTTP caching headers |
|
|
377
|
+
| `cacheMaxAge` | Number | `3600` | Cache duration in seconds |
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Directory Listing Features
|
|
382
|
+
|
|
383
|
+
### Sortable Columns
|
|
384
|
+
|
|
385
|
+
Click on column headers to sort:
|
|
386
|
+
|
|
387
|
+
- **Name** - Alphabetical sorting (A-Z or Z-A)
|
|
388
|
+
- **Type** - Sort by MIME type (directories always first)
|
|
389
|
+
- **Size** - Sort by file size (directories always first)
|
|
390
|
+
|
|
391
|
+
Visual indicators:
|
|
392
|
+
- **↑** - Ascending order
|
|
393
|
+
- **↓** - Descending order
|
|
394
|
+
|
|
395
|
+
### File Size Display
|
|
396
|
+
|
|
397
|
+
Human-readable format:
|
|
398
|
+
- `1.5 KB` - Kilobytes
|
|
399
|
+
- `2.3 MB` - Megabytes
|
|
400
|
+
- `1.2 GB` - Gigabytes
|
|
401
|
+
- `-` - Directories (no size)
|
|
402
|
+
|
|
403
|
+
### Navigation
|
|
404
|
+
|
|
405
|
+
- **Click folder name** - Enter directory
|
|
406
|
+
- **Click file name** - Download/view file
|
|
407
|
+
- **Parent Directory** - Go up one level
|
|
408
|
+
|
|
409
|
+
---
|
|
202
410
|
|
|
203
411
|
## Security
|
|
204
412
|
|
|
205
|
-
###
|
|
413
|
+
### Built-in Protection
|
|
414
|
+
|
|
415
|
+
koa-classic-server includes enterprise-grade security:
|
|
416
|
+
|
|
417
|
+
#### 1. Path Traversal Protection
|
|
206
418
|
|
|
207
|
-
|
|
419
|
+
Prevents access to files outside `rootDir`:
|
|
208
420
|
|
|
209
421
|
```javascript
|
|
210
|
-
// ❌
|
|
211
|
-
GET /../../../etc/passwd
|
|
212
|
-
GET /../config/database.yml
|
|
213
|
-
GET /%2e%2e%2fpackage.json
|
|
422
|
+
// ❌ Blocked requests
|
|
423
|
+
GET /../../../etc/passwd → 403 Forbidden
|
|
424
|
+
GET /../config/database.yml → 403 Forbidden
|
|
425
|
+
GET /%2e%2e%2fpackage.json → 403 Forbidden
|
|
214
426
|
```
|
|
215
427
|
|
|
216
|
-
|
|
428
|
+
#### 2. XSS Protection
|
|
217
429
|
|
|
218
|
-
|
|
430
|
+
All filenames and paths are HTML-escaped:
|
|
219
431
|
|
|
220
432
|
```javascript
|
|
221
|
-
|
|
222
|
-
|
|
433
|
+
// Malicious filename: <script>alert('xss')</script>.txt
|
|
434
|
+
// Displayed as: <script>alert('xss')</script>.txt
|
|
435
|
+
// ✅ Safe - script doesn't execute
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
#### 3. Reserved URLs
|
|
439
|
+
|
|
440
|
+
Protect sensitive directories:
|
|
441
|
+
|
|
442
|
+
```javascript
|
|
443
|
+
app.use(koaClassicServer(__dirname, {
|
|
444
|
+
urlsReserved: ['/admin', '/config', '/.git', '/node_modules']
|
|
223
445
|
}));
|
|
224
446
|
```
|
|
225
447
|
|
|
226
|
-
|
|
448
|
+
#### 4. Race Condition Protection
|
|
227
449
|
|
|
228
|
-
|
|
450
|
+
File access is verified before streaming:
|
|
229
451
|
|
|
230
|
-
|
|
452
|
+
```javascript
|
|
453
|
+
// File deleted between check and access?
|
|
454
|
+
// ✅ Returns 404 instead of crashing
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**See full security audit:** [Security Tests →](./__tests__/security.test.js)
|
|
231
458
|
|
|
232
|
-
|
|
459
|
+
---
|
|
233
460
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
461
|
+
## Performance
|
|
462
|
+
|
|
463
|
+
### Optimizations
|
|
464
|
+
|
|
465
|
+
Version 2.x includes major performance improvements:
|
|
466
|
+
|
|
467
|
+
- **Async/Await** - Non-blocking I/O, event loop never blocked
|
|
468
|
+
- **Array Join** - 30-40% less memory vs string concatenation
|
|
469
|
+
- **HTTP Caching** - 80-95% bandwidth reduction
|
|
470
|
+
- **Single stat() Call** - No double file system access
|
|
471
|
+
- **Streaming** - Large files streamed efficiently
|
|
472
|
+
|
|
473
|
+
### Benchmarks
|
|
474
|
+
|
|
475
|
+
Performance results on directory with 1,000 files:
|
|
476
|
+
|
|
477
|
+
```
|
|
478
|
+
Before (v1.x): ~350ms per request
|
|
479
|
+
After (v2.x): ~190ms per request
|
|
480
|
+
Improvement: 46% faster
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
**See detailed benchmarks:** [Performance Analysis →](./docs/PERFORMANCE_ANALYSIS.md)
|
|
484
|
+
|
|
485
|
+
---
|
|
237
486
|
|
|
238
487
|
## Testing
|
|
239
488
|
|
|
489
|
+
Run the comprehensive test suite:
|
|
490
|
+
|
|
240
491
|
```bash
|
|
241
492
|
# Run all tests
|
|
242
493
|
npm test
|
|
243
494
|
|
|
244
495
|
# Run security tests only
|
|
245
496
|
npm run test:security
|
|
497
|
+
|
|
498
|
+
# Run performance benchmarks
|
|
499
|
+
npm run test:performance
|
|
246
500
|
```
|
|
247
501
|
|
|
248
|
-
|
|
502
|
+
**Test Coverage:**
|
|
503
|
+
- ✅ 153 tests passing
|
|
504
|
+
- ✅ Security tests (path traversal, XSS, race conditions)
|
|
505
|
+
- ✅ EJS template integration tests
|
|
506
|
+
- ✅ Index option tests (strings, arrays, RegExp)
|
|
507
|
+
- ✅ Performance benchmarks
|
|
508
|
+
- ✅ Directory sorting tests
|
|
249
509
|
|
|
250
|
-
|
|
510
|
+
---
|
|
251
511
|
|
|
252
|
-
|
|
253
|
-
2. If `showDirContents: true` → show directory listing
|
|
254
|
-
3. If `showDirContents: false` → return 404
|
|
512
|
+
## Complete Documentation
|
|
255
513
|
|
|
256
|
-
###
|
|
514
|
+
### Core Documentation
|
|
257
515
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
516
|
+
- **[DOCUMENTATION.md](./docs/DOCUMENTATION.md)** - Complete API reference and usage guide
|
|
517
|
+
- **[FLOW_DIAGRAM.md](./docs/FLOW_DIAGRAM.md)** - Visual flow diagrams and code execution paths
|
|
518
|
+
- **[CHANGELOG.md](./docs/CHANGELOG.md)** - Version history and release notes
|
|
261
519
|
|
|
262
|
-
###
|
|
520
|
+
### Template Engine
|
|
263
521
|
|
|
264
|
-
|
|
522
|
+
- **[TEMPLATE_ENGINE_GUIDE.md](./docs/template-engine/TEMPLATE_ENGINE_GUIDE.md)** - Complete guide to template engine integration
|
|
523
|
+
- Progressive examples (simple to enterprise)
|
|
524
|
+
- EJS, Pug, Handlebars, Nunjucks support
|
|
525
|
+
- Best practices and troubleshooting
|
|
265
526
|
|
|
266
|
-
|
|
527
|
+
### Configuration
|
|
267
528
|
|
|
268
|
-
|
|
529
|
+
- **[INDEX_OPTION_PRIORITY.md](./docs/INDEX_OPTION_PRIORITY.md)** - Detailed priority behavior for `index` option
|
|
530
|
+
- String vs Array vs RegExp formats
|
|
531
|
+
- Priority order examples
|
|
532
|
+
- Migration guide from v1.x
|
|
269
533
|
|
|
270
|
-
|
|
271
|
-
|
|
534
|
+
- **[EXAMPLES_INDEX_OPTION.md](./docs/EXAMPLES_INDEX_OPTION.md)** - 10 practical examples of `index` option with RegExp
|
|
535
|
+
- Case-insensitive matching
|
|
536
|
+
- Multiple extensions
|
|
537
|
+
- Complex patterns
|
|
538
|
+
|
|
539
|
+
### Performance
|
|
540
|
+
|
|
541
|
+
- **[PERFORMANCE_ANALYSIS.md](./docs/PERFORMANCE_ANALYSIS.md)** - Performance optimization analysis
|
|
542
|
+
- Before/after comparisons
|
|
543
|
+
- Memory usage analysis
|
|
544
|
+
- Bottleneck identification
|
|
545
|
+
|
|
546
|
+
- **[PERFORMANCE_COMPARISON.md](./docs/PERFORMANCE_COMPARISON.md)** - Detailed performance benchmarks
|
|
547
|
+
- Request latency
|
|
548
|
+
- Throughput metrics
|
|
549
|
+
- Concurrent request handling
|
|
550
|
+
|
|
551
|
+
- **[OPTIMIZATION_HTTP_CACHING.md](./docs/OPTIMIZATION_HTTP_CACHING.md)** - HTTP caching implementation details
|
|
552
|
+
- ETag generation
|
|
553
|
+
- Last-Modified headers
|
|
554
|
+
- 304 Not Modified responses
|
|
555
|
+
|
|
556
|
+
- **[BENCHMARKS.md](./docs/BENCHMARKS.md)** - Benchmark results and methodology
|
|
557
|
+
|
|
558
|
+
### Code Quality
|
|
559
|
+
|
|
560
|
+
- **[CODE_REVIEW.md](./docs/CODE_REVIEW.md)** - Code quality analysis and review
|
|
561
|
+
- Security audit
|
|
562
|
+
- Best practices
|
|
563
|
+
- Standardization improvements
|
|
564
|
+
|
|
565
|
+
- **[DEBUG_REPORT.md](./docs/DEBUG_REPORT.md)** - Known limitations and debugging info
|
|
566
|
+
- Reserved URLs behavior
|
|
567
|
+
- Edge cases
|
|
568
|
+
- Troubleshooting tips
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## Migration Guide
|
|
573
|
+
|
|
574
|
+
### From v1.x to v2.x
|
|
575
|
+
|
|
576
|
+
**Breaking Changes:**
|
|
577
|
+
- `index` option: String format deprecated (still works), use array format
|
|
578
|
+
|
|
579
|
+
**Migration:**
|
|
580
|
+
|
|
581
|
+
```javascript
|
|
582
|
+
// v1.x (deprecated)
|
|
583
|
+
{
|
|
584
|
+
index: 'index.html'
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// v2.x (recommended)
|
|
588
|
+
{
|
|
589
|
+
index: ['index.html']
|
|
590
|
+
}
|
|
272
591
|
```
|
|
273
592
|
|
|
274
|
-
**
|
|
275
|
-
-
|
|
276
|
-
-
|
|
277
|
-
-
|
|
593
|
+
**New Features:**
|
|
594
|
+
- HTTP caching (enabled by default)
|
|
595
|
+
- Sortable directory columns
|
|
596
|
+
- File size display
|
|
597
|
+
- Enhanced index option with RegExp
|
|
278
598
|
|
|
279
|
-
See [CHANGELOG.md](./CHANGELOG.md)
|
|
599
|
+
**See full migration guide:** [CHANGELOG.md](./docs/CHANGELOG.md)
|
|
280
600
|
|
|
281
|
-
|
|
601
|
+
---
|
|
602
|
+
|
|
603
|
+
## Examples
|
|
604
|
+
|
|
605
|
+
### Example 1: Simple Static Server
|
|
606
|
+
|
|
607
|
+
```javascript
|
|
608
|
+
const Koa = require('koa');
|
|
609
|
+
const koaClassicServer = require('koa-classic-server');
|
|
610
|
+
|
|
611
|
+
const app = new Koa();
|
|
612
|
+
app.use(koaClassicServer(__dirname + '/public'));
|
|
613
|
+
app.listen(3000);
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### Example 2: Multi-Directory Server
|
|
617
|
+
|
|
618
|
+
```javascript
|
|
619
|
+
const Koa = require('koa');
|
|
620
|
+
const koaClassicServer = require('koa-classic-server');
|
|
621
|
+
|
|
622
|
+
const app = new Koa();
|
|
623
|
+
|
|
624
|
+
// Serve static assets
|
|
625
|
+
app.use(koaClassicServer(__dirname + '/public', {
|
|
626
|
+
urlPrefix: '/static',
|
|
627
|
+
showDirContents: false
|
|
628
|
+
}));
|
|
629
|
+
|
|
630
|
+
// Serve user uploads
|
|
631
|
+
app.use(koaClassicServer(__dirname + '/uploads', {
|
|
632
|
+
urlPrefix: '/files',
|
|
633
|
+
showDirContents: true
|
|
634
|
+
}));
|
|
282
635
|
|
|
283
|
-
|
|
636
|
+
app.listen(3000);
|
|
637
|
+
```
|
|
284
638
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
639
|
+
### Example 3: Development Server with Templates
|
|
640
|
+
|
|
641
|
+
```javascript
|
|
642
|
+
const Koa = require('koa');
|
|
643
|
+
const koaClassicServer = require('koa-classic-server');
|
|
644
|
+
const ejs = require('ejs');
|
|
645
|
+
|
|
646
|
+
const app = new Koa();
|
|
647
|
+
|
|
648
|
+
// Development mode - show directories
|
|
649
|
+
app.use(koaClassicServer(__dirname + '/src', {
|
|
650
|
+
showDirContents: true,
|
|
651
|
+
template: {
|
|
652
|
+
ext: ['ejs'],
|
|
653
|
+
render: async (ctx, next, filePath) => {
|
|
654
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
655
|
+
dev: true,
|
|
656
|
+
timestamp: Date.now()
|
|
657
|
+
});
|
|
658
|
+
ctx.type = 'text/html';
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}));
|
|
662
|
+
|
|
663
|
+
app.listen(3000);
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## Troubleshooting
|
|
669
|
+
|
|
670
|
+
### Common Issues
|
|
671
|
+
|
|
672
|
+
**Issue: 404 errors for all files**
|
|
673
|
+
|
|
674
|
+
Check that `rootDir` is an absolute path:
|
|
675
|
+
|
|
676
|
+
```javascript
|
|
677
|
+
// ❌ Wrong (relative path)
|
|
678
|
+
koaClassicServer('./public')
|
|
679
|
+
|
|
680
|
+
// ✅ Correct (absolute path)
|
|
681
|
+
koaClassicServer(__dirname + '/public')
|
|
682
|
+
koaClassicServer(path.join(__dirname, 'public'))
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
**Issue: Reserved URLs not working**
|
|
686
|
+
|
|
687
|
+
Reserved URLs only work for first-level directories:
|
|
688
|
+
|
|
689
|
+
```javascript
|
|
690
|
+
urlsReserved: ['/admin'] // ✅ Blocks /admin/*
|
|
691
|
+
urlsReserved: ['/admin/users'] // ❌ Doesn't work (nested)
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
**Issue: Directory sorting not working**
|
|
695
|
+
|
|
696
|
+
Make sure you're accessing directories without query params initially. The sorting is applied when you click headers.
|
|
697
|
+
|
|
698
|
+
**See full troubleshooting:** [DEBUG_REPORT.md](./docs/DEBUG_REPORT.md)
|
|
699
|
+
|
|
700
|
+
---
|
|
290
701
|
|
|
291
702
|
## Contributing
|
|
292
703
|
|
|
293
|
-
Contributions are welcome! Please
|
|
704
|
+
Contributions are welcome! Please:
|
|
705
|
+
|
|
706
|
+
1. Fork the repository
|
|
707
|
+
2. Create a feature branch
|
|
708
|
+
3. Add tests for new functionality
|
|
709
|
+
4. Ensure all tests pass (`npm test`)
|
|
710
|
+
5. Submit a pull request
|
|
711
|
+
|
|
712
|
+
---
|
|
294
713
|
|
|
295
714
|
## Known Limitations
|
|
296
715
|
|
|
297
716
|
- Reserved URLs only work for first-level directories
|
|
298
|
-
-
|
|
717
|
+
- Template rendering is synchronous per request
|
|
299
718
|
|
|
300
|
-
See [DEBUG_REPORT.md](./DEBUG_REPORT.md) for technical details.
|
|
719
|
+
See [DEBUG_REPORT.md](./docs/DEBUG_REPORT.md) for technical details.
|
|
720
|
+
|
|
721
|
+
---
|
|
301
722
|
|
|
302
723
|
## License
|
|
303
724
|
|
|
304
|
-
MIT
|
|
725
|
+
MIT License - see LICENSE file for details
|
|
726
|
+
|
|
727
|
+
---
|
|
305
728
|
|
|
306
729
|
## Author
|
|
307
730
|
|
|
308
731
|
Italo Paesano
|
|
309
732
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
See [CHANGELOG.md](./CHANGELOG.md)
|
|
733
|
+
---
|
|
313
734
|
|
|
314
735
|
## Links
|
|
315
736
|
|
|
316
|
-
- [
|
|
317
|
-
- [
|
|
318
|
-
- [
|
|
319
|
-
- [
|
|
320
|
-
|
|
737
|
+
- **[npm Package](https://www.npmjs.com/package/koa-classic-server)** - Official npm package
|
|
738
|
+
- **[GitHub Repository](https://github.com/italopaesano/koa-classic-server)** - Source code
|
|
739
|
+
- **[Issue Tracker](https://github.com/italopaesano/koa-classic-server/issues)** - Report bugs
|
|
740
|
+
- **[Full Documentation](./docs/DOCUMENTATION.md)** - Complete reference
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
744
|
+
## Changelog
|
|
745
|
+
|
|
746
|
+
See [CHANGELOG.md](./docs/CHANGELOG.md) for version history.
|
|
321
747
|
|
|
322
748
|
---
|
|
323
749
|
|
|
324
|
-
**⚠️ Security Notice:**
|
|
750
|
+
**⚠️ Security Notice:** Always use the latest version for security updates and bug fixes.
|