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); } }
快速验证步骤
- 直接用curl访问后端Spring代理的HTTPS地址(绕过Nginx),确认后端响应是否正常
- 查看Nginx的
access.log和error.log,定位响应阶段的异常 - 用Chrome DevTools查看请求的响应头,检查是否有非ASCII或冲突字段
- 临时禁用Nginx的HTTP/2(将
listen 443 ssl http2改为listen 443 ssl),验证是否为HTTP/2协议问题
内容的提问来源于stack exchange,提问作者Troncador
相关产品推荐
相关产品推荐

