背景: 公司多个服务间sdk调用, 需要传递token等header信息鉴权, feign在调用其他服务时, 使用GET请求发现一下正常(200), 一下异常(400), 当时觉得很诡异, 其他组调用都没问题, 找了半天, 在此记录下:
异常的请求路径打印出来发现没有任何问题, 其他博客所说的 header信息过长, get参数过长, 参数接收注解等等都没问题(因为存在正常调用的时候, 不是全部异常)
打印异常如下:
feign.FeignException$BadRequest: [400 ] during [GET] to [http://服务调用的完整路径] [Feign调用的方法(String)]: [<!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 feign.FeignException.clientErrorStatus(FeignException.java:195) ~[feign-core-10.10.1.jar:na]
at feign.FeignException.errorStatus(FeignException.java:177) ~[feign-core-10.10.1.jar:na]
at feign.FeignException.errorStatus(FeignException.java:169) ~[feign-core-10.10.1.jar:na]
排查过程:
- 检查请求参数是否存在特殊字符, 是否存在转义问题(无)
- 检查目标服务参数接收注解是否正常 (废话, 别人用的好好的, 况且又不是全部异常)
- 检查springboot版本号 (一度怀疑是框架bug, 更换版本号没有解决)
- 检查是否所有请求都存在问题 (发现只是被调用方的GET请求有问题, POST正常, 所以临时拜托小伙伴替换成POST方法, 不可能全部替换, 接着找...)
- 检查请求信息 (200 和 400 的请求信息完全一样, 这个目前还是没有找到具体问题...但是不影响)
- 尝试不同的请求调用链, 即
- A服务的Get请求 -> feign -> B服务的Get (完全正常)
- A服务的Get请求 -> feign -> B服务的Post (完全正常)
- A服务的Post请求 -> feign -> B服务的Get (问题复现)
- A服务的Post请求 -> feign -> B服务的Post (完全正常)
突然想到前边提到, header信息传递时, 定义了一个feign拦截器:
@Configuration
public class FeignConfiguration implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
requestTemplate.header(name, values);
}
}
}
}
将A服务的header信息全部传递至B服务, 那么A服务的POST请求的header信息会携带至B服务的GET请求上, 当POST请求存在body请求体时, header会带上
content-type:application/json
此时, 底层okhttp解析该请求时, 就会存在异常!
问题找到了,解决就简单了, feign拦截器里只过滤自己需要的header信息即可, 不要全部传递!!!!!!!
@Configuration
@Slf4j
public class FeignConfiguration implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
if (checkEssential(name)) {
requestTemplate.header(name, values);
}
}
}
}
/**
* 请求头必要传递参数过滤,只携带必要请求头信息
*
* @param name
* @return
*/
private boolean checkEssential(String name) {
ArrayList<String> headers = Lists.newArrayList("productId", "topGroupId", "customId", "userId", "lesseeId");
return headers.stream().anyMatch(e -> e.equalsIgnoreCase(name));
}
}