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

使用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逻辑存在变量未初始化、响应发送不完整的问题。

修复步骤:

  1. 在服务端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());
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:53:15