如何改进用于局域网文件共享的Node.js HTTP服务器?
Got it, let's break down each of your issues and fix them using just Node.js's built-in HTTP module (since you can't use Express):
1. How to stop the server after other devices finish downloading
You've got a few flexible options here depending on your needs:
- Auto-stop when all downloads complete
Track active connections and shut down once the last download finishes. This works great if you only need to serve the file once or to a small number of devices:let activeConnections = 0; const server = http.createServer((req, res) => { activeConnections++; // ... your file transfer logic ... res.on('finish', () => { activeConnections--; if (activeConnections === 0) { console.log('All downloads done—shutting down server'); server.close(() => { console.log('Server stopped successfully'); process.exit(0); }); } }); }); - Manual shutdown endpoint
Add a simple secret route you can hit from any device to trigger a shutdown (good for testing or when you want control):const server = http.createServer((req, res) => { if (req.url === '/shutdown') { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Server shutting down...'); server.close(() => { console.log('Server stopped via shutdown endpoint'); process.exit(0); }); return; } // ... your file transfer logic ... }); - Idle timeout auto-stop
If no requests come in for a set period, shut down automatically (perfect for unattended use):let idleTimeout; const resetIdleTimer = () => { clearTimeout(idleTimeout); idleTimeout = setTimeout(() => { console.log('No activity—shutting down server'); server.close(() => process.exit(0)); }, 5 * 60 * 1000); // 5-minute timeout }; const server = http.createServer((req, res) => { resetIdleTimer(); // ... your file transfer logic ... }); resetIdleTimer(); // Start the timer when server boots
2. Fixing "invalid characters" error for non-English filenames
HTTP headers don't support raw non-ASCII characters directly. You need to encode the filename following the RFC 5987 standard to avoid this error. Here's how to adjust your Content-Disposition header:
const fs = require('fs'); const path = require('path'); const http = require('http'); const filePath = './你好.pdf'; // Example non-English filename const rawFileName = path.basename(filePath); // Encode the filename for safe HTTP header transmission const encodedFileName = encodeURIComponent(rawFileName); const server = http.createServer((req, res) => { fs.stat(filePath, (err, stats) => { if (err) { res.writeHead(404); res.end('File not found'); return; } res.writeHead(200, { 'Content-Type': 'application/octet-stream', 'Content-Length': stats.size, // Use the RFC 5987 format for non-ASCII filenames 'Content-Disposition': `attachment; filename*=UTF-8''${encodedFileName}` }); // Stream the file to the client fs.createReadStream(filePath).pipe(res); }); });
This filename*=UTF-8''${encodedFileName} format is the official way to send non-English filenames in HTTP headers, and it'll work across all modern browsers/devices.
3. Boosting download speed with the Node.js HTTP module
Slow speeds are almost always caused by not using streaming or missing key optimizations. Here's how to fix it:
- Use streaming instead of loading the whole file into memory
Avoidfs.readFile(which loads the entire file into RAM) and usefs.createReadStreaminstead. This sends the file in chunks, which is way more efficient for large files. - Set critical response headers
Always includeContent-Lengthso the client knows exactly how much data to expect—this helps with progress tracking and efficient transfer. AddingAccept-Ranges: bytesalso enables resumable downloads, which is a big win for interrupted transfers. - Tweak stream chunk size
The defaulthighWaterMark(chunk size) is 64KB. Increasing it to 1MB can improve throughput for large files:fs.createReadStream(filePath, { highWaterMark: 1024 * 1024 }) // 1MB chunks .pipe(res); - Disable Nagle's algorithm
Node.js enables this by default, which delays small packets to reduce overhead. Disabling it can lower latency for file transfers:server.on('connection', (socket) => { socket.setNoDelay(true); // Turn off Nagle's algorithm });
Optimized full example
Here's all the fixes combined into one working server:
const http = require('http'); const fs = require('fs'); const path = require('path'); const filePath = './large-video.mp4'; const rawFileName = path.basename(filePath); const encodedFileName = encodeURIComponent(rawFileName); let activeConnections = 0; let idleTimeout; const resetIdleTimer = () => { clearTimeout(idleTimeout); idleTimeout = setTimeout(() => { console.log('No activity—shutting down'); server.close(() => process.exit(0)); }, 5 * 60 * 1000); }; const server = http.createServer((req, res) => { resetIdleTimer(); activeConnections++; // Handle manual shutdown if (req.url === '/shutdown') { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Server shutting down...'); activeConnections--; server.close(() => { console.log('Server stopped'); process.exit(0); }); return; } // Serve the file fs.stat(filePath, (err, stats) => { if (err) { res.writeHead(404); res.end('File not found'); activeConnections--; return; } res.writeHead(200, { 'Content-Type': 'application/octet-stream', 'Content-Length': stats.size, 'Content-Disposition': `attachment; filename*=UTF-8''${encodedFileName}`, 'Accept-Ranges': 'bytes' }); const stream = fs.createReadStream(filePath, { highWaterMark: 1024 * 1024 }); stream.pipe(res); // Track stream completion stream.on('end', () => { activeConnections--; if (activeConnections === 0) { console.log('All downloads done—shutting down'); server.close(() => process.exit(0)); } }); stream.on('error', (err) => { console.error('Stream error:', err); res.writeHead(500); res.end('Error reading file'); activeConnections--; }); }); }); // Disable Nagle's algorithm for faster transfers server.on('connection', (socket) => { socket.setNoDelay(true); }); server.listen(8080, '0.0.0.0', () => { console.log(`Server running at http://${server.address().address}:8080`); resetIdleTimer(); });
内容的提问来源于stack exchange,提问作者Un1

