【前车之鉴】好毒啊 RestTemplate 整合HttpClient 请求参数正确,还会出现400错误-HttpProcessor拦截器使用不当有剧毒!

先给结论

创建HttpClient时,自定义HttpProcessor 默认会因为丢失必要的请求头而导致请求400,BadRequest.

org.springframework.web.client.HttpClientErrorException$BadRequest: 400 : [<!doctype html><html lang="en"><head><title>HTTP Status 400Bad Request</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;... (435 bytes)]
	at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:101)
	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:184)

报错不可怕,让人🤮的是根本从错误看不出来为啥,只能无脑试错找到疑似目标并最终确定~~~

创建HttpClient核心代码配置如下

        HttpClientBuilder builder = HttpClients.custom()
                .setDefaultHeaders() // 设置了HttpProcessor 这个配置也失效了
                .setDefaultRequestConfig(requestConfig)
                .setRetryHandler(retryHandler)
                .setHttpProcessor(instance)
.setKeepAliveStrategy(getCustomConnectionKeepAliveStrategy());

自定义HttpProcessor【包含解决方案】

public class MetricHttpProcessor implements HttpProcessor {

    private static MetricHttpProcessor processor;

    public static MetricHttpProcessor getInstance() {
        if (Objects.isNull(processor)) {
            processor = new MetricHttpProcessor();
        }
        return processor;
    }
		
    private final List<HttpRequestInterceptor> MIN_LIMIT_REQUEST = new ArrayList<>();

    private MetricHttpProcessor() {
        MIN_LIMIT_REQUEST.add(new RequestTargetHost());
        MIN_LIMIT_REQUEST.add(new RequestUserAgent(getUserAgent())); // 非必须,调用三方建议一定加上,内网可省
        MIN_LIMIT_REQUEST.add(new RequestContent());
    }

    private String getUserAgent() {
        String userAgentCopy = System.getProperty("http.agent");
        if (userAgentCopy == null) {
            userAgentCopy = VersionInfo.getUserAgent("Apache-HttpClient",
                    "org.apache.http.client", getClass());
        }
        return userAgentCopy;
    }

    @Override
    public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
    	// 自定义配置
        context.setAttribute(HTTP_NAME_SPACE, System.currentTimeMillis());
        context.setAttribute(HTTP_URL, getUrl(request));
        // 解决400核心
        for (HttpRequestInterceptor httpRequestInterceptor : MIN_LIMIT_REQUEST) {
            httpRequestInterceptor.process(request, context);
        }
    }

原因探究

最近对自己的项目玩花活儿惹了一身“骚”, 以往我们使用RestTemplate 多半基于OkHttpClient ,我今天追源码看看RestTemplate 发现是可以定制化任何RPC框架包括不限于:HttpClient、OkHttpClient、NettyClient等等, 于是想着替换下我之前底层封装好HttpClient工具类,直接复用试试看,发现老项目报错如上所示

这过程最难受就是老项目一直没问题,但是接入底层创建HttpClient组件就会报400,无奈使用最基础的配置如下

    CloseableHttpClient client = HttpClients.custom().build()

居然可以请求成功, 于是猜想应该是哪个自定义配置干扰了请求,只好一个个控制变量法排除,最终确认是因为配置HttpProcessor如果不做额外处理一定会产生400的错误

通过Debug对比设置HttpProcessor 对请求差异如下表格:

设置HttpProcessor没有HttpProcessor
在这里插入图片描述在这里插入图片描述

你会发现根本原因是 如果设置自定义会丢失最基本请求头:Host,这个决定了400的根本原因,实时上Content-Length 在某些服务器也会做校验,更确切的说在http1.1+才会如此严格,不过现在基本都是1.1、1.2哈哈

最后看看源码就进一步定案:
在这里插入图片描述


再说风云

  1. 按开头配置方式,将必需的配置Host和Content-Length 请求拦截器也追加上,然后再去定制你自己的逻辑

注意这个是可调用执行的最基本配置,并且我只验证了web中的POST、GET请求,如果涉及text/html 等请求格式,不确定是否需要增加新的请求拦截器,一个简单的方式是拷贝底层兜底逻辑呗,缺啥补啥

  1. 抛弃HttpProcessor,单独添加请求拦截器和响应拦截器,如下
           
HttpClients.custom().addInterceptorLast(xxxRequestInterceptor).addInterceptorFirst(xxxResponseInterceptor);

本质还是因为博主偷懒,想用一个类同时处理请求和响应拦截器 少写点代码,无奈这么坑爹; 不过祸兮福所倚,真因为这次不经意的尝试,让博主对HttpClient底层有了更加深刻的认识

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值