koa-classic-server 1.1.0 → 2.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/BENCHMARKS.md +317 -0
- package/CHANGELOG.md +181 -0
- package/CREATE_RELEASE.sh +53 -0
- package/DEBUG_REPORT.md +593 -0
- package/DOCUMENTATION.md +1585 -0
- package/EXAMPLES_INDEX_OPTION.md +395 -0
- package/INDEX_OPTION_PRIORITY.md +527 -0
- package/LICENSE +21 -0
- package/OPTIMIZATION_HTTP_CACHING.md +687 -0
- package/PERFORMANCE_ANALYSIS.md +839 -0
- package/PERFORMANCE_COMPARISON.md +388 -0
- package/README.md +278 -103
- package/__tests__/index-option.test.js +447 -0
- package/__tests__/index.test.js +15 -11
- package/__tests__/performance.test.js +301 -0
- package/__tests__/publicWwwTest/cartella vuota con spazi nel nome/file con spazio nel nome .txt +1 -0
- package/__tests__/security.test.js +336 -0
- package/benchmark-results-baseline-v1.2.0.txt +354 -0
- package/benchmark-results-optimized-v2.0.0.txt +354 -0
- package/benchmark.js +239 -0
- package/demo-regex-index.js +140 -0
- package/index.cjs +386 -156
- package/jest.config.js +18 -0
- package/package.json +18 -5
- package/publish-to-npm.sh +65 -0
- package/scripts/setup-benchmark.js +178 -0
- package/test-regex-quick.js +158 -0
package/benchmark.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HTTP Load Testing Benchmark using autocannon
|
|
5
|
+
*
|
|
6
|
+
* This script performs realistic load testing to measure:
|
|
7
|
+
* - Requests per second
|
|
8
|
+
* - Latency (avg, p50, p99)
|
|
9
|
+
* - Throughput
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node benchmark.js
|
|
13
|
+
* node benchmark.js --save baseline.json
|
|
14
|
+
*
|
|
15
|
+
* Compare before/after:
|
|
16
|
+
* node benchmark.js --save before.json
|
|
17
|
+
* # ... apply optimizations ...
|
|
18
|
+
* node benchmark.js --save after.json --compare before.json
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const autocannon = require('autocannon');
|
|
22
|
+
const Koa = require('koa');
|
|
23
|
+
const koaClassicServer = require('./index.cjs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
|
|
27
|
+
const BENCHMARK_DIR = path.join(__dirname, 'benchmark-data');
|
|
28
|
+
|
|
29
|
+
// Check if benchmark data exists
|
|
30
|
+
if (!fs.existsSync(BENCHMARK_DIR)) {
|
|
31
|
+
console.error('\n❌ Benchmark data not found!');
|
|
32
|
+
console.error('Please run: node scripts/setup-benchmark.js\n');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Parse command line arguments
|
|
37
|
+
const args = process.argv.slice(2);
|
|
38
|
+
const saveFile = args.includes('--save') ? args[args.indexOf('--save') + 1] : null;
|
|
39
|
+
const compareFile = args.includes('--compare') ? args[args.indexOf('--compare') + 1] : null;
|
|
40
|
+
|
|
41
|
+
console.log('🚀 Starting koa-classic-server benchmark...\n');
|
|
42
|
+
|
|
43
|
+
// Start server
|
|
44
|
+
const app = new Koa();
|
|
45
|
+
app.use(koaClassicServer(BENCHMARK_DIR));
|
|
46
|
+
const server = app.listen(0); // Random available port
|
|
47
|
+
|
|
48
|
+
const port = server.address().port;
|
|
49
|
+
console.log(`Server running on http://localhost:${port}\n`);
|
|
50
|
+
|
|
51
|
+
// Define benchmark scenarios
|
|
52
|
+
const scenarios = [
|
|
53
|
+
{
|
|
54
|
+
name: 'Small file (1KB)',
|
|
55
|
+
url: `http://localhost:${port}/small-files/file-1.txt`,
|
|
56
|
+
duration: 10,
|
|
57
|
+
connections: 10
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'Medium file (100KB)',
|
|
61
|
+
url: `http://localhost:${port}/medium-files/file-1.txt`,
|
|
62
|
+
duration: 10,
|
|
63
|
+
connections: 10
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'Large file (1MB)',
|
|
67
|
+
url: `http://localhost:${port}/large-files/file-1.txt`,
|
|
68
|
+
duration: 10,
|
|
69
|
+
connections: 10
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'Directory listing (100 files)',
|
|
73
|
+
url: `http://localhost:${port}/small-files/`,
|
|
74
|
+
duration: 10,
|
|
75
|
+
connections: 10
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'Directory listing (1,000 files)',
|
|
79
|
+
url: `http://localhost:${port}/large-directory/`,
|
|
80
|
+
duration: 10,
|
|
81
|
+
connections: 5
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'HTML file',
|
|
85
|
+
url: `http://localhost:${port}/test.html`,
|
|
86
|
+
duration: 10,
|
|
87
|
+
connections: 10
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: '404 Not Found',
|
|
91
|
+
url: `http://localhost:${port}/does-not-exist.txt`,
|
|
92
|
+
duration: 10,
|
|
93
|
+
connections: 10
|
|
94
|
+
}
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
// Run benchmarks sequentially
|
|
98
|
+
async function runBenchmarks() {
|
|
99
|
+
const results = {};
|
|
100
|
+
|
|
101
|
+
for (const scenario of scenarios) {
|
|
102
|
+
console.log('─'.repeat(70));
|
|
103
|
+
console.log(`📊 Benchmarking: ${scenario.name}`);
|
|
104
|
+
console.log('─'.repeat(70));
|
|
105
|
+
|
|
106
|
+
const result = await runBenchmark(scenario);
|
|
107
|
+
results[scenario.name] = result;
|
|
108
|
+
|
|
109
|
+
// Wait a bit between tests
|
|
110
|
+
await sleep(2000);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function runBenchmark(scenario) {
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
const instance = autocannon({
|
|
119
|
+
url: scenario.url,
|
|
120
|
+
connections: scenario.connections,
|
|
121
|
+
duration: scenario.duration,
|
|
122
|
+
pipelining: 1,
|
|
123
|
+
}, (err, result) => {
|
|
124
|
+
if (err) {
|
|
125
|
+
reject(err);
|
|
126
|
+
} else {
|
|
127
|
+
printResults(result, scenario.name);
|
|
128
|
+
resolve(formatResults(result, scenario.name));
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
autocannon.track(instance, { renderProgressBar: true });
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function printResults(result, name) {
|
|
137
|
+
console.log(`\n✓ ${name} - Results:`);
|
|
138
|
+
console.log(` Requests/sec: ${result.requests.average.toFixed(2)}`);
|
|
139
|
+
console.log(` Latency (avg): ${result.latency.mean.toFixed(2)}ms`);
|
|
140
|
+
console.log(` Latency (p50): ${result.latency.p50.toFixed(2)}ms`);
|
|
141
|
+
console.log(` Latency (p99): ${result.latency.p99.toFixed(2)}ms`);
|
|
142
|
+
console.log(` Throughput: ${(result.throughput.average / 1024 / 1024).toFixed(2)} MB/sec`);
|
|
143
|
+
console.log(` Total requests: ${result.requests.total}`);
|
|
144
|
+
console.log('');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function formatResults(result, name) {
|
|
148
|
+
return {
|
|
149
|
+
name,
|
|
150
|
+
requestsPerSecond: result.requests.average,
|
|
151
|
+
latency: {
|
|
152
|
+
mean: result.latency.mean,
|
|
153
|
+
p50: result.latency.p50,
|
|
154
|
+
p75: result.latency.p75,
|
|
155
|
+
p90: result.latency.p90,
|
|
156
|
+
p99: result.latency.p99,
|
|
157
|
+
p999: result.latency.p999
|
|
158
|
+
},
|
|
159
|
+
throughput: result.throughput.average,
|
|
160
|
+
totalRequests: result.requests.total,
|
|
161
|
+
errors: result.errors
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function sleep(ms) {
|
|
166
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Main execution
|
|
170
|
+
runBenchmarks()
|
|
171
|
+
.then(results => {
|
|
172
|
+
console.log('='.repeat(70));
|
|
173
|
+
console.log('📋 BENCHMARK SUMMARY');
|
|
174
|
+
console.log('='.repeat(70));
|
|
175
|
+
console.log('');
|
|
176
|
+
|
|
177
|
+
// Summary table
|
|
178
|
+
console.log('Scenario | Req/sec | Latency (avg) | Throughput');
|
|
179
|
+
console.log('----------------------------------|---------|---------------|-------------');
|
|
180
|
+
|
|
181
|
+
Object.values(results).forEach(result => {
|
|
182
|
+
const name = result.name.padEnd(33);
|
|
183
|
+
const reqSec = result.requestsPerSecond.toFixed(0).padStart(7);
|
|
184
|
+
const latency = `${result.latency.mean.toFixed(2)}ms`.padStart(13);
|
|
185
|
+
const throughput = `${(result.throughput / 1024 / 1024).toFixed(2)} MB/s`.padStart(11);
|
|
186
|
+
console.log(`${name} | ${reqSec} | ${latency} | ${throughput}`);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
console.log('');
|
|
190
|
+
console.log('='.repeat(70));
|
|
191
|
+
|
|
192
|
+
// Save results if requested
|
|
193
|
+
if (saveFile) {
|
|
194
|
+
const data = {
|
|
195
|
+
timestamp: new Date().toISOString(),
|
|
196
|
+
version: require('./package.json').version,
|
|
197
|
+
results
|
|
198
|
+
};
|
|
199
|
+
fs.writeFileSync(saveFile, JSON.stringify(data, null, 2));
|
|
200
|
+
console.log(`\n✓ Results saved to: ${saveFile}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Compare with previous results if requested
|
|
204
|
+
if (compareFile && fs.existsSync(compareFile)) {
|
|
205
|
+
const previous = JSON.parse(fs.readFileSync(compareFile, 'utf8'));
|
|
206
|
+
console.log('\n📊 COMPARISON WITH PREVIOUS RESULTS');
|
|
207
|
+
console.log('='.repeat(70));
|
|
208
|
+
|
|
209
|
+
Object.keys(results).forEach(key => {
|
|
210
|
+
const current = results[key];
|
|
211
|
+
const prev = previous.results[key];
|
|
212
|
+
|
|
213
|
+
if (prev) {
|
|
214
|
+
const reqDiff = ((current.requestsPerSecond - prev.requestsPerSecond) / prev.requestsPerSecond * 100);
|
|
215
|
+
const latDiff = ((current.latency.mean - prev.latency.mean) / prev.latency.mean * 100);
|
|
216
|
+
|
|
217
|
+
console.log(`\n${key}:`);
|
|
218
|
+
console.log(` Requests/sec: ${prev.requestsPerSecond.toFixed(2)} → ${current.requestsPerSecond.toFixed(2)} (${reqDiff > 0 ? '+' : ''}${reqDiff.toFixed(1)}%)`);
|
|
219
|
+
console.log(` Latency: ${prev.latency.mean.toFixed(2)}ms → ${current.latency.mean.toFixed(2)}ms (${latDiff > 0 ? '+' : ''}${latDiff.toFixed(1)}%)`);
|
|
220
|
+
|
|
221
|
+
if (reqDiff > 10) {
|
|
222
|
+
console.log(` ✅ Significant improvement!`);
|
|
223
|
+
} else if (reqDiff < -10) {
|
|
224
|
+
console.log(` ⚠️ Performance regression!`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
console.log('\n' + '='.repeat(70));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
server.close();
|
|
233
|
+
console.log('\n✓ Benchmark complete!\n');
|
|
234
|
+
})
|
|
235
|
+
.catch(err => {
|
|
236
|
+
console.error('❌ Benchmark failed:', err);
|
|
237
|
+
server.close();
|
|
238
|
+
process.exit(1);
|
|
239
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Demo: Enhanced index option with RegExp support
|
|
5
|
+
*
|
|
6
|
+
* Questo esempio dimostra come usare RegExp nell'opzione index
|
|
7
|
+
* per matching case-insensitive e pattern flessibili
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const Koa = require('koa');
|
|
11
|
+
const koaClassicServer = require('./index.cjs');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
// Crea directory di test
|
|
16
|
+
const testDir = path.join(__dirname, 'demo-regex-test');
|
|
17
|
+
if (!fs.existsSync(testDir)) {
|
|
18
|
+
fs.mkdirSync(testDir);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Crea file di test con vari case
|
|
22
|
+
console.log('📁 Creazione file di test...\n');
|
|
23
|
+
|
|
24
|
+
const files = [
|
|
25
|
+
{ name: 'INDEX.HTML', content: '<h1 style="color: red;">Trovato INDEX.HTML (maiuscolo)</h1>' },
|
|
26
|
+
{ name: 'Index.Html', content: '<h1 style="color: blue;">Trovato Index.Html (mixed case)</h1>' },
|
|
27
|
+
{ name: 'index.htm', content: '<h1 style="color: green;">Trovato index.htm (estensione .htm)</h1>' },
|
|
28
|
+
{ name: 'default.html', content: '<h1 style="color: orange;">Trovato default.html</h1>' },
|
|
29
|
+
{ name: 'readme.txt', content: 'Questo è un file normale' }
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
files.forEach(file => {
|
|
33
|
+
const filePath = path.join(testDir, file.name);
|
|
34
|
+
fs.writeFileSync(filePath, file.content);
|
|
35
|
+
console.log(` ✓ Creato: ${file.name}`);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
console.log('\n' + '='.repeat(70));
|
|
39
|
+
console.log('🧪 TEST CONFIGURAZIONI DIVERSE\n');
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// TEST 1: Solo case-insensitive .html
|
|
43
|
+
// ============================================================================
|
|
44
|
+
console.log('TEST 1: RegExp case-insensitive /index\\.html/i');
|
|
45
|
+
console.log(' Pattern: [/index\\.html/i]');
|
|
46
|
+
console.log(' Aspettativa: Matcha INDEX.HTML o Index.Html');
|
|
47
|
+
|
|
48
|
+
const app1 = new Koa();
|
|
49
|
+
app1.use(koaClassicServer(testDir, {
|
|
50
|
+
index: [/index\.html/i],
|
|
51
|
+
showDirContents: true
|
|
52
|
+
}));
|
|
53
|
+
const server1 = app1.listen(3001);
|
|
54
|
+
console.log(' ✓ Server avviato su http://localhost:3001\n');
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// TEST 2: Multiple estensioni (.html e .htm)
|
|
58
|
+
// ============================================================================
|
|
59
|
+
console.log('TEST 2: RegExp per .html e .htm');
|
|
60
|
+
console.log(' Pattern: [/index\\.(html|htm)/i]');
|
|
61
|
+
console.log(' Aspettativa: Matcha INDEX.HTML, Index.Html, index.htm');
|
|
62
|
+
|
|
63
|
+
const app2 = new Koa();
|
|
64
|
+
app2.use(koaClassicServer(testDir, {
|
|
65
|
+
index: [/index\.(html|htm)/i],
|
|
66
|
+
showDirContents: true
|
|
67
|
+
}));
|
|
68
|
+
const server2 = app2.listen(3002);
|
|
69
|
+
console.log(' ✓ Server avviato su http://localhost:3002\n');
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// TEST 3: Priority: index.html prima, poi default.html
|
|
73
|
+
// ============================================================================
|
|
74
|
+
console.log('TEST 3: Array con priorità');
|
|
75
|
+
console.log(' Pattern: [/index\\.(html|htm)/i, /default\\.html/i]');
|
|
76
|
+
console.log(' Aspettativa: Prima cerca index.*, poi default.html');
|
|
77
|
+
|
|
78
|
+
const app3 = new Koa();
|
|
79
|
+
app3.use(koaClassicServer(testDir, {
|
|
80
|
+
index: [
|
|
81
|
+
/index\.(html|htm)/i,
|
|
82
|
+
/default\.html/i
|
|
83
|
+
],
|
|
84
|
+
showDirContents: true
|
|
85
|
+
}));
|
|
86
|
+
const server3 = app3.listen(3003);
|
|
87
|
+
console.log(' ✓ Server avviato su http://localhost:3003\n');
|
|
88
|
+
|
|
89
|
+
// ============================================================================
|
|
90
|
+
// TEST 4: Mixed (string + RegExp)
|
|
91
|
+
// ============================================================================
|
|
92
|
+
console.log('TEST 4: Mixed array (string exact + RegExp fallback)');
|
|
93
|
+
console.log(' Pattern: ["index.html", /INDEX\\.HTML/i, /default\\.html/i]');
|
|
94
|
+
console.log(' Aspettativa: Prima exact "index.html", poi case-insensitive');
|
|
95
|
+
|
|
96
|
+
const app4 = new Koa();
|
|
97
|
+
app4.use(koaClassicServer(testDir, {
|
|
98
|
+
index: [
|
|
99
|
+
'index.html', // Exact match (più veloce)
|
|
100
|
+
/INDEX\.HTML/i, // Case-insensitive fallback
|
|
101
|
+
/default\.html/i // Default fallback
|
|
102
|
+
],
|
|
103
|
+
showDirContents: true
|
|
104
|
+
}));
|
|
105
|
+
const server4 = app4.listen(3004);
|
|
106
|
+
console.log(' ✓ Server avviato su http://localhost:3004\n');
|
|
107
|
+
|
|
108
|
+
console.log('='.repeat(70));
|
|
109
|
+
console.log('\n🌐 SERVER ATTIVI:\n');
|
|
110
|
+
console.log(' 1️⃣ http://localhost:3001 - Case-insensitive /index\\.html/i');
|
|
111
|
+
console.log(' 2️⃣ http://localhost:3002 - Multi-extension /index\\.(html|htm)/i');
|
|
112
|
+
console.log(' 3️⃣ http://localhost:3003 - Priority array con fallback');
|
|
113
|
+
console.log(' 4️⃣ http://localhost:3004 - Mixed string + RegExp\n');
|
|
114
|
+
|
|
115
|
+
console.log('📝 File presenti nella directory:');
|
|
116
|
+
files.forEach(file => {
|
|
117
|
+
console.log(` - ${file.name}`);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
console.log('\n💡 Prova ad aprire i link sopra per vedere quale file viene servito!');
|
|
121
|
+
console.log(' Ogni server ha una configurazione diversa.\n');
|
|
122
|
+
console.log('⏹️ Premi Ctrl+C per fermare i server\n');
|
|
123
|
+
|
|
124
|
+
// Cleanup on exit
|
|
125
|
+
process.on('SIGINT', () => {
|
|
126
|
+
console.log('\n\n🧹 Chiusura server e pulizia...');
|
|
127
|
+
server1.close();
|
|
128
|
+
server2.close();
|
|
129
|
+
server3.close();
|
|
130
|
+
server4.close();
|
|
131
|
+
|
|
132
|
+
// Rimuovi file di test
|
|
133
|
+
files.forEach(file => {
|
|
134
|
+
fs.unlinkSync(path.join(testDir, file.name));
|
|
135
|
+
});
|
|
136
|
+
fs.rmdirSync(testDir);
|
|
137
|
+
|
|
138
|
+
console.log('✓ Cleanup completato\n');
|
|
139
|
+
process.exit(0);
|
|
140
|
+
});
|