Node.js服务器文件上传的优化与安全问题咨询
解决分块上传引发的服务器内存耗尽风险
这确实是分块上传场景里很典型的恶意攻击风险,咱们一步步拆解可行的解决思路:
核心问题分析
你当前把所有未完成的分块都存在堆内存的Uint8数组里,一旦恶意客户端发起大量半截上传请求,这些占内存的分块会持续堆积,直接把服务器内存撑爆。下面是几个针对性的解决方案,按落地优先级排序:
1. 把临时分块从内存迁移到磁盘存储
这是最直接有效的办法——别再把分块存在堆内存里了,改成存在服务器本地的临时目录。给每个上传任务分配单独的子文件夹,分块按序号命名(比如chunk_1.dat、chunk_2.dat),等所有分块上传完成后,再从磁盘读取合并成完整文件,最后清理临时文件。
示例代码思路:
const fs = require('fs').promises; const path = require('path'); // 为每个上传任务创建专属临时目录 async function createTempUploadDir(uploadId) { const tempDir = path.join(__dirname, 'temp_uploads', uploadId); await fs.mkdir(tempDir, { recursive: true }); return tempDir; } // 将接收的分块写入磁盘 async function saveChunkToDisk(tempDir, chunkIndex, chunkBuffer) { const chunkPath = path.join(tempDir, `chunk_${chunkIndex}`); await fs.writeFile(chunkPath, chunkBuffer); }
这样内存里只会留存当前正在处理的单个分块,不会堆积大量未完成任务的数据。
2. 给未完成上传任务设置超时清理机制
前端会在2小时内重试,那咱们给每个上传任务设置2.5小时的超时阈值(留一点缓冲时间)。用定时器或定时任务,定期扫描所有未完成的上传任务,超过超时时间的直接删除对应临时文件/清理残留数据。
示例实现:
const activeUploads = new Map(); // key: uploadId, value: { lastActiveTime: Date, tempDir: string } // 每次收到分块时更新任务活动时间 function refreshUploadActivity(uploadId) { const uploadInfo = activeUploads.get(uploadId); if (uploadInfo) { uploadInfo.lastActiveTime = new Date(); } } // 每30分钟清理一次超时任务 setInterval(async () => { const now = new Date(); const timeoutMs = 2.5 * 60 * 60 * 1000; // 2.5小时 for (const [uploadId, info] of activeUploads.entries()) { if (now - info.lastActiveTime > timeoutMs) { // 删除临时目录及分块文件 await fs.rm(info.tempDir, { recursive: true, force: true }); activeUploads.delete(uploadId); } } }, 30 * 60 * 1000);
3. 限制单个客户端的并发上传任务数
恶意客户端可能同时发起上百个上传任务来消耗内存,所以要限制每个IP(或已登录用户ID)的并发上传数量。比如用一个IP计数器:
const ipUploadCounts = new Map(); // key: clientIp, value: number // 处理上传请求前检查并发数 function checkUploadLimit(clientIp) { const currentCount = ipUploadCounts.get(clientIp) || 0; if (currentCount >= 5) { // 限制每个IP最多5个并发上传 return false; } ipUploadCounts.set(clientIp, currentCount + 1); return true; } // 上传完成/超时后减少计数 function decreaseUploadCount(clientIp) { const currentCount = ipUploadCounts.get(clientIp) || 0; if (currentCount > 0) { ipUploadCounts.set(clientIp, currentCount - 1); } else { ipUploadCounts.delete(clientIp); } }
如果有用户系统,用用户ID代替IP会更准确,避免IP代理绕过限制。
4. 启用内存监控与告警
在服务器中加入内存监控逻辑,当堆内存使用率超过阈值(比如70%)时,暂时拒绝新的上传请求,同时触发告警通知运维人员。
示例监控代码:
let isAcceptingUploads = true; function checkMemoryStatus() { const memUsage = process.memoryUsage(); const heapUsedPercent = (memUsage.heapUsed / memUsage.heapTotal) * 100; if (heapUsedPercent > 70 && isAcceptingUploads) { isAcceptingUploads = false; console.warn(`内存使用率过高:${heapUsedPercent.toFixed(2)}%,暂停接收新上传`); // 这里可以添加告警逻辑,比如发邮件/企业微信通知 // 5分钟后自动恢复接收 setTimeout(() => { isAcceptingUploads = true; console.log("恢复接收新上传请求"); }, 5 * 60 * 1000); } } // 每分钟检查一次内存状态 setInterval(checkMemoryStatus, 60 * 1000);
总结一下,核心思路就是减少内存中留存的临时数据+主动清理过期资源+限制恶意请求规模,这几个方案组合起来就能有效抵御这类内存耗尽攻击。
内容的提问来源于stack exchange,提问作者vjjj
相关产品推荐
相关产品推荐

