使用RestTemplate请求Netty Server时线程阻塞问题排查
解决RestTemplate(Netty4ClientHttpRequestFactory)请求阻塞与Netty服务端无法获取请求体的问题
你的问题核心是客户端请求体未正确发送+服务端未处理HTTP请求分块聚合,导致服务端拿不到完整请求体、无法返回响应,最终客户端线程一直卡在request.execute()处阻塞。我来帮你一步步解决:
一、客户端代码的问题分析与修复
你的doSendByPOST方法存在几个关键错误:
- 错误地把请求体放到了
parts参数(这是URL查询参数),而非HttpEntity的body中 - 手动计算
Content-Length容易出错,应该让Spring自动处理 - 重复创建
AsyncRestTemplate和RestTemplate,配置逻辑混乱
修复后的客户端代码
public class SimpleClient { private final String URL; private final AsyncRestTemplate rest; private final RestTemplate restTemplate; private final ObjectMapper objectMapper = new ObjectMapper(); public SimpleClient(String url) { this.URL = url; Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(); try { nettyFactory.setSslContext(SslContextBuilder.forClient().build()); } catch (SSLException e) { e.printStackTrace(); } // 初始化一次即可,无需重复创建 rest = new AsyncRestTemplate(nettyFactory); restTemplate = new RestTemplate(nettyFactory); // 统一配置消息转换器,只需设置一次 List<HttpMessageConverter<?>> messageConvertors = new ArrayList<>(); messageConvertors.add(new MappingJackson2HttpMessageConverter(objectMapper)); rest.setMessageConverters(messageConvertors); restTemplate.setMessageConverters(messageConvertors); } public ResponseEntity<ResponseData> doSendByPOST(RequestData data, Class<ResponseData> clazz) { HttpHeaders headers = new HttpHeaders(); headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); // 正确将请求体放入HttpEntity,Spring会自动处理序列化和Content-Length HttpEntity<RequestData> request = new HttpEntity<>(data, headers); // exchange的最后一个参数是URL路径变量,不是请求体!这里不需要传parts ListenableFuture<ResponseEntity<ResponseData>> entity = rest.exchange( this.URL, HttpMethod.POST, request, clazz ); return extractResponseEntity(entity); } // 保留你的extractResponseEntity方法... }
二、服务端代码的问题分析与修复
Netty中HttpRequest仅代表请求头,HttpContent是请求体的分块(可能有多个),必须用HttpObjectAggregator将它们聚合成FullHttpRequest,否则你无法拿到完整请求体。另外你的channelRead逻辑存在变量未初始化、响应发送不完整的问题。
修复步骤:
- 在服务端
ChannelInitializer中添加HttpObjectAggregator处理器:
@Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); // 添加HTTP编解码器 p.addLast(new HttpRequestDecoder()); p.addLast(new HttpResponseEncoder()); // 聚合HTTP请求,最大请求体大小根据业务调整(示例为10MB) p.addLast(new HttpObjectAggregator(10 * 1024 * 1024)); // 添加你的自定义处理器 p.addLast(new YourCustomChannelHandler()); }
- 修改
channelRead方法,直接处理聚合后的FullHttpRequest:
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof FullHttpRequest) { FullHttpRequest fullRequest = (FullHttpRequest) msg; // 处理100 Continue请求 if (HttpUtil.is100ContinueExpected(fullRequest)) { ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE)); } boolean keepAlive = HttpUtil.isKeepAlive(fullRequest); // 读取完整请求体 ByteBuf content = fullRequest.content(); byte[] bodyBytes = new byte[content.readableBytes()]; content.readBytes(bodyBytes); String bodyStr = new String(bodyBytes, StandardCharsets.UTF_8); // 反序列化请求体并处理业务逻辑 RequestData requestData = objectMapper.readValue(bodyStr, RequestData.class); FullHttpResponse response = frontController.dispatchRequestAndHandle(requestData); if (response == null) { response = prepareDefaultResponse(); } // 设置响应头 HttpUtil.setContentLength(response, response.content().readableBytes()); response.headers().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); if (keepAlive) { response.headers().set(HttpHeaders.CONNECTION, HttpHeaderValues.KEEP_ALIVE); } else { response.headers().set(HttpHeaders.CONNECTION, HttpHeaderValues.CLOSE); } // 发送响应 ChannelFuture future = ctx.writeAndFlush(response); if (!keepAlive) { future.addListener(ChannelFutureListener.CLOSE); } // 释放请求资源 fullRequest.release(); } else { // 非FullHttpRequest类型,直接释放资源 ReferenceCountUtil.release(msg); } }
三、关键注意事项
- 客户端:不要手动处理
Content-Length,Spring的消息转换器会自动完成序列化和请求头设置 - 服务端:必须添加
HttpObjectAggregator才能获取包含头和体的完整HTTP请求 - 确保客户端和服务端的
ObjectMapper配置一致,避免序列化/反序列化错误 - 客户端的
extractResponseEntity方法要正确处理ListenableFuture,避免额外线程问题
这样修改后,客户端会正确发送带请求体的POST请求,服务端能拿到完整请求体并返回响应,客户端的阻塞问题自然解决。
内容的提问来源于stack exchange,提问作者Gurkan İlleez
相关产品推荐
相关产品推荐

