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/DOCUMENTATION.md
ADDED
|
@@ -0,0 +1,1585 @@
|
|
|
1
|
+
# KOA-CLASSIC-SERVER - Documentazione Completa
|
|
2
|
+
|
|
3
|
+
## Indice
|
|
4
|
+
|
|
5
|
+
- [Descrizione del Modulo](#descrizione-del-modulo)
|
|
6
|
+
- [Installazione](#installazione)
|
|
7
|
+
- [Avvio Rapido](#avvio-rapido)
|
|
8
|
+
- [Configurazione](#configurazione)
|
|
9
|
+
- [Esempi d'Uso](#esempi-duso)
|
|
10
|
+
- [API Reference](#api-reference)
|
|
11
|
+
- [Comportamento del Middleware](#comportamento-del-middleware)
|
|
12
|
+
- [Testing](#testing)
|
|
13
|
+
- [Troubleshooting](#troubleshooting)
|
|
14
|
+
- [Problemi Noti](#problemi-noti)
|
|
15
|
+
- [Best Practices](#best-practices)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Descrizione del Modulo
|
|
20
|
+
|
|
21
|
+
### Panoramica
|
|
22
|
+
|
|
23
|
+
**koa-classic-server** è un middleware per Koa.js che emula il comportamento di Apache 2 per la gestione di file statici. Il modulo permette di servire file e directory con funzionalità avanzate di directory listing, supporto per template engine, gestione di URL riservati e configurazione flessibile.
|
|
24
|
+
|
|
25
|
+
### Caratteristiche Principali
|
|
26
|
+
|
|
27
|
+
#### 1. Servizio File Statici
|
|
28
|
+
- Serve file statici da una directory specificata
|
|
29
|
+
- Riconoscimento automatico dei MIME types
|
|
30
|
+
- Gestione corretta di encoding e content-disposition
|
|
31
|
+
- Supporto per caratteri speciali e Unicode nei nomi file
|
|
32
|
+
|
|
33
|
+
#### 2. Directory Listing
|
|
34
|
+
- Visualizzazione del contenuto delle directory in formato HTML tabellare
|
|
35
|
+
- Navigazione parent directory con link ".. Parent Directory"
|
|
36
|
+
- Indicazione chiara del tipo di risorsa (DIR, MIME type)
|
|
37
|
+
- Gestione e visualizzazione cartelle riservate
|
|
38
|
+
- Supporto link simbolici
|
|
39
|
+
|
|
40
|
+
#### 3. Supporto Template Engine
|
|
41
|
+
- Integrazione flessibile con motori di template (es. EJS, Pug, Handlebars)
|
|
42
|
+
- Rendering personalizzato per estensioni specifiche
|
|
43
|
+
- Callback configurabile per il rendering
|
|
44
|
+
- Accesso completo al contesto Koa (ctx, next)
|
|
45
|
+
|
|
46
|
+
#### 4. Gestione URL Avanzata
|
|
47
|
+
- Supporto per URL prefix (es. `/public`, `/static`, `/files`)
|
|
48
|
+
- URL riservati non accessibili da remoto
|
|
49
|
+
- Normalizzazione automatica degli URL (rimozione trailing slash)
|
|
50
|
+
- Decodifica URI corretta per spazi e caratteri speciali
|
|
51
|
+
- Gestione case-sensitive dei path
|
|
52
|
+
|
|
53
|
+
#### 5. Compatibilità Moduli
|
|
54
|
+
- Supporto CommonJS (`require`)
|
|
55
|
+
- Supporto ES Modules (`import`)
|
|
56
|
+
- Conditional exports in package.json per massima compatibilità
|
|
57
|
+
|
|
58
|
+
### Architettura
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
koa-classic-server/
|
|
62
|
+
├── index.cjs # Implementazione principale (CommonJS)
|
|
63
|
+
├── index.mjs # Wrapper ES Modules
|
|
64
|
+
├── package.json # Configurazione con conditional exports
|
|
65
|
+
├── __tests__/ # Suite di test Jest
|
|
66
|
+
│ ├── index.test.js # Test completi
|
|
67
|
+
│ └── publicWwwTest/ # Cartella di test
|
|
68
|
+
├── customTest/ # Utility per test manuali
|
|
69
|
+
│ ├── loadConfig.util.js # Script interattivo
|
|
70
|
+
│ └── serversToLoad.util.js # Configurazioni test
|
|
71
|
+
├── LICENSE # Licenza MIT
|
|
72
|
+
├── README.md # Documentazione base
|
|
73
|
+
└── DOCUMENTATION.md # Questa documentazione
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Flusso di Esecuzione
|
|
77
|
+
|
|
78
|
+
Il middleware segue questo flusso per ogni richiesta HTTP:
|
|
79
|
+
|
|
80
|
+
1. **Validazione Metodo HTTP**: Verifica che il metodo sia tra quelli ammessi
|
|
81
|
+
2. **Normalizzazione URL**: Rimuove trailing slash e normalizza il path
|
|
82
|
+
3. **Verifica URL Prefix**: Controlla che la richiesta cada sotto il prefix configurato
|
|
83
|
+
4. **Controllo URL Riservati**: Verifica che non sia una risorsa protetta
|
|
84
|
+
5. **Risoluzione Path**: Costruisce il path completo file/directory
|
|
85
|
+
6. **Esistenza Risorsa**: Verifica che file o directory esistano
|
|
86
|
+
7. **Gestione Risorsa**:
|
|
87
|
+
- **File**: Template rendering o servizio statico
|
|
88
|
+
- **Directory**: Index file o directory listing
|
|
89
|
+
|
|
90
|
+
### Dipendenze
|
|
91
|
+
|
|
92
|
+
#### Production
|
|
93
|
+
- **koa** (^2.13.4): Framework web minimale per Node.js
|
|
94
|
+
- **mime-types**: Riconoscimento automatico MIME types (dependency implicita)
|
|
95
|
+
|
|
96
|
+
#### Development
|
|
97
|
+
- **jest** (^29.7.0): Framework di testing
|
|
98
|
+
- **supertest** (^7.0.0): Testing richieste HTTP
|
|
99
|
+
- **inquirer** (^12.4.1): CLI interattiva per testing manuale
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Installazione
|
|
104
|
+
|
|
105
|
+
### Installazione via npm
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm install koa-classic-server
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Installazione via yarn
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
yarn add koa-classic-server
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Requisiti
|
|
118
|
+
|
|
119
|
+
- **Node.js**: 12.20+ (raccomandato 14+)
|
|
120
|
+
- **Koa**: 2.x
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Avvio Rapido
|
|
125
|
+
|
|
126
|
+
### Esempio Minimale
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
const Koa = require('koa');
|
|
130
|
+
const koaClassicServer = require('koa-classic-server');
|
|
131
|
+
|
|
132
|
+
const app = new Koa();
|
|
133
|
+
|
|
134
|
+
// Serve tutti i file dalla cartella "public"
|
|
135
|
+
app.use(koaClassicServer(__dirname + '/public'));
|
|
136
|
+
|
|
137
|
+
app.listen(3000);
|
|
138
|
+
console.log('Server avviato su http://localhost:3000');
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Questa configurazione base:
|
|
142
|
+
- Serve tutti i file dalla cartella `public`
|
|
143
|
+
- Mostra il contenuto delle directory
|
|
144
|
+
- Accetta solo richieste GET
|
|
145
|
+
- Nessun URL prefix o percorso riservato
|
|
146
|
+
|
|
147
|
+
### Esempio con Opzioni
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
const Koa = require('koa');
|
|
151
|
+
const koaClassicServer = require('koa-classic-server');
|
|
152
|
+
|
|
153
|
+
const app = new Koa();
|
|
154
|
+
|
|
155
|
+
app.use(koaClassicServer(__dirname + '/public', {
|
|
156
|
+
showDirContents: true,
|
|
157
|
+
index: 'index.html',
|
|
158
|
+
urlPrefix: '/static'
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
app.listen(3000);
|
|
162
|
+
console.log('Server avviato su http://localhost:3000/static');
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Configurazione
|
|
168
|
+
|
|
169
|
+
### Sintassi
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
koaClassicServer(rootDir, options)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Parametri
|
|
176
|
+
|
|
177
|
+
| Parametro | Tipo | Obbligatorio | Descrizione |
|
|
178
|
+
|-----------|------|--------------|-------------|
|
|
179
|
+
| `rootDir` | String | Sì | Path assoluto della directory da servire |
|
|
180
|
+
| `options` | Object | No | Oggetto di configurazione |
|
|
181
|
+
|
|
182
|
+
### Opzioni Disponibili
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
const options = {
|
|
186
|
+
// Metodi HTTP ammessi
|
|
187
|
+
// Default: ['GET']
|
|
188
|
+
method: ['GET', 'HEAD'],
|
|
189
|
+
|
|
190
|
+
// Mostra il contenuto delle directory
|
|
191
|
+
// Default: true
|
|
192
|
+
showDirContents: true,
|
|
193
|
+
|
|
194
|
+
// Nome del file index da caricare automaticamente nelle directory
|
|
195
|
+
// Se presente, viene caricato invece del directory listing
|
|
196
|
+
// Default: ''
|
|
197
|
+
index: 'index.html',
|
|
198
|
+
|
|
199
|
+
// Prefisso URL da rimuovere dal path
|
|
200
|
+
// Es: '/public' significa che i file sono accessibili sotto /public/
|
|
201
|
+
// Default: ''
|
|
202
|
+
urlPrefix: '/public',
|
|
203
|
+
|
|
204
|
+
// Array di percorsi riservati (non accessibili da remoto)
|
|
205
|
+
// Funziona solo per directory di primo livello
|
|
206
|
+
// Default: []
|
|
207
|
+
urlsReserved: ['/api', '/admin', '/config'],
|
|
208
|
+
|
|
209
|
+
// Configurazione template engine
|
|
210
|
+
template: {
|
|
211
|
+
// Funzione di rendering personalizzata
|
|
212
|
+
// Riceve: ctx (contesto Koa), next (middleware successivo), filePath (path file)
|
|
213
|
+
// Default: undefined
|
|
214
|
+
render: async (ctx, next, filePath) => {
|
|
215
|
+
// Implementazione custom
|
|
216
|
+
// Es: ctx.body = await ejs.renderFile(filePath, data);
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
// Array di estensioni file da processare con template.render
|
|
220
|
+
// Se un file ha una di queste estensioni, viene chiamato render()
|
|
221
|
+
// Default: []
|
|
222
|
+
ext: ['ejs', 'EJS', 'pug', 'html']
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Dettaglio Opzioni
|
|
228
|
+
|
|
229
|
+
#### `method` (Array)
|
|
230
|
+
|
|
231
|
+
Specifica i metodi HTTP accettati dal middleware. Se una richiesta usa un metodo non presente nell'array, viene passata al middleware successivo.
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
// Solo GET
|
|
235
|
+
method: ['GET']
|
|
236
|
+
|
|
237
|
+
// GET e HEAD (utile per check esistenza file)
|
|
238
|
+
method: ['GET', 'HEAD']
|
|
239
|
+
|
|
240
|
+
// Multipli metodi (uso avanzato)
|
|
241
|
+
method: ['GET', 'HEAD', 'POST']
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### `showDirContents` (Boolean)
|
|
245
|
+
|
|
246
|
+
Controlla se mostrare il contenuto delle directory.
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
// Mostra directory listing
|
|
250
|
+
showDirContents: true
|
|
251
|
+
|
|
252
|
+
// Non mostra directory (restituisce "Not Found")
|
|
253
|
+
showDirContents: false
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### `index` (String)
|
|
257
|
+
|
|
258
|
+
Nome del file da caricare automaticamente quando si accede a una directory.
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
// Carica index.html se presente
|
|
262
|
+
index: 'index.html'
|
|
263
|
+
|
|
264
|
+
// Carica default.htm se presente
|
|
265
|
+
index: 'default.htm'
|
|
266
|
+
|
|
267
|
+
// Nessun index (mostra sempre directory listing)
|
|
268
|
+
index: ''
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Comportamento:**
|
|
272
|
+
1. Utente accede a `/cartella/`
|
|
273
|
+
2. Se esiste `/cartella/index.html` → viene servito
|
|
274
|
+
3. Altrimenti → mostra directory listing (se `showDirContents: true`)
|
|
275
|
+
|
|
276
|
+
#### `urlPrefix` (String)
|
|
277
|
+
|
|
278
|
+
Prefisso URL che il middleware deve intercettare. Utile per montare il file server sotto un percorso specifico.
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
// File accessibili sotto /static
|
|
282
|
+
// Es: /static/image.png, /static/css/style.css
|
|
283
|
+
urlPrefix: '/static'
|
|
284
|
+
|
|
285
|
+
// File accessibili sotto /public
|
|
286
|
+
urlPrefix: '/public'
|
|
287
|
+
|
|
288
|
+
// Nessun prefix (root del server)
|
|
289
|
+
urlPrefix: ''
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Importante:**
|
|
293
|
+
- Il prefix viene rimosso prima di cercare il file nel filesystem
|
|
294
|
+
- Richiesta: `/static/image.png` → cerca `rootDir/image.png`
|
|
295
|
+
|
|
296
|
+
#### `urlsReserved` (Array)
|
|
297
|
+
|
|
298
|
+
Array di percorsi protetti, non accessibili da remoto. Utile per proteggere directory sensibili.
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
urlsReserved: ['/config', '/private', '/.env']
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Limitazioni:**
|
|
305
|
+
- Funziona solo per directory di primo livello
|
|
306
|
+
- Non supporta percorsi annidati
|
|
307
|
+
- Non supporta wildcard
|
|
308
|
+
|
|
309
|
+
**Esempio:**
|
|
310
|
+
```javascript
|
|
311
|
+
// ✅ Funziona
|
|
312
|
+
urlsReserved: ['/admin']
|
|
313
|
+
// Blocca: /admin, /admin/file.txt, /admin/sub/file.txt
|
|
314
|
+
|
|
315
|
+
// ❌ Non funziona come ci si aspetta
|
|
316
|
+
urlsReserved: ['/folder/subfolder']
|
|
317
|
+
// Non blocca percorsi annidati
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### `template.render` (Function)
|
|
321
|
+
|
|
322
|
+
Funzione personalizzata per il rendering di template.
|
|
323
|
+
|
|
324
|
+
**Firma:**
|
|
325
|
+
```javascript
|
|
326
|
+
async function render(ctx, next, filePath) {
|
|
327
|
+
// ctx: Contesto Koa
|
|
328
|
+
// next: Middleware successivo
|
|
329
|
+
// filePath: Path completo del file da renderizzare
|
|
330
|
+
|
|
331
|
+
// Esempio EJS
|
|
332
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
333
|
+
// Dati passati al template
|
|
334
|
+
title: 'My Page',
|
|
335
|
+
user: ctx.state.user
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
#### `template.ext` (Array)
|
|
341
|
+
|
|
342
|
+
Array di estensioni file che devono essere processate con `template.render`.
|
|
343
|
+
|
|
344
|
+
```javascript
|
|
345
|
+
// Solo file .ejs
|
|
346
|
+
ext: ['ejs']
|
|
347
|
+
|
|
348
|
+
// File .ejs e .EJS (case-sensitive)
|
|
349
|
+
ext: ['ejs', 'EJS']
|
|
350
|
+
|
|
351
|
+
// Multipli template engine
|
|
352
|
+
ext: ['ejs', 'pug', 'html', 'hbs']
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Comportamento:**
|
|
356
|
+
- Se file ha estensione in `ext` E `render` è definito → esegue rendering
|
|
357
|
+
- Altrimenti → serve file normalmente
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Esempi d'Uso
|
|
362
|
+
|
|
363
|
+
### 1. Server Base con Directory Listing
|
|
364
|
+
|
|
365
|
+
```javascript
|
|
366
|
+
const Koa = require('koa');
|
|
367
|
+
const koaClassicServer = require('koa-classic-server');
|
|
368
|
+
|
|
369
|
+
const app = new Koa();
|
|
370
|
+
|
|
371
|
+
app.use(koaClassicServer(__dirname + '/public', {
|
|
372
|
+
showDirContents: true,
|
|
373
|
+
index: 'index.html'
|
|
374
|
+
}));
|
|
375
|
+
|
|
376
|
+
app.listen(3000);
|
|
377
|
+
console.log('Server su http://localhost:3000');
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**Funzionalità:**
|
|
381
|
+
- Serve file da `public/`
|
|
382
|
+
- Mostra directory listing
|
|
383
|
+
- Carica `index.html` automaticamente se presente
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
### 2. Server con URL Prefix
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
const Koa = require('koa');
|
|
391
|
+
const koaClassicServer = require('koa-classic-server');
|
|
392
|
+
|
|
393
|
+
const app = new Koa();
|
|
394
|
+
|
|
395
|
+
// File accessibili sotto /static
|
|
396
|
+
app.use(koaClassicServer(__dirname + '/public', {
|
|
397
|
+
urlPrefix: '/static',
|
|
398
|
+
method: ['GET', 'HEAD'],
|
|
399
|
+
showDirContents: true
|
|
400
|
+
}));
|
|
401
|
+
|
|
402
|
+
// Altri middleware per route diverse
|
|
403
|
+
app.use(async (ctx) => {
|
|
404
|
+
ctx.body = 'Homepage del sito';
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
app.listen(3000);
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
**URL esempi:**
|
|
411
|
+
- `http://localhost:3000/` → Homepage
|
|
412
|
+
- `http://localhost:3000/static/` → Directory listing di public/
|
|
413
|
+
- `http://localhost:3000/static/image.png` → public/image.png
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
### 3. Server con Cartelle Protette
|
|
418
|
+
|
|
419
|
+
```javascript
|
|
420
|
+
const Koa = require('koa');
|
|
421
|
+
const koaClassicServer = require('koa-classic-server');
|
|
422
|
+
|
|
423
|
+
const app = new Koa();
|
|
424
|
+
|
|
425
|
+
app.use(koaClassicServer(__dirname + '/www', {
|
|
426
|
+
showDirContents: true,
|
|
427
|
+
// Protegge directory sensibili
|
|
428
|
+
urlsReserved: ['/config', '/private', '/.git', '/node_modules']
|
|
429
|
+
}));
|
|
430
|
+
|
|
431
|
+
app.listen(3000);
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
**Comportamento:**
|
|
435
|
+
- `/config/` → Not Found (protetto)
|
|
436
|
+
- `/private/secret.txt` → Not Found (protetto)
|
|
437
|
+
- `/public/file.txt` → Accessibile
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
### 4. Server con Template Engine (EJS)
|
|
442
|
+
|
|
443
|
+
```javascript
|
|
444
|
+
const Koa = require('koa');
|
|
445
|
+
const koaClassicServer = require('koa-classic-server');
|
|
446
|
+
const ejs = require('ejs');
|
|
447
|
+
|
|
448
|
+
const app = new Koa();
|
|
449
|
+
|
|
450
|
+
app.use(koaClassicServer(__dirname + '/views', {
|
|
451
|
+
showDirContents: false,
|
|
452
|
+
template: {
|
|
453
|
+
render: async (ctx, next, filePath) => {
|
|
454
|
+
// Rendering EJS con dati
|
|
455
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
456
|
+
title: 'My App',
|
|
457
|
+
filePath: filePath,
|
|
458
|
+
href: ctx.href,
|
|
459
|
+
query: ctx.query,
|
|
460
|
+
user: ctx.state.user || 'Guest'
|
|
461
|
+
});
|
|
462
|
+
},
|
|
463
|
+
ext: ['ejs', 'EJS']
|
|
464
|
+
}
|
|
465
|
+
}));
|
|
466
|
+
|
|
467
|
+
app.listen(3000);
|
|
468
|
+
console.log('Server con EJS su http://localhost:3000');
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
### 5. Server Multi-Directory
|
|
474
|
+
|
|
475
|
+
```javascript
|
|
476
|
+
const Koa = require('koa');
|
|
477
|
+
const koaClassicServer = require('koa-classic-server');
|
|
478
|
+
|
|
479
|
+
const app = new Koa();
|
|
480
|
+
|
|
481
|
+
// File statici pubblici
|
|
482
|
+
app.use(koaClassicServer(__dirname + '/public', {
|
|
483
|
+
urlPrefix: '/public',
|
|
484
|
+
showDirContents: true
|
|
485
|
+
}));
|
|
486
|
+
|
|
487
|
+
// Asset (CSS, JS, images)
|
|
488
|
+
app.use(koaClassicServer(__dirname + '/assets', {
|
|
489
|
+
urlPrefix: '/assets',
|
|
490
|
+
showDirContents: false
|
|
491
|
+
}));
|
|
492
|
+
|
|
493
|
+
// Download area
|
|
494
|
+
app.use(koaClassicServer(__dirname + '/downloads', {
|
|
495
|
+
urlPrefix: '/downloads',
|
|
496
|
+
showDirContents: true,
|
|
497
|
+
index: 'README.txt'
|
|
498
|
+
}));
|
|
499
|
+
|
|
500
|
+
app.listen(3000);
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
### 6. Configurazione Completa Produzione
|
|
506
|
+
|
|
507
|
+
```javascript
|
|
508
|
+
const Koa = require('koa');
|
|
509
|
+
const koaClassicServer = require('koa-classic-server');
|
|
510
|
+
const ejs = require('ejs');
|
|
511
|
+
const path = require('path');
|
|
512
|
+
|
|
513
|
+
const app = new Koa();
|
|
514
|
+
|
|
515
|
+
// Logging middleware
|
|
516
|
+
app.use(async (ctx, next) => {
|
|
517
|
+
const start = Date.now();
|
|
518
|
+
await next();
|
|
519
|
+
const ms = Date.now() - start;
|
|
520
|
+
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// Error handling
|
|
524
|
+
app.on('error', (err, ctx) => {
|
|
525
|
+
console.error('Server error:', err);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// Template render function
|
|
529
|
+
const templateRender = async (ctx, next, filePath) => {
|
|
530
|
+
try {
|
|
531
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
532
|
+
filePath: filePath,
|
|
533
|
+
href: ctx.href,
|
|
534
|
+
query: ctx.query,
|
|
535
|
+
method: ctx.method,
|
|
536
|
+
env: process.env.NODE_ENV
|
|
537
|
+
});
|
|
538
|
+
} catch (err) {
|
|
539
|
+
console.error('Template render error:', err);
|
|
540
|
+
ctx.status = 500;
|
|
541
|
+
ctx.body = 'Template Error';
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// File server
|
|
546
|
+
app.use(koaClassicServer(path.join(__dirname, 'public'), {
|
|
547
|
+
method: ['GET', 'HEAD'],
|
|
548
|
+
showDirContents: process.env.NODE_ENV !== 'production',
|
|
549
|
+
index: 'index.html',
|
|
550
|
+
urlPrefix: '/files',
|
|
551
|
+
urlsReserved: ['/admin', '/private', '/config', '/.env'],
|
|
552
|
+
template: {
|
|
553
|
+
render: templateRender,
|
|
554
|
+
ext: ['ejs', 'html']
|
|
555
|
+
}
|
|
556
|
+
}));
|
|
557
|
+
|
|
558
|
+
// 404 fallback
|
|
559
|
+
app.use(async (ctx) => {
|
|
560
|
+
ctx.status = 404;
|
|
561
|
+
ctx.body = 'Pagina non trovata';
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const port = process.env.PORT || 3000;
|
|
565
|
+
app.listen(port, () => {
|
|
566
|
+
console.log(`Server avviato su http://localhost:${port}`);
|
|
567
|
+
console.log(`Ambiente: ${process.env.NODE_ENV || 'development'}`);
|
|
568
|
+
});
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
---
|
|
572
|
+
|
|
573
|
+
### 7. Integrazione con Router Koa
|
|
574
|
+
|
|
575
|
+
```javascript
|
|
576
|
+
const Koa = require('koa');
|
|
577
|
+
const Router = require('@koa/router');
|
|
578
|
+
const koaClassicServer = require('koa-classic-server');
|
|
579
|
+
|
|
580
|
+
const app = new Koa();
|
|
581
|
+
const router = new Router();
|
|
582
|
+
|
|
583
|
+
// API routes
|
|
584
|
+
router.get('/api/users', (ctx) => {
|
|
585
|
+
ctx.body = { users: ['Alice', 'Bob'] };
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
router.post('/api/login', (ctx) => {
|
|
589
|
+
ctx.body = { token: 'xyz123' };
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// Monta il router
|
|
593
|
+
app.use(router.routes());
|
|
594
|
+
app.use(router.allowedMethods());
|
|
595
|
+
|
|
596
|
+
// File statici (dopo le route API)
|
|
597
|
+
app.use(koaClassicServer(__dirname + '/public', {
|
|
598
|
+
showDirContents: true
|
|
599
|
+
}));
|
|
600
|
+
|
|
601
|
+
app.listen(3000);
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**Ordine importante:**
|
|
605
|
+
1. Router con API dinamiche
|
|
606
|
+
2. File server statico
|
|
607
|
+
3. Questo permette alle API di avere precedenza sui file
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
## API Reference
|
|
612
|
+
|
|
613
|
+
### koaClassicServer(rootDir, options)
|
|
614
|
+
|
|
615
|
+
Crea e restituisce un middleware Koa per servire file statici.
|
|
616
|
+
|
|
617
|
+
**Parametri:**
|
|
618
|
+
|
|
619
|
+
- `rootDir` (String, required): Path assoluto directory root
|
|
620
|
+
- `options` (Object, optional): Oggetto configurazione
|
|
621
|
+
|
|
622
|
+
**Restituisce:**
|
|
623
|
+
|
|
624
|
+
- Function: Middleware Koa `async (ctx, next) => {...}`
|
|
625
|
+
|
|
626
|
+
**Esempio:**
|
|
627
|
+
|
|
628
|
+
```javascript
|
|
629
|
+
const middleware = koaClassicServer('/path/to/files', {
|
|
630
|
+
showDirContents: true
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
app.use(middleware);
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### Oggetto Options
|
|
637
|
+
|
|
638
|
+
Vedi sezione [Configurazione](#configurazione) per dettagli completi.
|
|
639
|
+
|
|
640
|
+
### Context (ctx) nel Template Render
|
|
641
|
+
|
|
642
|
+
Quando usi `template.render`, hai accesso completo al contesto Koa:
|
|
643
|
+
|
|
644
|
+
```javascript
|
|
645
|
+
template: {
|
|
646
|
+
render: async (ctx, next, filePath) => {
|
|
647
|
+
// ctx.request - Oggetto richiesta
|
|
648
|
+
console.log(ctx.request.method); // 'GET', 'POST', etc.
|
|
649
|
+
console.log(ctx.request.url); // '/path/to/file'
|
|
650
|
+
console.log(ctx.request.header); // Headers HTTP
|
|
651
|
+
|
|
652
|
+
// ctx.response - Oggetto risposta
|
|
653
|
+
ctx.response.type = 'text/html';
|
|
654
|
+
ctx.response.body = '<html>...</html>';
|
|
655
|
+
|
|
656
|
+
// Shortcuts
|
|
657
|
+
console.log(ctx.method); // 'GET'
|
|
658
|
+
console.log(ctx.url); // '/path/to/file'
|
|
659
|
+
console.log(ctx.href); // 'http://localhost:3000/path/to/file'
|
|
660
|
+
console.log(ctx.query); // { key: 'value' }
|
|
661
|
+
console.log(ctx.path); // '/path/to/file'
|
|
662
|
+
|
|
663
|
+
// State (dati condivisi tra middleware)
|
|
664
|
+
console.log(ctx.state.user);
|
|
665
|
+
|
|
666
|
+
// Cookies
|
|
667
|
+
console.log(ctx.cookies.get('session'));
|
|
668
|
+
|
|
669
|
+
// filePath - Path completo del file
|
|
670
|
+
console.log(filePath); // '/var/www/public/page.ejs'
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
## Comportamento del Middleware
|
|
678
|
+
|
|
679
|
+
### Gestione delle Directory
|
|
680
|
+
|
|
681
|
+
#### Caso 1: showDirContents = true, index presente
|
|
682
|
+
|
|
683
|
+
```
|
|
684
|
+
Richiesta: GET /cartella/
|
|
685
|
+
Filesystem:
|
|
686
|
+
/cartella/index.html ✓ (esiste)
|
|
687
|
+
/cartella/file1.txt
|
|
688
|
+
|
|
689
|
+
Risultato: Serve /cartella/index.html
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
#### Caso 2: showDirContents = true, index assente
|
|
693
|
+
|
|
694
|
+
```
|
|
695
|
+
Richiesta: GET /cartella/
|
|
696
|
+
Filesystem:
|
|
697
|
+
/cartella/file1.txt
|
|
698
|
+
/cartella/file2.jpg
|
|
699
|
+
|
|
700
|
+
Risultato: Mostra directory listing HTML
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
#### Caso 3: showDirContents = false
|
|
704
|
+
|
|
705
|
+
```
|
|
706
|
+
Richiesta: GET /cartella/
|
|
707
|
+
|
|
708
|
+
Risultato: "Not Found" (indipendentemente da index)
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### Gestione dei File
|
|
712
|
+
|
|
713
|
+
#### File Normale
|
|
714
|
+
|
|
715
|
+
```
|
|
716
|
+
Richiesta: GET /document.pdf
|
|
717
|
+
Filesystem: /document.pdf (esiste)
|
|
718
|
+
|
|
719
|
+
Risultato:
|
|
720
|
+
Status: 200
|
|
721
|
+
Content-Type: application/pdf
|
|
722
|
+
Content-Disposition: inline; filename=document.pdf
|
|
723
|
+
Body: [file stream]
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
#### File Template
|
|
727
|
+
|
|
728
|
+
```
|
|
729
|
+
Richiesta: GET /page.ejs
|
|
730
|
+
Filesystem: /page.ejs (esiste)
|
|
731
|
+
Config: template.ext = ['ejs'], template.render definito
|
|
732
|
+
|
|
733
|
+
Risultato:
|
|
734
|
+
Status: 200
|
|
735
|
+
Content-Type: [settato da template.render]
|
|
736
|
+
Body: [output renderizzato]
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### URL Riservati
|
|
740
|
+
|
|
741
|
+
```
|
|
742
|
+
Config: urlsReserved = ['/admin', '/private']
|
|
743
|
+
|
|
744
|
+
Richiesta: GET /admin/config.json
|
|
745
|
+
Risultato: Passa a middleware successivo (salta koa-classic-server)
|
|
746
|
+
|
|
747
|
+
Richiesta: GET /admin/
|
|
748
|
+
Risultato: Directory listing mostra "DIR BUT RESERVED"
|
|
749
|
+
|
|
750
|
+
Richiesta: GET /public/file.txt
|
|
751
|
+
Risultato: File servito normalmente
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Parent Directory Navigation
|
|
755
|
+
|
|
756
|
+
Nel directory listing, viene sempre mostrato link alla parent directory, tranne nella root:
|
|
757
|
+
|
|
758
|
+
```html
|
|
759
|
+
<!-- Root: http://localhost:3000/ -->
|
|
760
|
+
<!-- Nessun parent directory link -->
|
|
761
|
+
|
|
762
|
+
<!-- Sub-directory: http://localhost:3000/folder/ -->
|
|
763
|
+
<table>
|
|
764
|
+
<tr><td><a href="http://localhost:3000"><b>.. Parent Directory</b></a></td><td>DIR</td></tr>
|
|
765
|
+
<!-- altri file... -->
|
|
766
|
+
</table>
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
### Normalizzazione URL
|
|
770
|
+
|
|
771
|
+
Il middleware normalizza automaticamente gli URL:
|
|
772
|
+
|
|
773
|
+
```
|
|
774
|
+
http://localhost:3000/folder/ → http://localhost:3000/folder
|
|
775
|
+
http://localhost:3000/file.txt/ → http://localhost:3000/file.txt
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
Questo assicura comportamento coerente indipendentemente dal trailing slash.
|
|
779
|
+
|
|
780
|
+
### MIME Types
|
|
781
|
+
|
|
782
|
+
MIME types riconosciuti automaticamente tramite estensione file:
|
|
783
|
+
|
|
784
|
+
```javascript
|
|
785
|
+
.html → text/html
|
|
786
|
+
.css → text/css
|
|
787
|
+
.js → application/javascript
|
|
788
|
+
.json → application/json
|
|
789
|
+
.png → image/png
|
|
790
|
+
.jpg → image/jpeg
|
|
791
|
+
.pdf → application/pdf
|
|
792
|
+
.txt → text/plain
|
|
793
|
+
// ... e molti altri
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
Se MIME type non riconosciuto:
|
|
797
|
+
```
|
|
798
|
+
Estensione sconosciuta → 'unknow' (nel directory listing)
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
---
|
|
802
|
+
|
|
803
|
+
## Testing
|
|
804
|
+
|
|
805
|
+
### Test Automatici (Jest)
|
|
806
|
+
|
|
807
|
+
Il progetto include una suite completa di test.
|
|
808
|
+
|
|
809
|
+
```bash
|
|
810
|
+
# Esegui tutti i test
|
|
811
|
+
npm test
|
|
812
|
+
|
|
813
|
+
# Test con coverage
|
|
814
|
+
npm test -- --coverage
|
|
815
|
+
|
|
816
|
+
# Test in watch mode
|
|
817
|
+
npm test -- --watch
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
### Test Coverage
|
|
821
|
+
|
|
822
|
+
I test coprono:
|
|
823
|
+
|
|
824
|
+
- ✅ Servizio file statici
|
|
825
|
+
- ✅ Directory listing
|
|
826
|
+
- ✅ URL prefix
|
|
827
|
+
- ✅ URL riservati
|
|
828
|
+
- ✅ File index
|
|
829
|
+
- ✅ Percorsi non esistenti
|
|
830
|
+
- ✅ Metodi HTTP
|
|
831
|
+
- ✅ Caratteri speciali nei nomi file
|
|
832
|
+
- ⚠️ Template rendering (parziale)
|
|
833
|
+
- ⚠️ Directory listing completo (parziale)
|
|
834
|
+
|
|
835
|
+
### Test Manuali Interattivi
|
|
836
|
+
|
|
837
|
+
Per testare manualmente diverse configurazioni:
|
|
838
|
+
|
|
839
|
+
```bash
|
|
840
|
+
npm run loadConfig
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
Questo comando:
|
|
844
|
+
1. Mostra menu interattivo con configurazioni predefinite
|
|
845
|
+
2. Avvia server con configurazione scelta
|
|
846
|
+
3. Permette test manuale via browser
|
|
847
|
+
|
|
848
|
+
**Configurazioni disponibili:**
|
|
849
|
+
- Test generico base
|
|
850
|
+
- Test con URL prefix `/public`
|
|
851
|
+
- Test con file index
|
|
852
|
+
- Test con percorsi riservati
|
|
853
|
+
|
|
854
|
+
### Struttura Test
|
|
855
|
+
|
|
856
|
+
```
|
|
857
|
+
__tests__/
|
|
858
|
+
├── index.test.js # Suite principale
|
|
859
|
+
└── publicWwwTest/ # Cartella test con file/directory campione
|
|
860
|
+
├── file.txt
|
|
861
|
+
├── image.png
|
|
862
|
+
├── subfolder/
|
|
863
|
+
└── ...
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
### Scrivere Nuovi Test
|
|
867
|
+
|
|
868
|
+
Esempio test personalizzato:
|
|
869
|
+
|
|
870
|
+
```javascript
|
|
871
|
+
const supertest = require('supertest');
|
|
872
|
+
const koaClassicServer = require('../index.cjs');
|
|
873
|
+
const Koa = require('koa');
|
|
874
|
+
|
|
875
|
+
describe('My custom test', () => {
|
|
876
|
+
let app, server;
|
|
877
|
+
|
|
878
|
+
beforeAll(() => {
|
|
879
|
+
app = new Koa();
|
|
880
|
+
app.use(koaClassicServer(__dirname + '/test-files', {
|
|
881
|
+
showDirContents: true
|
|
882
|
+
}));
|
|
883
|
+
server = app.listen();
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
test('should serve HTML file', async () => {
|
|
887
|
+
const res = await supertest(server).get('/index.html');
|
|
888
|
+
expect(res.status).toBe(200);
|
|
889
|
+
expect(res.type).toMatch(/text\/html/);
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
afterAll(() => {
|
|
893
|
+
server.close();
|
|
894
|
+
});
|
|
895
|
+
});
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
---
|
|
899
|
+
|
|
900
|
+
## Troubleshooting
|
|
901
|
+
|
|
902
|
+
### File non viene servito
|
|
903
|
+
|
|
904
|
+
**Problema:** Richiesta a file esistente restituisce 404
|
|
905
|
+
|
|
906
|
+
**Soluzioni:**
|
|
907
|
+
|
|
908
|
+
1. Verifica path assoluto:
|
|
909
|
+
```javascript
|
|
910
|
+
// ❌ Path relativo
|
|
911
|
+
koaClassicServer('./public')
|
|
912
|
+
|
|
913
|
+
// ✅ Path assoluto
|
|
914
|
+
koaClassicServer(__dirname + '/public')
|
|
915
|
+
koaClassicServer(path.join(__dirname, 'public'))
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
2. Controlla URL prefix:
|
|
919
|
+
```javascript
|
|
920
|
+
// Config
|
|
921
|
+
urlPrefix: '/static'
|
|
922
|
+
|
|
923
|
+
// ❌ URL sbagliato
|
|
924
|
+
http://localhost:3000/file.txt
|
|
925
|
+
|
|
926
|
+
// ✅ URL corretto
|
|
927
|
+
http://localhost:3000/static/file.txt
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
3. Verifica URL riservati:
|
|
931
|
+
```javascript
|
|
932
|
+
// Config
|
|
933
|
+
urlsReserved: ['/protected']
|
|
934
|
+
|
|
935
|
+
// ❌ File in cartella protetta
|
|
936
|
+
GET /protected/file.txt → 404
|
|
937
|
+
|
|
938
|
+
// ✅ File fuori da cartella protetta
|
|
939
|
+
GET /public/file.txt → 200
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
---
|
|
943
|
+
|
|
944
|
+
### Template non viene renderizzato
|
|
945
|
+
|
|
946
|
+
**Problema:** File template viene servito come testo invece di essere renderizzato
|
|
947
|
+
|
|
948
|
+
**Soluzioni:**
|
|
949
|
+
|
|
950
|
+
1. Verifica estensione in `template.ext`:
|
|
951
|
+
```javascript
|
|
952
|
+
// File: page.ejs
|
|
953
|
+
|
|
954
|
+
// ❌ Estensione mancante
|
|
955
|
+
template: {
|
|
956
|
+
render: renderFunction,
|
|
957
|
+
ext: ['html'] // manca 'ejs'
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// ✅ Estensione presente
|
|
961
|
+
template: {
|
|
962
|
+
render: renderFunction,
|
|
963
|
+
ext: ['ejs', 'EJS']
|
|
964
|
+
}
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
2. Verifica `template.render` sia funzione:
|
|
968
|
+
```javascript
|
|
969
|
+
// ❌ render non definito
|
|
970
|
+
template: {
|
|
971
|
+
ext: ['ejs']
|
|
972
|
+
// render mancante!
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// ✅ render definito
|
|
976
|
+
template: {
|
|
977
|
+
render: async (ctx, next, filePath) => {
|
|
978
|
+
ctx.body = await ejs.renderFile(filePath, {});
|
|
979
|
+
},
|
|
980
|
+
ext: ['ejs']
|
|
981
|
+
}
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
3. Installa template engine:
|
|
985
|
+
```bash
|
|
986
|
+
npm install ejs
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
---
|
|
990
|
+
|
|
991
|
+
### Directory listing non funziona
|
|
992
|
+
|
|
993
|
+
**Problema:** Accesso a directory restituisce "Not Found"
|
|
994
|
+
|
|
995
|
+
**Soluzione:**
|
|
996
|
+
|
|
997
|
+
```javascript
|
|
998
|
+
// ❌ showDirContents disabilitato
|
|
999
|
+
showDirContents: false
|
|
1000
|
+
|
|
1001
|
+
// ✅ showDirContents abilitato
|
|
1002
|
+
showDirContents: true
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
---
|
|
1006
|
+
|
|
1007
|
+
### URL riservati non proteggono
|
|
1008
|
+
|
|
1009
|
+
**Problema:** File in cartelle riservate sono accessibili
|
|
1010
|
+
|
|
1011
|
+
**Cause comuni:**
|
|
1012
|
+
|
|
1013
|
+
1. **Percorsi annidati non supportati:**
|
|
1014
|
+
```javascript
|
|
1015
|
+
// ❌ Non funziona
|
|
1016
|
+
urlsReserved: ['/path/to/protected']
|
|
1017
|
+
|
|
1018
|
+
// ✅ Solo primo livello
|
|
1019
|
+
urlsReserved: ['/protected']
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
2. **Spazi nei nomi (bug noto):**
|
|
1023
|
+
```javascript
|
|
1024
|
+
// ⚠️ Problematico
|
|
1025
|
+
urlsReserved: ['/percorso riservato']
|
|
1026
|
+
|
|
1027
|
+
// ✅ Usa underscore o trattini
|
|
1028
|
+
urlsReserved: ['/percorso_riservato', '/percorso-riservato']
|
|
1029
|
+
```
|
|
1030
|
+
|
|
1031
|
+
---
|
|
1032
|
+
|
|
1033
|
+
### Metodo HTTP non accettato
|
|
1034
|
+
|
|
1035
|
+
**Problema:** Richieste POST/PUT/DELETE non funzionano
|
|
1036
|
+
|
|
1037
|
+
**Soluzione:**
|
|
1038
|
+
|
|
1039
|
+
```javascript
|
|
1040
|
+
// ❌ Solo GET (default)
|
|
1041
|
+
method: ['GET']
|
|
1042
|
+
|
|
1043
|
+
// ✅ Aggiungi metodi necessari
|
|
1044
|
+
method: ['GET', 'POST', 'PUT', 'DELETE']
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
**Nota:** Di solito file server necessita solo GET e HEAD
|
|
1048
|
+
|
|
1049
|
+
---
|
|
1050
|
+
|
|
1051
|
+
### Caratteri speciali in nomi file
|
|
1052
|
+
|
|
1053
|
+
**Problema:** File con spazi o caratteri speciali non accessibili
|
|
1054
|
+
|
|
1055
|
+
**Soluzione:**
|
|
1056
|
+
|
|
1057
|
+
Il middleware gestisce automaticamente URI encoding. Assicurati che il client codifichi correttamente:
|
|
1058
|
+
|
|
1059
|
+
```javascript
|
|
1060
|
+
// ❌ Non encodato
|
|
1061
|
+
GET /my file.txt
|
|
1062
|
+
|
|
1063
|
+
// ✅ Encodato (automatico nei browser)
|
|
1064
|
+
GET /my%20file.txt
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
In JavaScript:
|
|
1068
|
+
```javascript
|
|
1069
|
+
const filename = 'my file.txt';
|
|
1070
|
+
const url = '/' + encodeURIComponent(filename);
|
|
1071
|
+
// url = '/my%20file.txt'
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
---
|
|
1075
|
+
|
|
1076
|
+
### Performance con molti file
|
|
1077
|
+
|
|
1078
|
+
**Problema:** Directory listing lento con migliaia di file
|
|
1079
|
+
|
|
1080
|
+
**Soluzioni:**
|
|
1081
|
+
|
|
1082
|
+
1. Disabilita directory listing:
|
|
1083
|
+
```javascript
|
|
1084
|
+
showDirContents: false
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
2. Usa file index:
|
|
1088
|
+
```javascript
|
|
1089
|
+
showDirContents: true,
|
|
1090
|
+
index: 'index.html' // Carica index invece di listing
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
3. Considera soluzioni alternative per grandi quantità di file (es. nginx, CDN)
|
|
1094
|
+
|
|
1095
|
+
---
|
|
1096
|
+
|
|
1097
|
+
## Problemi Noti
|
|
1098
|
+
|
|
1099
|
+
### 1. Status Code 404 Mancante
|
|
1100
|
+
|
|
1101
|
+
**Problema:** Quando una risorsa non viene trovata, lo status code è 200 invece di 404
|
|
1102
|
+
|
|
1103
|
+
**File:** `index.cjs:110`
|
|
1104
|
+
|
|
1105
|
+
```javascript
|
|
1106
|
+
if (!fs.existsSync(toOpen)) {
|
|
1107
|
+
ctx.body = requestedUrlNotFound();
|
|
1108
|
+
// Manca: ctx.status = 404;
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
**Workaround:**
|
|
1114
|
+
```javascript
|
|
1115
|
+
// Middleware successivo per gestire 404
|
|
1116
|
+
app.use(async (ctx) => {
|
|
1117
|
+
if (!ctx.body) {
|
|
1118
|
+
ctx.status = 404;
|
|
1119
|
+
ctx.body = 'Not Found';
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
---
|
|
1125
|
+
|
|
1126
|
+
### 2. URL Riservati con Spazi
|
|
1127
|
+
|
|
1128
|
+
**Problema:** Il controllo URL riservati non funziona con percorsi contenenti spazi
|
|
1129
|
+
|
|
1130
|
+
**File:** `index.cjs:87-96`
|
|
1131
|
+
|
|
1132
|
+
**Limitazione:** Problemi con URI encoding negli URL riservati
|
|
1133
|
+
|
|
1134
|
+
**Workaround:** Evita spazi nei nomi delle cartelle riservate:
|
|
1135
|
+
```javascript
|
|
1136
|
+
// ❌ Non funziona
|
|
1137
|
+
urlsReserved: ['/percorso riservato']
|
|
1138
|
+
|
|
1139
|
+
// ✅ Funziona
|
|
1140
|
+
urlsReserved: ['/percorso_riservato', '/percorso-riservato']
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
---
|
|
1144
|
+
|
|
1145
|
+
### 3. URL Riservati Solo Primo Livello
|
|
1146
|
+
|
|
1147
|
+
**Problema:** URL riservati funzionano solo per directory di primo livello, non per percorsi annidati
|
|
1148
|
+
|
|
1149
|
+
**File:** `index.cjs:87-96`
|
|
1150
|
+
|
|
1151
|
+
**Limitazione:** Design della funzionalità
|
|
1152
|
+
|
|
1153
|
+
```javascript
|
|
1154
|
+
// ❌ Non supportato
|
|
1155
|
+
urlsReserved: ['/path/to/protected']
|
|
1156
|
+
|
|
1157
|
+
// ✅ Supportato
|
|
1158
|
+
urlsReserved: ['/protected']
|
|
1159
|
+
// Blocca: /protected, /protected/file.txt, /protected/sub/file.txt
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
---
|
|
1163
|
+
|
|
1164
|
+
### 4. Test Coverage Incompleto
|
|
1165
|
+
|
|
1166
|
+
**Problema:** Test non coprono completamente:
|
|
1167
|
+
- Contenuto HTML del directory listing
|
|
1168
|
+
- Tutti i casi di template rendering
|
|
1169
|
+
|
|
1170
|
+
**File:** `__tests__/index.test.js:1-11`
|
|
1171
|
+
|
|
1172
|
+
**Impatto:** Possibili bug non rilevati in queste aree
|
|
1173
|
+
|
|
1174
|
+
---
|
|
1175
|
+
|
|
1176
|
+
### 5. Single Index File
|
|
1177
|
+
|
|
1178
|
+
**Problema:** Supporto per un solo nome file index, non array di fallback
|
|
1179
|
+
|
|
1180
|
+
**Limitazione:** Design attuale
|
|
1181
|
+
|
|
1182
|
+
```javascript
|
|
1183
|
+
// ❌ Non supportato
|
|
1184
|
+
index: ['index.html', 'index.htm', 'default.html']
|
|
1185
|
+
|
|
1186
|
+
// ✅ Supportato
|
|
1187
|
+
index: 'index.html'
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
**Workaround:** Standardizza su un solo nome file index
|
|
1191
|
+
|
|
1192
|
+
---
|
|
1193
|
+
|
|
1194
|
+
## Best Practices
|
|
1195
|
+
|
|
1196
|
+
### 1. Sicurezza
|
|
1197
|
+
|
|
1198
|
+
#### Usa Path Assoluti
|
|
1199
|
+
```javascript
|
|
1200
|
+
// ✅ Corretto
|
|
1201
|
+
const path = require('path');
|
|
1202
|
+
app.use(koaClassicServer(path.join(__dirname, 'public')));
|
|
1203
|
+
|
|
1204
|
+
// ❌ Evita path relativi
|
|
1205
|
+
app.use(koaClassicServer('./public'));
|
|
1206
|
+
```
|
|
1207
|
+
|
|
1208
|
+
#### Proteggi Directory Sensibili
|
|
1209
|
+
```javascript
|
|
1210
|
+
app.use(koaClassicServer(__dirname + '/www', {
|
|
1211
|
+
urlsReserved: [
|
|
1212
|
+
'/config',
|
|
1213
|
+
'/.env',
|
|
1214
|
+
'/.git',
|
|
1215
|
+
'/node_modules',
|
|
1216
|
+
'/private',
|
|
1217
|
+
'/admin'
|
|
1218
|
+
]
|
|
1219
|
+
}));
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
#### Limita Metodi HTTP
|
|
1223
|
+
```javascript
|
|
1224
|
+
// Solo lettura
|
|
1225
|
+
method: ['GET', 'HEAD']
|
|
1226
|
+
|
|
1227
|
+
// Evita metodi di scrittura per file server
|
|
1228
|
+
// method: ['POST', 'PUT', 'DELETE'] ❌
|
|
1229
|
+
```
|
|
1230
|
+
|
|
1231
|
+
#### Disabilita Directory Listing in Produzione
|
|
1232
|
+
```javascript
|
|
1233
|
+
app.use(koaClassicServer(rootDir, {
|
|
1234
|
+
showDirContents: process.env.NODE_ENV !== 'production',
|
|
1235
|
+
index: 'index.html'
|
|
1236
|
+
}));
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
---
|
|
1240
|
+
|
|
1241
|
+
### 2. Performance
|
|
1242
|
+
|
|
1243
|
+
#### Usa Variabili d'Ambiente
|
|
1244
|
+
```javascript
|
|
1245
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
1246
|
+
|
|
1247
|
+
app.use(koaClassicServer(rootDir, {
|
|
1248
|
+
showDirContents: !isProduction,
|
|
1249
|
+
method: ['GET', 'HEAD']
|
|
1250
|
+
}));
|
|
1251
|
+
```
|
|
1252
|
+
|
|
1253
|
+
#### Middleware Logging Efficiente
|
|
1254
|
+
```javascript
|
|
1255
|
+
// Solo in development
|
|
1256
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1257
|
+
app.use(async (ctx, next) => {
|
|
1258
|
+
console.log(`${ctx.method} ${ctx.url}`);
|
|
1259
|
+
await next();
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
#### Cache Headers (middleware aggiuntivo)
|
|
1265
|
+
```javascript
|
|
1266
|
+
// Aggiungi cache headers per file statici
|
|
1267
|
+
app.use(async (ctx, next) => {
|
|
1268
|
+
await next();
|
|
1269
|
+
if (ctx.method === 'GET' && ctx.status === 200) {
|
|
1270
|
+
ctx.set('Cache-Control', 'public, max-age=86400'); // 24h
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
app.use(koaClassicServer(rootDir));
|
|
1275
|
+
```
|
|
1276
|
+
|
|
1277
|
+
---
|
|
1278
|
+
|
|
1279
|
+
### 3. Organizzazione Codice
|
|
1280
|
+
|
|
1281
|
+
#### Separa Configurazione
|
|
1282
|
+
```javascript
|
|
1283
|
+
// config/fileServer.js
|
|
1284
|
+
const path = require('path');
|
|
1285
|
+
|
|
1286
|
+
module.exports = {
|
|
1287
|
+
rootDir: path.join(__dirname, '../public'),
|
|
1288
|
+
options: {
|
|
1289
|
+
method: ['GET', 'HEAD'],
|
|
1290
|
+
showDirContents: true,
|
|
1291
|
+
index: 'index.html',
|
|
1292
|
+
urlPrefix: '/static',
|
|
1293
|
+
urlsReserved: ['/admin', '/config']
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
|
|
1297
|
+
// app.js
|
|
1298
|
+
const fileServerConfig = require('./config/fileServer');
|
|
1299
|
+
app.use(koaClassicServer(
|
|
1300
|
+
fileServerConfig.rootDir,
|
|
1301
|
+
fileServerConfig.options
|
|
1302
|
+
));
|
|
1303
|
+
```
|
|
1304
|
+
|
|
1305
|
+
#### Template Rendering Modulare
|
|
1306
|
+
```javascript
|
|
1307
|
+
// lib/templateRenderer.js
|
|
1308
|
+
const ejs = require('ejs');
|
|
1309
|
+
|
|
1310
|
+
module.exports = async function renderTemplate(ctx, next, filePath) {
|
|
1311
|
+
try {
|
|
1312
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
1313
|
+
title: getPageTitle(filePath),
|
|
1314
|
+
user: ctx.state.user,
|
|
1315
|
+
...ctx.state.templateData
|
|
1316
|
+
});
|
|
1317
|
+
} catch (error) {
|
|
1318
|
+
console.error('Template error:', error);
|
|
1319
|
+
ctx.status = 500;
|
|
1320
|
+
ctx.body = 'Rendering Error';
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
function getPageTitle(filePath) {
|
|
1325
|
+
// Logica per estrarre titolo
|
|
1326
|
+
return 'My Page';
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// app.js
|
|
1330
|
+
const templateRenderer = require('./lib/templateRenderer');
|
|
1331
|
+
|
|
1332
|
+
app.use(koaClassicServer(rootDir, {
|
|
1333
|
+
template: {
|
|
1334
|
+
render: templateRenderer,
|
|
1335
|
+
ext: ['ejs', 'html']
|
|
1336
|
+
}
|
|
1337
|
+
}));
|
|
1338
|
+
```
|
|
1339
|
+
|
|
1340
|
+
---
|
|
1341
|
+
|
|
1342
|
+
### 4. Error Handling
|
|
1343
|
+
|
|
1344
|
+
#### Global Error Handler
|
|
1345
|
+
```javascript
|
|
1346
|
+
app.on('error', (err, ctx) => {
|
|
1347
|
+
console.error('Server error:', {
|
|
1348
|
+
error: err.message,
|
|
1349
|
+
stack: err.stack,
|
|
1350
|
+
url: ctx.url,
|
|
1351
|
+
method: ctx.method,
|
|
1352
|
+
ip: ctx.ip
|
|
1353
|
+
});
|
|
1354
|
+
});
|
|
1355
|
+
```
|
|
1356
|
+
|
|
1357
|
+
#### Try-Catch in Template Render
|
|
1358
|
+
```javascript
|
|
1359
|
+
template: {
|
|
1360
|
+
render: async (ctx, next, filePath) => {
|
|
1361
|
+
try {
|
|
1362
|
+
ctx.body = await ejs.renderFile(filePath, data);
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
console.error('Render error:', error);
|
|
1365
|
+
ctx.status = 500;
|
|
1366
|
+
ctx.body = 'Template Error';
|
|
1367
|
+
}
|
|
1368
|
+
},
|
|
1369
|
+
ext: ['ejs']
|
|
1370
|
+
}
|
|
1371
|
+
```
|
|
1372
|
+
|
|
1373
|
+
#### 404 Fallback
|
|
1374
|
+
```javascript
|
|
1375
|
+
// Ultimo middleware
|
|
1376
|
+
app.use(async (ctx) => {
|
|
1377
|
+
ctx.status = 404;
|
|
1378
|
+
ctx.type = 'html';
|
|
1379
|
+
ctx.body = `
|
|
1380
|
+
<!DOCTYPE html>
|
|
1381
|
+
<html>
|
|
1382
|
+
<head><title>404</title></head>
|
|
1383
|
+
<body>
|
|
1384
|
+
<h1>Pagina Non Trovata</h1>
|
|
1385
|
+
<p>La risorsa richiesta non esiste.</p>
|
|
1386
|
+
</body>
|
|
1387
|
+
</html>
|
|
1388
|
+
`;
|
|
1389
|
+
});
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
---
|
|
1393
|
+
|
|
1394
|
+
### 5. Development vs Production
|
|
1395
|
+
|
|
1396
|
+
```javascript
|
|
1397
|
+
const Koa = require('koa');
|
|
1398
|
+
const koaClassicServer = require('koa-classic-server');
|
|
1399
|
+
const path = require('path');
|
|
1400
|
+
|
|
1401
|
+
const app = new Koa();
|
|
1402
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
1403
|
+
|
|
1404
|
+
// Logging solo in development
|
|
1405
|
+
if (isDev) {
|
|
1406
|
+
app.use(async (ctx, next) => {
|
|
1407
|
+
const start = Date.now();
|
|
1408
|
+
await next();
|
|
1409
|
+
console.log(`${ctx.method} ${ctx.url} - ${Date.now() - start}ms`);
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// File server con configurazione ambiente-specifica
|
|
1414
|
+
app.use(koaClassicServer(path.join(__dirname, 'public'), {
|
|
1415
|
+
showDirContents: isDev, // Solo in dev
|
|
1416
|
+
index: 'index.html',
|
|
1417
|
+
urlPrefix: isDev ? '' : '/static', // Prefix solo in prod
|
|
1418
|
+
urlsReserved: ['/admin', '/config', '/.env'],
|
|
1419
|
+
template: {
|
|
1420
|
+
render: isDev ? devTemplateRender : prodTemplateRender,
|
|
1421
|
+
ext: ['ejs']
|
|
1422
|
+
}
|
|
1423
|
+
}));
|
|
1424
|
+
|
|
1425
|
+
// Error details solo in development
|
|
1426
|
+
app.on('error', (err, ctx) => {
|
|
1427
|
+
if (isDev) {
|
|
1428
|
+
console.error('Error details:', err);
|
|
1429
|
+
} else {
|
|
1430
|
+
console.error('Error:', err.message);
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1434
|
+
app.listen(process.env.PORT || 3000);
|
|
1435
|
+
```
|
|
1436
|
+
|
|
1437
|
+
---
|
|
1438
|
+
|
|
1439
|
+
### 6. Testing
|
|
1440
|
+
|
|
1441
|
+
#### Testa Configurazioni Diverse
|
|
1442
|
+
```javascript
|
|
1443
|
+
// __tests__/server.test.js
|
|
1444
|
+
const configs = [
|
|
1445
|
+
{ name: 'base', options: {} },
|
|
1446
|
+
{ name: 'with-prefix', options: { urlPrefix: '/public' } },
|
|
1447
|
+
{ name: 'no-listing', options: { showDirContents: false } }
|
|
1448
|
+
];
|
|
1449
|
+
|
|
1450
|
+
configs.forEach(({ name, options }) => {
|
|
1451
|
+
describe(`Config: ${name}`, () => {
|
|
1452
|
+
let server;
|
|
1453
|
+
|
|
1454
|
+
beforeAll(() => {
|
|
1455
|
+
const app = new Koa();
|
|
1456
|
+
app.use(koaClassicServer(testDir, options));
|
|
1457
|
+
server = app.listen();
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
test('serves files', async () => {
|
|
1461
|
+
// test...
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
afterAll(() => server.close());
|
|
1465
|
+
});
|
|
1466
|
+
});
|
|
1467
|
+
```
|
|
1468
|
+
|
|
1469
|
+
---
|
|
1470
|
+
|
|
1471
|
+
### 7. Documentazione
|
|
1472
|
+
|
|
1473
|
+
#### Commenta Configurazioni Complesse
|
|
1474
|
+
```javascript
|
|
1475
|
+
app.use(koaClassicServer(__dirname + '/public', {
|
|
1476
|
+
// Mostra directory solo in development per sicurezza
|
|
1477
|
+
showDirContents: process.env.NODE_ENV !== 'production',
|
|
1478
|
+
|
|
1479
|
+
// Protegge configurazioni sensibili
|
|
1480
|
+
// Nota: funziona solo per directory di primo livello
|
|
1481
|
+
urlsReserved: ['/config', '/private'],
|
|
1482
|
+
|
|
1483
|
+
// Template EJS per pagine dinamiche
|
|
1484
|
+
// Rendering con dati utente e query params
|
|
1485
|
+
template: {
|
|
1486
|
+
render: ejsRenderer,
|
|
1487
|
+
ext: ['ejs']
|
|
1488
|
+
}
|
|
1489
|
+
}));
|
|
1490
|
+
```
|
|
1491
|
+
|
|
1492
|
+
---
|
|
1493
|
+
|
|
1494
|
+
## Informazioni Aggiuntive
|
|
1495
|
+
|
|
1496
|
+
### Versione
|
|
1497
|
+
**1.1.0**
|
|
1498
|
+
|
|
1499
|
+
### Autore
|
|
1500
|
+
**Italo Paesano**
|
|
1501
|
+
|
|
1502
|
+
### Licenza
|
|
1503
|
+
**MIT**
|
|
1504
|
+
|
|
1505
|
+
### Keywords
|
|
1506
|
+
- file
|
|
1507
|
+
- server
|
|
1508
|
+
- koa
|
|
1509
|
+
- middleware
|
|
1510
|
+
- static
|
|
1511
|
+
- apache
|
|
1512
|
+
|
|
1513
|
+
### Compatibilità
|
|
1514
|
+
|
|
1515
|
+
#### Node.js
|
|
1516
|
+
- **Minimo:** 12.20+
|
|
1517
|
+
- **Raccomandato:** 14+
|
|
1518
|
+
- **Testato:** 14, 16, 18, 20
|
|
1519
|
+
|
|
1520
|
+
#### Koa
|
|
1521
|
+
- **Versione:** 2.x
|
|
1522
|
+
|
|
1523
|
+
### Conditional Exports
|
|
1524
|
+
|
|
1525
|
+
Il modulo usa conditional exports per supportare sia CommonJS che ES Modules:
|
|
1526
|
+
|
|
1527
|
+
```json
|
|
1528
|
+
{
|
|
1529
|
+
"main": "index.cjs",
|
|
1530
|
+
"exports": {
|
|
1531
|
+
"import": "./index.mjs",
|
|
1532
|
+
"require": "./index.cjs"
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
```
|
|
1536
|
+
|
|
1537
|
+
Questo permette:
|
|
1538
|
+
```javascript
|
|
1539
|
+
// CommonJS
|
|
1540
|
+
const koaClassicServer = require('koa-classic-server');
|
|
1541
|
+
|
|
1542
|
+
// ES Modules
|
|
1543
|
+
import koaClassicServer from 'koa-classic-server';
|
|
1544
|
+
```
|
|
1545
|
+
|
|
1546
|
+
### Repository
|
|
1547
|
+
|
|
1548
|
+
Segnala bug o richiedi funzionalità tramite GitHub Issues.
|
|
1549
|
+
|
|
1550
|
+
### Contributing
|
|
1551
|
+
|
|
1552
|
+
Contributi benvenuti! Per favore:
|
|
1553
|
+
1. Fork del repository
|
|
1554
|
+
2. Crea branch per feature (`git checkout -b feature/AmazingFeature`)
|
|
1555
|
+
3. Commit modifiche (`git commit -m 'Add AmazingFeature'`)
|
|
1556
|
+
4. Push al branch (`git push origin feature/AmazingFeature`)
|
|
1557
|
+
5. Apri Pull Request
|
|
1558
|
+
|
|
1559
|
+
### Changelog
|
|
1560
|
+
|
|
1561
|
+
#### v1.1.0
|
|
1562
|
+
- Versione attuale
|
|
1563
|
+
- Supporto conditional exports
|
|
1564
|
+
- Test suite completa
|
|
1565
|
+
|
|
1566
|
+
---
|
|
1567
|
+
|
|
1568
|
+
## Risorse Aggiuntive
|
|
1569
|
+
|
|
1570
|
+
### Link Utili
|
|
1571
|
+
|
|
1572
|
+
- [Documentazione Koa](https://koajs.com/)
|
|
1573
|
+
- [MIME Types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)
|
|
1574
|
+
- [Node.js Path Module](https://nodejs.org/api/path.html)
|
|
1575
|
+
- [EJS Documentation](https://ejs.co/)
|
|
1576
|
+
|
|
1577
|
+
### Esempi Completi
|
|
1578
|
+
|
|
1579
|
+
Repository con esempi completi disponibile nella cartella `customTest/` del progetto.
|
|
1580
|
+
|
|
1581
|
+
---
|
|
1582
|
+
|
|
1583
|
+
**Documentazione generata per koa-classic-server v1.1.0**
|
|
1584
|
+
|
|
1585
|
+
*Ultimo aggiornamento: 2025-11-17*
|