使用HttpWebRequest而非HttpClient调用Web API返回Base64异常时如何正确获取文件
基于HttpWebRequest的正确实现方案
原有代码核心问题定位
- 请求构造漏参:后端接口要求接收
IFormFile file和string test两个参数,现有代码仅构造了file字段的表单段,完全没有传递test参数,导致后端参数绑定失败,返回的Base64字符串实际是后端参数校验异常的默认响应内容,不是预期的文件字节 - 响应流重复消费缺陷:代码同时对同一个响应流创建了
StreamReader和BinaryReader实例,StreamReader的内部缓冲区会提前吞掉响应流的起始内容,导致后续BinaryReader读到的内容残缺、错乱 - 响应读取硬截断:
reader1.ReadBytes(16*1024)固定只读取16KB内容,超过该大小的文件会被直接截断 - multipart格式不规范:现有边界拼接逻辑缺少标准换行符、段分隔不完整,不符合RFC对multipart/form-data格式的要求,容易导致后端解析失败
- 输入流位置未重置:读取
HttpPostedFileBase的文件流前没有将流位置重置到起点,可能读取到空字节数组
修正后的完整代码
[HttpPost] public ActionResult DownloadProfilePicture(HttpPostedFileBase file) { // 生成合规的multipart请求边界 string boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x"); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri("https://localhost:44387/api/file/UploadFileAndOtherParamsAndGetDoc")); request.ContentType = $"multipart/form-data; boundary={boundary}"; request.Method = "POST"; request.KeepAlive = true; using (var memStream = new MemoryStream()) { // 1. 构造普通字符串参数 test(补全缺失的必填表单段) string testSection = string.Format( "\r\n--{0}\r\nContent-Disposition: form-data; name=\"test\"\r\n\r\n{1}\r\n", boundary, "这里替换为实际的test参数值" ); byte[] testSectionBytes = Encoding.UTF8.GetBytes(testSection); memStream.Write(testSectionBytes, 0, testSectionBytes.Length); // 2. 构造文件上传段 string fileSectionHeader = string.Format( "--{0}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n", boundary, file.FileName ); byte[] fileHeaderBytes = Encoding.UTF8.GetBytes(fileSectionHeader); memStream.Write(fileHeaderBytes, 0, fileHeaderBytes.Length); // 重置文件流位置后读取完整文件字节 file.InputStream.Position = 0; byte[] fileBytes = new byte[file.InputStream.Length]; file.InputStream.Read(fileBytes, 0, fileBytes.Length); memStream.Write(fileBytes, 0, fileBytes.Length); // 写入multipart结束边界 byte[] endBoundaryBytes = Encoding.ASCII.GetBytes($"\r\n--{boundary}--"); memStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length); request.ContentLength = memStream.Length; // 添加基础认证头 var username = "fdg"; var password = "dsffsd"; string authEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}")); request.Headers.Add("Authorization", $"Basic {authEncoded}"); // 将内存流内容写入请求流 memStream.Position = 0; using (Stream requestStream = request.GetRequestStream()) { memStream.CopyTo(requestStream); } // 全量读取响应流,不做硬截断,不重复实例化流读取器 byte[] responseFileBytes; using (var response = (HttpWebResponse)request.GetResponse()) { using (Stream responseStream = response.GetResponseStream()) { using (var responseMs = new MemoryStream()) { responseStream.CopyTo(responseMs); responseFileBytes = responseMs.ToArray(); } } } return File(responseFileBytes, System.Net.Mime.MediaTypeNames.Application.Octet); } }
实现注意事项
- multipart边界值不能在请求体和请求头中重复出现,否则会导致解析错乱
- 普通表单参数和文件参数的顺序可以互换,但必须按照格式补齐每段的
Content-Disposition头 - 读取响应流时全程只维护一个流实例,不要同时叠加多个读取器,避免流内容被内部缓冲吞掉
- 用
CopyTo替代手动定长读取,避免大文件内容截断
内容的提问来源于stack exchange,提问作者cemil
相关产品推荐
相关产品推荐

