解决vite环境下调用获取二进制文件流 部分文件报错 (failed)net::ERR_INVALID_HTTP_RESPONSE)

354 阅读5分钟

背景

对于同一个文件,通过接口来获取二进制文件流的时候,线上部署环境是ok的,可以正常下载,但是本地的vite proxy代理 对于同一个文件展示不同的效果 会直接拒绝访问 报错信息为

(failed)

net::ERR_INVALID_HTTP_RESPONSE

image.png

解决办法 代理中增加二进制处理配置信息 configure

原理

Vite 增加代理配置之所以能解决二进制流的问题,核心原因在于代理服务器对二进制响应的处理方式

问题根源:Vite 代理的默认行为与二进制流的冲突

Vite 的代理功能基于http-proxy库实现,其默认配置会对响应做一些 "优化处理",比如:

  1. 自动添加或修改Content-Length响应头

  2. 可能对响应内容进行字符编码转换

  3. 处理分块传输(chunked)时的格式转换

这些处理对普通 JSON / 文本响应没问题,但对二进制流(如文件、图片、PDF 等)  会造成严重问题:

  • 二进制数据被误当作文本处理导致编码错误
  • Content-Length计算错误引发浏览器解析失败(ERR_INVALID_HTTP_RESPONSE
  • 二进制数据被截断或修改,导致文件损坏

配置生效的关键原因

configure: (proxy, options) => {
  proxy.on('proxyRes', (proxyRes, req, res) => {
    // 检测二进制流类型的响应
    if (proxyRes.headers['content-type'] && 
        (proxyRes.headers['content-type'].includes('application/octet-stream') ||
         proxyRes.headers['content-type'].includes('application/pdf') ||
         proxyRes.headers['content-type'].includes('image/'))) {
      // 删除Content-Length头
      delete proxyRes.headers['content-length'];
    }
  });
}

这段配置的作用是:

  1. 识别二进制响应:通过Content-Type判断是否为二进制流(文件、图片等)

  2. 移除Content-Length

    • 二进制流的长度计算非常严格,代理自动添加的Content-Length可能与实际内容长度不匹配
    • 浏览器发现Content-Length与实际接收的字节数不符时,会判定为无效响应
    • 删除该头后,浏览器会根据实际接收的数据流来处理,避免校验失败

额外说明

对于二进制流,正确的传输方式通常是:

  • 使用Transfer-Encoding: chunked分块传输(无需Content-Length

  • 保持原始二进制编码(不做任何字符转换)

Vite 的默认代理配置没有针对二进制流做特殊处理,而我们的自定义配置正是修复了这个问题,让二进制数据能够原封不动地从后端传输到前端,从而与线上环境保持一致的行为。

有些文件可以成功,有些不可以的原因  文件大小触发不同传输模式

  • 小文件:后端可能使用Content-Length固定长度传输,且代理计算的长度恰好正确,因此能正常加载。
  • 大文件:后端通常使用Transfer-Encoding: chunked分块传输(不包含Content-Length),但代理可能强行添加了错误的Content-Length,导致浏览器接收时长度不匹配,直接报错。

Content-Length介绍

Content-Length 的工作流程涉及客户端与服务器之间的  "长度协商" ,但具体判断逻辑分场景,核心是  "客户端告知服务器自己要发多少数据"  和  "服务器告知客户端会收到多少数据" ,两者是独立的。

1. 基本流程:客户端与服务器的双向长度告知

Content-Length 是 HTTP 协议中用于标识  "实体数据长度" (字节数)的响应头,工作流程分两种方向:

(1)客户端 → 服务器(请求阶段)

当客户端向服务器发送数据时(如 POST 表单、上传文件),可能会在请求头中携带 Content-Length,用于告知服务器:
"我接下来要发的数据总共有 X 字节,请准备接收这么多"
例如:

POST /upload HTTP/1.1
Host: example.com
Content-Length: 1024  # 客户端告知:本次请求体有1024字节
Content-Type: application/octet-stream

[1024字节的二进制数据]

2)服务器 → 客户端(响应阶段)

当服务器向客户端返回数据时(如返回文件、接口响应),会在响应头中携带 Content-Length,用于告知客户端:
"我接下来要返回的数据总共有 Y 字节,请准备接收这么多"
例如:

HTTP/1.1 200 OK
Content-Length: 2048  # 服务器告知:本次响应体有2048字节
Content-Type: application/pdf

[2048字节的PDF二进制数据]

此时客户端(浏览器)会根据这个值来判断:

  • 是否已接收完整数据(如果实际收到的字节数≠Content-Length,会判定为 "响应无效",触发 ERR_INVALID_HTTP_RESPONSE 等错误)。

2. 关键问题:为什么代理会导致 Content-Length 不匹配?

在你的场景中,问题出在 服务器→客户端的响应阶段,且与 Vite 代理(基于 http-proxy)的处理逻辑相关:

  • 线上环境:客户端直接与服务器通信,服务器返回的 Content-Length 是准确的(或使用 Transfer-Encoding: chunked 分块传输,不携带 Content-Length),因此浏览器能正常接收。
  • 本地代理环境
    当服务器返回二进制流(尤其是大文件)时,可能使用 chunked 分块传输(不包含 Content-Length)。
    但 http-proxy 代理在转发时,会 自动合并分块数据并计算总长度,然后 "好心" 地添加一个 Content-Length 头。
    然而,这个自动计算的长度可能与实际二进制流的真实长度不匹配(比如二进制数据中有特殊字符被转换),导致浏览器校验失败。

3. 总结

Content-Length 的核心作用是  "预先告知数据总长度" ,确保通信双方能校验数据完整性。
你的问题中,代理的自动计算破坏了这种完整性校验,因此删除 Content-Length 后,浏览器会通过分块传输自然接收完整数据,反而避免了错误。