Node.js中转Box文件下载:实现网络异常时的暂停与续传
Got it, let's work through how to add resumeable downloads and automatic retries to your Node.js proxy for Box files. The main issue with your current code is that it doesn't handle network drops or partial failures—once the connection breaks, the download stops cold and throws an error. Here's how to fix this properly:
1. First, Support HTTP Range Requests (The Foundation of Resumeable Downloads)
Browsers send a Range header when resuming a partial download, asking for bytes starting from a specific offset. Your proxy needs to:
- Parse this header from the client's request
- Forward the same
Rangeheader to Box - Respond with a
206 Partial Contentstatus and the appropriate headers to confirm the range being sent
2. Add Retry Logic for Failed Box Requests
When the connection to Box drops or times out, we want to automatically retry the request (with a backoff delay to avoid overwhelming the server) instead of immediately failing the client's download.
3. Robust Stream Error Handling
Instead of ending the client's response on the first error, we'll catch stream errors, trigger a retry, and resume the stream once the connection is restored.
Modified Core Code
Here's the updated code implementing all these features:
const urlParse = require('url'); const https = require('https'); // Configure retry settings const MAX_RETRIES = 5; const RETRY_DELAY_BASE = 1000; // 1 second base delay function handleBoxDownload(req, res, fileUrl, retryCount = 0) { const obj = urlParse.parse(fileUrl, true); // Build request options for Box, including Range header if present const options = { ...obj, headers: { ...(req.headers.range ? { Range: req.headers.range } : {}) } }; const creq = https.request(options, (cres) => { // Handle partial content or success if (cres.statusCode === 200 || cres.statusCode === 206) { res.writeHead(cres.statusCode, { 'Content-Type': 'application/octet-stream', 'Content-Disposition': cres.headers['content-disposition'], 'Accept-Ranges': 'bytes', // Tell browser we support resuming ...(cres.headers['content-range'] ? { 'Content-Range': cres.headers['content-range'] } : {}) }); // Pipe the Box response to the client, with error handling const stream = cres.pipe(res); stream.on('error', (err) => { console.error(`Stream error: ${err.message}, retrying...`); retryDownload(req, res, fileUrl, retryCount); }); } else { // Pass through non-success status codes to the client res.writeHead(cres.statusCode, { 'Content-Type': 'application/json' }); cres.pipe(res); } }); // Handle request errors (e.g., network timeout, connection reset) creq.on('error', (err) => { console.error(`Box request failed: ${err.message}, retry count: ${retryCount}`); retryDownload(req, res, fileUrl, retryCount); }); // Set a timeout to avoid hanging requests creq.setTimeout(10000, () => { console.error('Request to Box timed out'); creq.abort(); retryDownload(req, res, fileUrl, retryCount); }); creq.end(); } function retryDownload(req, res, fileUrl, retryCount) { if (retryCount >= MAX_RETRIES) { // Max retries reached, send user-friendly error res.statusCode = 503; return res.end('Failed to complete download after multiple retries. Please try again later.'); } // Calculate exponential backoff delay const delay = RETRY_DELAY_BASE * Math.pow(2, retryCount); setTimeout(() => { handleBoxDownload(req, res, fileUrl, retryCount + 1); }, delay); } // Example usage in your route handler app.get('/', (req, res) => { const fileUrl = req.query.url; if (!fileUrl) { return res.status(400).end('Missing "url" query parameter'); } handleBoxDownload(req, res, fileUrl); });
Key Improvements Explained
- Range Request Support: We check for the client's
Rangeheader and forward it to Box, then send backAccept-RangesandContent-Rangeheaders so the browser knows it can resume downloads. - Exponential Backoff Retries: When a request fails, we wait longer between retries (1s, 2s, 4s, etc.) to give the network time to recover, up to a maximum retry limit.
- Stream & Timeout Handling: We added timeouts to avoid hanging requests, and handle stream errors by triggering a retry instead of immediately ending the client's response.
- Graceful Failure: After max retries, we send a clear, user-friendly error instead of a generic network failure message.
Testing Tips
- Use browser dev tools (Network tab) to simulate slow networks or manually interrupt downloads to verify resuming works.
- Test with large files to ensure partial downloads can pick up where they left off.
内容的提问来源于stack exchange,提问作者Ashok Gurram

