You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring代理出现net::ERR_HTTP2_PROTOCOL_ERROR问题排查与优化建议

问题排查与解决方案

1. 响应头不符合HTTP/2规范(核心诱因)

Nginx启用HTTPS时通常默认开启HTTP/2,而HTTP/2对响应头的规范比HTTP/1.1严格得多,这是最常见的触发原因:

  • 响应头包含非ASCII字符(比如中文命名的头字段)
  • 同时存在Transfer-Encoding和Content-Length头(HTTP/2不允许这种冲突)
  • 重复的同名响应头

排查与修复:

检查代理代码是否直接透传目标服务的所有响应头,或者手动添加了冲突字段。比如目标服务返回Transfer-Encoding: chunked,而你的代码又手动设置了Content-Length,会直接触发HTTP/2协议错误。

示例修复代码(过滤并规范响应头):

// 转发响应时清理不规范的头字段
HttpHeaders targetHeaders = targetResponse.getHeaders();
HttpHeaders proxyHeaders = new HttpHeaders();

targetHeaders.forEach((key, values) -> {
    // 跳过会导致HTTP/2冲突的传输编码头,交给Spring自动处理
    if (key.equalsIgnoreCase("Transfer-Encoding") || key.equalsIgnoreCase("Content-Length")) {
        return;
    }
    // 仅保留符合ASCII规范的头名
    if (key.matches("[a-zA-Z0-9-]+")) {
        proxyHeaders.put(key, values);
    }
});

return ResponseEntity.status(targetResponse.getStatusCode())
        .headers(proxyHeaders)
        .body(targetResponse.getBody());

2. 响应体流未完整传输

HTTP/2对响应体的完整性要求极高,如果代理代码未正确处理流,会导致浏览器认为响应中断:

  • 未完全读取目标服务的响应体就关闭了流
  • 异步转发时流被提前释放
  • 使用InputStream时未全量复制到输出流

排查与修复:

避免直接返回未处理的InputStreamResource,确保响应体被完整读取或由框架自动处理。示例代码(用WebClient安全转发):

WebClient webClient = WebClient.create();
return webClient.method(HttpMethod.valueOf(request.getMethod()))
        .uri(targetUrl)
        .headers(headers -> headers.putAll(request.getHeaders()))
        .body(BodyInserters.fromRequest(request))
        .exchangeToMono(response -> {
            // 复用上面的响应头过滤逻辑
            HttpHeaders filteredHeaders = new HttpHeaders();
            response.headers().asHttpHeaders().forEach((key, values) -> {
                if (!key.equalsIgnoreCase("Transfer-Encoding") && !key.equalsIgnoreCase("Content-Length")) {
                    filteredHeaders.put(key, values);
                }
            });
            // 将响应体转为byte数组,确保全量传输
            return Mono.just(ResponseEntity.status(response.statusCode())
                    .headers(filteredHeaders)
                    .body(response.bodyToMono(byte[].class).block()));
        }).block();

3. Nginx HTTP/2配置冲突

即使后端转发成功,Nginx的配置也可能破坏HTTP/2的传输规则:

  • 关闭了proxy_buffering,导致后端响应未完全接收就转发给浏览器
  • 设置了proxy_set_header Connection "close",破坏HTTP/2长连接机制
  • 缓冲区过小导致响应被截断

排查与修复:

修改Nginx的server配置块:

server {
    listen 443 ssl http2;
    # 确保缓冲开启,让Nginx完整接收后端响应再转发
    proxy_buffering on;
    # 移除破坏长连接的配置
    # proxy_set_header Connection "close";
    # 调整缓冲区大小,避免响应截断
    proxy_buffer_size 16k;
    proxy_buffers 4 64k;
    proxy_busy_buffers_size 128k;
}

4. Spring消息转换器不兼容

Spring默认的消息转换器可能在HTTP/2环境下处理二进制响应(如文件、图片)时出错,或字符编码不统一导致乱码。

排查与修复:

优先配置二进制消息转换器,统一字符编码:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 优先处理二进制响应
        ByteArrayHttpMessageConverter byteArrayConverter = new ByteArrayHttpMessageConverter();
        converters.add(0, byteArrayConverter);
        // 统一UTF-8字符编码
        StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
        converters.add(stringConverter);
    }
}

快速验证步骤

  1. 直接用curl访问后端Spring代理的HTTPS地址(绕过Nginx),确认后端响应是否正常
  2. 查看Nginx的access.log和error.log,定位响应阶段的异常
  3. 用Chrome DevTools查看请求的响应头,检查是否有非ASCII或冲突字段
  4. 临时禁用Nginx的HTTP/2(将listen 443 ssl http2改为listen 443 ssl),验证是否为HTTP/2协议问题

内容的提问来源于stack exchange,提问作者Troncador

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 06:06:28