WebAPI大文件批量传输更新机制咨询:单文件1GB/20客户端并发
大文件WebAPI下载的优化方案
嘿,首先得指出你当前代码里的致命问题:你用File.ReadAllBytes(reqFile)把整个1GB的文件直接加载到服务器内存里了,单个请求就占1GB内存,20个并发的话服务器内存直接炸锅,完全扛不住。而且内存流也是多余的,完全可以直接用文件流实现流式传输,不用把整个文件塞进内存。
优化后的基础实现(最省心)
这个版本直接用StreamContent让WebAPI自动处理流式传输,不会一次性加载整个文件到内存:
public HttpResponseMessage GetFile(string name) { // 先做安全校验:只取文件名,防止路径遍历攻击 var safeFileName = Path.GetFileName(name); var reqFile = Path.Combine(@"C:\updates\", safeFileName); // 先检查文件是否存在,避免抛出异常 if (!File.Exists(reqFile)) { return Request.CreateResponse(HttpStatusCode.NotFound); } // 打开文件流,允许其他进程同时读取(比如你后台更新文件的时候) var fileStream = new FileStream(reqFile, FileMode.Open, FileAccess.Read, FileShare.Read); var response = Request.CreateResponse(HttpStatusCode.OK); // 用StreamContent替代内存流,WebAPI会自动分块发送文件 response.Content = new StreamContent(fileStream); // 设置必要的响应头,让客户端能正确处理下载 response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); response.Content.Headers.ContentLength = fileStream.Length; // 告诉客户端文件总大小,方便显示进度 response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = safeFileName // 让客户端保存时用这个文件名 }; return response; }
进阶:支持断点续传(提升用户体验)
如果你的客户端可能遇到下载中断的情况,支持断点续传会更友好,客户端可以从断点继续下载,不用重新下载整个文件:
public HttpResponseMessage GetFile(string name) { var safeFileName = Path.GetFileName(name); var reqFile = Path.Combine(@"C:\updates\", safeFileName); if (!File.Exists(reqFile)) { return Request.CreateResponse(HttpStatusCode.NotFound); } var fileInfo = new FileInfo(reqFile); var response = new HttpResponseMessage(); var fileStream = new FileStream(reqFile, FileMode.Open, FileAccess.Read, FileShare.Read); // 处理客户端的Range请求(断点续传的核心) if (Request.Headers.Range != null) { try { var range = Request.Headers.Range.Ranges.FirstOrDefault(); if (range == null) { response.StatusCode = HttpStatusCode.RequestedRangeNotSatisfiable; return response; } var startByte = range.From ?? 0; var endByte = range.To ?? fileInfo.Length - 1; // 校验请求的范围是否合法 if (startByte >= fileInfo.Length || endByte >= fileInfo.Length || startByte > endByte) { response.StatusCode = HttpStatusCode.RequestedRangeNotSatisfiable; response.Content = new StringContent($"请求的范围无效:{startByte}-{endByte}"); return response; } // 把文件流定位到起始位置 fileStream.Seek(startByte, SeekOrigin.Begin); var contentLength = endByte - startByte + 1; response.StatusCode = HttpStatusCode.PartialContent; response.Content = new StreamContent(fileStream); response.Content.Headers.ContentLength = contentLength; response.Content.Headers.ContentRange = new ContentRangeHeaderValue(startByte, endByte, fileInfo.Length); } catch { response.StatusCode = HttpStatusCode.RequestedRangeNotSatisfiable; return response; } } else { // 普通下载请求,直接返回整个文件 response.StatusCode = HttpStatusCode.OK; response.Content = new StreamContent(fileStream); response.Content.Headers.ContentLength = fileInfo.Length; } // 设置通用的响应头 response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = safeFileName }; return response; }
额外的几个注意点
- 路径安全:一定要用
Path.GetFileName(name)过滤用户传入的文件名,防止路径遍历攻击(比如用户传入../config/secret.key这种恶意路径)。 - 并发优化:20个并发不算特别高,但可以考虑把方法改成异步(
async Task<HttpResponseMessage>),避免阻塞服务器线程;如果后续并发更高,可以考虑加个简单的并发限制。 - 缓存控制:如果你的更新文件不会频繁变动,可以添加
Cache-Control响应头,让客户端缓存文件,减少重复请求对服务器的压力。
内容的提问来源于stack exchange,提问作者Thomas
相关产品推荐
相关产品推荐

