先给结论
创建HttpClient时,自定义HttpProcessor 默认会因为丢失必要的请求头而导致请求400,BadRequest.
org.springframework.web.client.HttpClientErrorException$BadRequest: 400 : [<!doctype html><html lang="en"><head><title>HTTP Status 400 – Bad 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哈哈
最后看看源码就进一步定案:
再说风云
- 按开头配置方式,将必需的配置Host和Content-Length 请求拦截器也追加上,然后再去定制你自己的逻辑
注意这个是可调用执行的最基本配置,并且我只验证了web中的POST、GET请求,如果涉及text/html 等请求格式,不确定是否需要增加新的请求拦截器,一个简单的方式是拷贝底层兜底逻辑呗,缺啥补啥
- 抛弃HttpProcessor,单独添加请求拦截器和响应拦截器,如下
HttpClients.custom().addInterceptorLast(xxxRequestInterceptor).addInterceptorFirst(xxxResponseInterceptor);
本质还是因为博主偷懒,想用一个类同时处理请求和响应拦截器 少写点代码,无奈这么坑爹; 不过祸兮福所倚,真因为这次不经意的尝试,让博主对HttpClient底层有了更加深刻的认识