kempo-server 1.2.1 → 1.3.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/builtinMiddleware.js +136 -0
- package/defaultConfig.js +36 -0
- package/example-middleware.js +23 -0
- package/example.config.json +50 -0
- package/middlewareRunner.js +25 -0
- package/package.json +1 -1
- package/router.js +125 -64
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Built-in middleware functions for Kempo Server
|
|
2
|
+
import zlib from 'zlib';
|
|
3
|
+
|
|
4
|
+
// CORS Middleware
|
|
5
|
+
export const corsMiddleware = (config) => {
|
|
6
|
+
return async (req, res, next) => {
|
|
7
|
+
const origin = req.headers.origin;
|
|
8
|
+
const allowedOrigins = Array.isArray(config.origin) ? config.origin : [config.origin];
|
|
9
|
+
|
|
10
|
+
if (config.origin === '*' || allowedOrigins.includes(origin)) {
|
|
11
|
+
res.setHeader('Access-Control-Allow-Origin', origin || '*');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
res.setHeader('Access-Control-Allow-Methods', config.methods.join(', '));
|
|
15
|
+
res.setHeader('Access-Control-Allow-Headers', config.headers.join(', '));
|
|
16
|
+
|
|
17
|
+
// Handle preflight requests
|
|
18
|
+
if (req.method === 'OPTIONS') {
|
|
19
|
+
res.writeHead(200);
|
|
20
|
+
res.end();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await next();
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Compression Middleware
|
|
29
|
+
export const compressionMiddleware = (config) => {
|
|
30
|
+
return async (req, res, next) => {
|
|
31
|
+
const acceptEncoding = req.headers['accept-encoding'] || '';
|
|
32
|
+
|
|
33
|
+
if (!acceptEncoding.includes('gzip')) {
|
|
34
|
+
return await next();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const originalEnd = res.end;
|
|
38
|
+
const originalWrite = res.write;
|
|
39
|
+
const chunks = [];
|
|
40
|
+
|
|
41
|
+
res.write = function(chunk) {
|
|
42
|
+
if (chunk) chunks.push(Buffer.from(chunk));
|
|
43
|
+
return true;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
res.end = function(chunk) {
|
|
47
|
+
if (chunk) chunks.push(Buffer.from(chunk));
|
|
48
|
+
|
|
49
|
+
const buffer = Buffer.concat(chunks);
|
|
50
|
+
|
|
51
|
+
// Only compress if above threshold
|
|
52
|
+
if (buffer.length >= config.threshold) {
|
|
53
|
+
zlib.gzip(buffer, (err, compressed) => {
|
|
54
|
+
if (!err && compressed.length < buffer.length) {
|
|
55
|
+
res.setHeader('Content-Encoding', 'gzip');
|
|
56
|
+
res.setHeader('Content-Length', compressed.length);
|
|
57
|
+
originalEnd.call(res, compressed);
|
|
58
|
+
} else {
|
|
59
|
+
originalEnd.call(res, buffer);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
originalEnd.call(res, buffer);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
await next();
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Rate Limiting Middleware
|
|
72
|
+
export const rateLimitMiddleware = (config) => {
|
|
73
|
+
const requestCounts = new Map();
|
|
74
|
+
|
|
75
|
+
return async (req, res, next) => {
|
|
76
|
+
const clientId = req.socket.remoteAddress;
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
const windowStart = now - config.windowMs;
|
|
79
|
+
|
|
80
|
+
if (!requestCounts.has(clientId)) {
|
|
81
|
+
requestCounts.set(clientId, []);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const requests = requestCounts.get(clientId);
|
|
85
|
+
const recentRequests = requests.filter(time => time > windowStart);
|
|
86
|
+
|
|
87
|
+
if (recentRequests.length >= config.maxRequests) {
|
|
88
|
+
res.writeHead(429, { 'Content-Type': 'text/plain' });
|
|
89
|
+
res.end(config.message);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
recentRequests.push(now);
|
|
94
|
+
requestCounts.set(clientId, recentRequests);
|
|
95
|
+
|
|
96
|
+
await next();
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Security Headers Middleware
|
|
101
|
+
export const securityMiddleware = (config) => {
|
|
102
|
+
return async (req, res, next) => {
|
|
103
|
+
for (const [header, value] of Object.entries(config.headers)) {
|
|
104
|
+
res.setHeader(header, value);
|
|
105
|
+
}
|
|
106
|
+
await next();
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Logging Middleware
|
|
111
|
+
export const loggingMiddleware = (config, log) => {
|
|
112
|
+
return async (req, res, next) => {
|
|
113
|
+
const startTime = Date.now();
|
|
114
|
+
const userAgent = config.includeUserAgent ? req.headers['user-agent'] : '';
|
|
115
|
+
|
|
116
|
+
// Store original end to capture response
|
|
117
|
+
const originalEnd = res.end;
|
|
118
|
+
res.end = function(...args) {
|
|
119
|
+
const responseTime = Date.now() - startTime;
|
|
120
|
+
let logMessage = `${req.method} ${req.url}`;
|
|
121
|
+
|
|
122
|
+
if (config.includeResponseTime) {
|
|
123
|
+
logMessage += ` - ${responseTime}ms`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (config.includeUserAgent && userAgent) {
|
|
127
|
+
logMessage += ` - ${userAgent}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
log(logMessage, 1);
|
|
131
|
+
originalEnd.apply(res, args);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
await next();
|
|
135
|
+
};
|
|
136
|
+
};
|
package/defaultConfig.js
CHANGED
|
@@ -90,5 +90,41 @@ export default {
|
|
|
90
90
|
customRoutes: {
|
|
91
91
|
// Example: "/vendor/bootstrap.css": "./node_modules/bootstrap/dist/css/bootstrap.min.css"
|
|
92
92
|
// Wildcard example: "kempo/*": "./node_modules/kempo/dust/*"
|
|
93
|
+
},
|
|
94
|
+
middleware: {
|
|
95
|
+
// Built-in middleware configuration
|
|
96
|
+
cors: {
|
|
97
|
+
enabled: false,
|
|
98
|
+
origin: "*",
|
|
99
|
+
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
100
|
+
headers: ["Content-Type", "Authorization"]
|
|
101
|
+
},
|
|
102
|
+
compression: {
|
|
103
|
+
enabled: false,
|
|
104
|
+
threshold: 1024 // Only compress files larger than 1KB
|
|
105
|
+
},
|
|
106
|
+
rateLimit: {
|
|
107
|
+
enabled: false,
|
|
108
|
+
maxRequests: 100,
|
|
109
|
+
windowMs: 60000, // 1 minute
|
|
110
|
+
message: "Too many requests"
|
|
111
|
+
},
|
|
112
|
+
security: {
|
|
113
|
+
enabled: true,
|
|
114
|
+
headers: {
|
|
115
|
+
"X-Content-Type-Options": "nosniff",
|
|
116
|
+
"X-Frame-Options": "DENY",
|
|
117
|
+
"X-XSS-Protection": "1; mode=block"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
logging: {
|
|
121
|
+
enabled: true,
|
|
122
|
+
includeUserAgent: false,
|
|
123
|
+
includeResponseTime: true
|
|
124
|
+
},
|
|
125
|
+
// Custom middleware files
|
|
126
|
+
custom: [
|
|
127
|
+
// Example: "./middleware/auth.js"
|
|
128
|
+
]
|
|
93
129
|
}
|
|
94
130
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Example custom middleware file
|
|
2
|
+
// This would be placed in your project directory and referenced in config
|
|
3
|
+
|
|
4
|
+
export default async function authMiddleware(req, res, next) {
|
|
5
|
+
// Example: Check for API key in headers
|
|
6
|
+
const apiKey = req.headers['x-api-key'];
|
|
7
|
+
|
|
8
|
+
// Skip auth for public routes
|
|
9
|
+
if (req.url.startsWith('/public/')) {
|
|
10
|
+
return await next();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!apiKey || apiKey !== process.env.API_KEY) {
|
|
14
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
15
|
+
res.end(JSON.stringify({ error: 'Unauthorized' }));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Add user info to request for downstream use
|
|
20
|
+
req.user = { authenticated: true, apiKey };
|
|
21
|
+
|
|
22
|
+
await next();
|
|
23
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"allowedMimes": {
|
|
3
|
+
"html": "text/html",
|
|
4
|
+
"css": "text/css",
|
|
5
|
+
"js": "application/javascript",
|
|
6
|
+
"json": "application/json",
|
|
7
|
+
"png": "image/png",
|
|
8
|
+
"jpg": "image/jpeg"
|
|
9
|
+
},
|
|
10
|
+
"middleware": {
|
|
11
|
+
"cors": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"origin": ["http://localhost:3000", "https://mydomain.com"],
|
|
14
|
+
"methods": ["GET", "POST", "PUT", "DELETE"],
|
|
15
|
+
"headers": ["Content-Type", "Authorization", "X-API-Key"]
|
|
16
|
+
},
|
|
17
|
+
"compression": {
|
|
18
|
+
"enabled": true,
|
|
19
|
+
"threshold": 512
|
|
20
|
+
},
|
|
21
|
+
"rateLimit": {
|
|
22
|
+
"enabled": true,
|
|
23
|
+
"maxRequests": 50,
|
|
24
|
+
"windowMs": 60000,
|
|
25
|
+
"message": "Rate limit exceeded. Please try again later."
|
|
26
|
+
},
|
|
27
|
+
"security": {
|
|
28
|
+
"enabled": true,
|
|
29
|
+
"headers": {
|
|
30
|
+
"X-Content-Type-Options": "nosniff",
|
|
31
|
+
"X-Frame-Options": "SAMEORIGIN",
|
|
32
|
+
"X-XSS-Protection": "1; mode=block",
|
|
33
|
+
"Strict-Transport-Security": "max-age=31536000; includeSubDomains"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"logging": {
|
|
37
|
+
"enabled": true,
|
|
38
|
+
"includeUserAgent": true,
|
|
39
|
+
"includeResponseTime": true
|
|
40
|
+
},
|
|
41
|
+
"custom": [
|
|
42
|
+
"./middleware/auth.js",
|
|
43
|
+
"./middleware/analytics.js"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"customRoutes": {
|
|
47
|
+
"api/*": "./api-handlers/*",
|
|
48
|
+
"/vendor/bootstrap.css": "./node_modules/bootstrap/dist/css/bootstrap.min.css"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Middleware runner for Kempo Server
|
|
2
|
+
export default class MiddlewareRunner {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.middlewares = [];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
use(middleware) {
|
|
8
|
+
this.middlewares.push(middleware);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async run(req, res, finalHandler) {
|
|
12
|
+
let index = 0;
|
|
13
|
+
|
|
14
|
+
const next = async () => {
|
|
15
|
+
if (index >= this.middlewares.length) {
|
|
16
|
+
return await finalHandler();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const middleware = this.middlewares[index++];
|
|
20
|
+
await middleware(req, res, next);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
await next();
|
|
24
|
+
}
|
|
25
|
+
}
|
package/package.json
CHANGED
package/router.js
CHANGED
|
@@ -5,6 +5,14 @@ import defaultConfig from './defaultConfig.js';
|
|
|
5
5
|
import getFiles from './getFiles.js';
|
|
6
6
|
import findFile from './findFile.js';
|
|
7
7
|
import serveFile from './serveFile.js';
|
|
8
|
+
import MiddlewareRunner from './middlewareRunner.js';
|
|
9
|
+
import {
|
|
10
|
+
corsMiddleware,
|
|
11
|
+
compressionMiddleware,
|
|
12
|
+
rateLimitMiddleware,
|
|
13
|
+
securityMiddleware,
|
|
14
|
+
loggingMiddleware
|
|
15
|
+
} from './builtinMiddleware.js';
|
|
8
16
|
|
|
9
17
|
export default async (flags, log) => {
|
|
10
18
|
log('Initializing router', 2);
|
|
@@ -39,6 +47,57 @@ export default async (flags, log) => {
|
|
|
39
47
|
let files = await getFiles(rootPath, config, log);
|
|
40
48
|
log(`Initial scan found ${files.length} files`, 1);
|
|
41
49
|
|
|
50
|
+
// Initialize middleware runner
|
|
51
|
+
const middlewareRunner = new MiddlewareRunner();
|
|
52
|
+
|
|
53
|
+
// Load built-in middleware based on config
|
|
54
|
+
if (config.middleware?.cors?.enabled) {
|
|
55
|
+
middlewareRunner.use(corsMiddleware(config.middleware.cors));
|
|
56
|
+
log('CORS middleware enabled', 2);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (config.middleware?.compression?.enabled) {
|
|
60
|
+
middlewareRunner.use(compressionMiddleware(config.middleware.compression));
|
|
61
|
+
log('Compression middleware enabled', 2);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (config.middleware?.rateLimit?.enabled) {
|
|
65
|
+
middlewareRunner.use(rateLimitMiddleware(config.middleware.rateLimit));
|
|
66
|
+
log('Rate limit middleware enabled', 2);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (config.middleware?.security?.enabled) {
|
|
70
|
+
middlewareRunner.use(securityMiddleware(config.middleware.security));
|
|
71
|
+
log('Security middleware enabled', 2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (config.middleware?.logging?.enabled) {
|
|
75
|
+
middlewareRunner.use(loggingMiddleware(config.middleware.logging, log));
|
|
76
|
+
log('Logging middleware enabled', 2);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Load custom middleware files
|
|
80
|
+
if (config.middleware?.custom && config.middleware.custom.length > 0) {
|
|
81
|
+
log(`Loading ${config.middleware.custom.length} custom middleware files`, 2);
|
|
82
|
+
|
|
83
|
+
for (const middlewarePath of config.middleware.custom) {
|
|
84
|
+
try {
|
|
85
|
+
const resolvedPath = path.resolve(middlewarePath);
|
|
86
|
+
const middlewareModule = await import(pathToFileURL(resolvedPath));
|
|
87
|
+
const customMiddleware = middlewareModule.default;
|
|
88
|
+
|
|
89
|
+
if (typeof customMiddleware === 'function') {
|
|
90
|
+
middlewareRunner.use(customMiddleware(config.middleware));
|
|
91
|
+
log(`Custom middleware loaded: ${middlewarePath}`, 2);
|
|
92
|
+
} else {
|
|
93
|
+
log(`Custom middleware error: ${middlewarePath} does not export a default function`, 1);
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
log(`Custom middleware error for ${middlewarePath}: ${error.message}`, 1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
42
101
|
// Process custom routes - resolve paths and validate files exist
|
|
43
102
|
const customRoutes = new Map();
|
|
44
103
|
const wildcardRoutes = new Map();
|
|
@@ -146,80 +205,82 @@ export default async (flags, log) => {
|
|
|
146
205
|
};
|
|
147
206
|
|
|
148
207
|
return async (req, res) => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// Check custom routes first
|
|
153
|
-
if (customRoutes.has(requestPath)) {
|
|
154
|
-
const customFilePath = customRoutes.get(requestPath);
|
|
155
|
-
log(`Serving custom route: ${requestPath} -> ${customFilePath}`, 2);
|
|
208
|
+
await middlewareRunner.run(req, res, async () => {
|
|
209
|
+
const requestPath = req.url.split('?')[0];
|
|
210
|
+
log(`${req.method} ${requestPath}`, 0);
|
|
156
211
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
212
|
+
// Check custom routes first
|
|
213
|
+
if (customRoutes.has(requestPath)) {
|
|
214
|
+
const customFilePath = customRoutes.get(requestPath);
|
|
215
|
+
log(`Serving custom route: ${requestPath} -> ${customFilePath}`, 2);
|
|
161
216
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
217
|
+
try {
|
|
218
|
+
const fileContent = await readFile(customFilePath);
|
|
219
|
+
const fileExtension = path.extname(customFilePath).toLowerCase().slice(1);
|
|
220
|
+
const mimeType = config.allowedMimes[fileExtension] || 'application/octet-stream';
|
|
221
|
+
|
|
222
|
+
log(`Serving custom file as ${mimeType} (${fileContent.length} bytes)`, 2);
|
|
223
|
+
res.writeHead(200, { 'Content-Type': mimeType });
|
|
224
|
+
res.end(fileContent);
|
|
225
|
+
return; // Successfully served custom route
|
|
226
|
+
} catch (error) {
|
|
227
|
+
log(`Error serving custom route ${requestPath}: ${error.message}`, 0);
|
|
228
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
229
|
+
res.end('Internal Server Error');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
171
232
|
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Check wildcard routes
|
|
175
|
-
const wildcardMatch = findWildcardRoute(requestPath);
|
|
176
|
-
if (wildcardMatch) {
|
|
177
|
-
const resolvedFilePath = resolveWildcardPath(wildcardMatch.filePath, wildcardMatch.matches);
|
|
178
|
-
log(`Serving wildcard route: ${requestPath} -> ${resolvedFilePath}`, 2);
|
|
179
233
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
234
|
+
// Check wildcard routes
|
|
235
|
+
const wildcardMatch = findWildcardRoute(requestPath);
|
|
236
|
+
if (wildcardMatch) {
|
|
237
|
+
const resolvedFilePath = resolveWildcardPath(wildcardMatch.filePath, wildcardMatch.matches);
|
|
238
|
+
log(`Serving wildcard route: ${requestPath} -> ${resolvedFilePath}`, 2);
|
|
184
239
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
240
|
+
try {
|
|
241
|
+
const fileContent = await readFile(resolvedFilePath);
|
|
242
|
+
const fileExtension = path.extname(resolvedFilePath).toLowerCase().slice(1);
|
|
243
|
+
const mimeType = config.allowedMimes[fileExtension] || 'application/octet-stream';
|
|
244
|
+
|
|
245
|
+
log(`Serving wildcard file as ${mimeType} (${fileContent.length} bytes)`, 2);
|
|
246
|
+
res.writeHead(200, { 'Content-Type': mimeType });
|
|
247
|
+
res.end(fileContent);
|
|
248
|
+
return; // Successfully served wildcard route
|
|
249
|
+
} catch (error) {
|
|
250
|
+
log(`Error serving wildcard route ${requestPath}: ${error.message}`, 0);
|
|
251
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
252
|
+
res.end('Internal Server Error');
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
194
255
|
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Try to serve the file normally
|
|
198
|
-
const served = await serveFile(files, rootPath, requestPath, req.method, config, req, res, log);
|
|
199
|
-
|
|
200
|
-
// If not served and scan flag is enabled, try rescanning once (with blacklist check)
|
|
201
|
-
if (!served && flags.scan && !shouldSkipRescan(requestPath)) {
|
|
202
|
-
trackRescanAttempt(requestPath);
|
|
203
|
-
log('File not found, rescanning directory...', 1);
|
|
204
|
-
files = await getFiles(rootPath, config, log);
|
|
205
|
-
log(`Rescan found ${files.length} files`, 2);
|
|
206
256
|
|
|
207
|
-
// Try to serve
|
|
208
|
-
const
|
|
257
|
+
// Try to serve the file normally
|
|
258
|
+
const served = await serveFile(files, rootPath, requestPath, req.method, config, req, res, log);
|
|
209
259
|
|
|
210
|
-
|
|
211
|
-
|
|
260
|
+
// If not served and scan flag is enabled, try rescanning once (with blacklist check)
|
|
261
|
+
if (!served && flags.scan && !shouldSkipRescan(requestPath)) {
|
|
262
|
+
trackRescanAttempt(requestPath);
|
|
263
|
+
log('File not found, rescanning directory...', 1);
|
|
264
|
+
files = await getFiles(rootPath, config, log);
|
|
265
|
+
log(`Rescan found ${files.length} files`, 2);
|
|
266
|
+
|
|
267
|
+
// Try to serve again after rescan
|
|
268
|
+
const reserved = await serveFile(files, rootPath, requestPath, req.method, config, req, res, log);
|
|
269
|
+
|
|
270
|
+
if (!reserved) {
|
|
271
|
+
log(`404 - File not found after rescan: ${requestPath}`, 1);
|
|
272
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
273
|
+
res.end('Not Found');
|
|
274
|
+
}
|
|
275
|
+
} else if (!served) {
|
|
276
|
+
if (shouldSkipRescan(requestPath)) {
|
|
277
|
+
log(`404 - Skipped rescan for: ${requestPath}`, 2);
|
|
278
|
+
} else {
|
|
279
|
+
log(`404 - File not found: ${requestPath}`, 1);
|
|
280
|
+
}
|
|
212
281
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
213
282
|
res.end('Not Found');
|
|
214
283
|
}
|
|
215
|
-
}
|
|
216
|
-
if (shouldSkipRescan(requestPath)) {
|
|
217
|
-
log(`404 - Skipped rescan for: ${requestPath}`, 2);
|
|
218
|
-
} else {
|
|
219
|
-
log(`404 - File not found: ${requestPath}`, 1);
|
|
220
|
-
}
|
|
221
|
-
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
222
|
-
res.end('Not Found');
|
|
223
|
-
}
|
|
284
|
+
});
|
|
224
285
|
}
|
|
225
286
|
}
|