Feign远程调用时丢失请求头分析
一 背景
最近在学习大型分布式商城系统时,用到了feign远程调用。集群会话使用的是spring session,使用ThreadLocal保存当前用户登录状态,在一次使用feign调用中,在被调用的服务(购物车服务)中无法获取到当前线程用户数据(从订单服务调用购物车服务),使用断点检查发现,购物车服务的请求头中并没有包含对应的会话信息(cookie)。
springboot版本为2.1.8
openfeign版本为2.1.3 不同版本源码会有差异,但大致流程应差不多。
二 分析
1.业务流程
1. 流程图
2. feign源码分析
第一步进入
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!"equals".equals(method.getName())) {
if ("hashCode".equals(method.getName())) {
return this.hashCode();
} else {
return "toString".equals(method.getName()) ? this.toString() :
//真正执行模块,其他都是一些判断
((MethodHandler)this.dispatch.get(method)).invoke(args);
}
} else {
try {
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return this.equals(otherHandler);
} catch (IllegalArgumentException var5) {
return false;
}
}
}
第二步
public Object invoke(Object[] argv) throws Throwable {
//构建一个新的requestTemplate,该request请求头为空
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while(true) {
try {
//执行请求块,使用上面创建的requestTemplate进行请求
return this.executeAndDecode(template);
} catch (RetryableException var8) {
.......省略部分源码
}
}
}
第三步: 查看如何创建新的requestTemplate
public RequestTemplate create(Object[] argv) {
//this.metadata.template() 从元数据中获取一个template,实际就是new RequestTemplate()
//RequestTemplate.from() 对默认的template进行处理,返回处理后的requestTemplate
RequestTemplate mutable = RequestTemplate.from(this.metadata.template());
if (this.metadata.urlIndex() != null) {
int urlIndex = this.metadata.urlIndex();
Util.checkArgument(argv[urlIndex] != null, "URI parameter %s was null", new Object[]{urlIndex});
mutable.target(String.valueOf(argv[urlIndex]));
}
......省略部分源码
}
第四步:查看处理逻辑 RequestTemplate.from(this.metadata.template())
public static RequestTemplate from(RequestTemplate requestTemplate) {
//通过原requestTemplate的参数生成一个新的RequestTemplate
RequestTemplate template = new RequestTemplate(requestTemplate.target, requestTemplate.fragment, requestTemplate.uriTemplate, requestTemplate.method, requestTemplate.charset, requestTemplate.body, requestTemplate.decodeSlash, requestTemplate.collectionFormat);
//处理传入进来requestTemplate的查询参数
if (!requestTemplate.queries().isEmpty()) {
template.queries.putAll(requestTemplate.queries);
}
//处理传入进来requestTemplate的请求头,如果不为空,就放到新的template中去
if (!requestTemplate.headers().isEmpty()) {
template.headers.putAll(requestTemplate.headers);
}
return template;
}
由第四步源码可知,其请求头和查询参数都是在第三步从元数据中获取的RequestTemplate中来的。因此,具体分析第三步中的this.metadata.template(),发现template的无参构造方法如下
public RequestTemplate() {
//也是空的
this.headers = new TreeMap(String.CASE_INSENSITIVE_ORDER);
this.resolved = false;
this.charset = Util.UTF_8;
this.body = Body.empty();
this.decodeSlash = true;
this.collectionFormat = CollectionFormat.EXPLODED;
}
由此可得出结论,第二步执行request请求方法中,获取到得requestTemplate请求头为空。因此,使用feign进行远程调用时,请求头为空的原因找到了。
三 处理
1. 分析feign的执行方法
this.executeAndDecode(template),执行和解码
Object executeAndDecode(RequestTemplate template) throws Throwable {
//将template处理成request
Request request = this.targetRequest(template);
if (this.logLevel != Level.NONE) {
this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
}
......省略部分源码
}
this.targetRequest(template)//获取目标request
Request targetRequest(RequestTemplate template) {
//获取request拦截器(默认情况下,是空的)
Iterator var2 = this.requestInterceptors.iterator();
while(var2.hasNext()) {
//遍历拦截器,执行拦截器的apply方法
RequestInterceptor interceptor = (RequestInterceptor)var2.next();
interceptor.apply(template);
}
return this.target.apply(template);
}
RequestInterceptor是一个接口,interceptor.apply(template) 也是一个抽象方法,参考BasicAuthRequestInterceptor实现的方法
public class BasicAuthRequestInterceptor implements RequestInterceptor {
private final String headerValue;
......省略部分源码
//RequestInterceptor拦截器的apply方法,可以修改template的请求头
public void apply(RequestTemplate template) {
template.header("Authorization", new String[]{this.headerValue});
}
}
总结: 我们在RequestInterceptor中加入自定义的request拦截器,这样就能修改请求头了。
2. 处理
先看分析中this.requestInterceptors(request拦截器是如何获取的)
//省略部分源码,只看重要部分 final class SynchronousMethodHandler implements MethodHandler { //私有属性 private final List<RequestInterceptor> requestInterceptors; //在构造的时候会传入request拦截器 private SynchronousMethodHandler(Target<?> target, Client client, Retryer retryer, this.requestInterceptors = checkNotNull(requestInterceptors, "requestInterceptors for %s", target); } //静态内部类 Factory static class Factory { ...... private final List<RequestInterceptor> requestInterceptors; ..... Factory(Client client, Retryer retryer, List<RequestInterceptor> requestInterceptors, Logger logger, Logger.Level logLevel, boolean decode404, boolean closeAfterDecode, ExceptionPropagationPolicy propagationPolicy) { .... this.requestInterceptors = checkNotNull(requestInterceptors, "requestInterceptors"); .... } //有一个创建MethodHandler方法 public MethodHandler create(Target<?> target, MethodMetadata md, RequestTemplate.Factory buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder) { return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger, logLevel, md, buildTemplateFromArgs, options, decoder, errorDecoder, decode404, closeAfterDecode, propagationPolicy); } }
查看谁在调用Factory
public Feign build() { //此处没有传参,requestInterceptors来自Builder SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); }
查看Builder
public static class Builder { private final List<RequestInterceptor> requestInterceptors = new ArrayList<RequestInterceptor>(); ......省略部分源码 /** * Adds a single request interceptor to the builder. */ public Builder requestInterceptor(RequestInterceptor requestInterceptor) { this.requestInterceptors.add(requestInterceptor); return this; } /** * Sets the full set of request interceptors for the builder, overwriting any previous * interceptors. */ public Builder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) { this.requestInterceptors.clear(); for (RequestInterceptor requestInterceptor : requestInterceptors) { this.requestInterceptors.add(requestInterceptor); } return this; } }
通过Builder requestInterceptor(RequestInterceptor requestInterceptor)方法分析到,RequestInterceptor是从容器中获取的。详细源码查阅即知
3.配置
查看RequestInterceptor接口的注释可知,可以配置零个或多个RequestInterceptor拦截器,通过apply方法修改RequestTemplate。因此自定义一个request拦截器实现功能即可。
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Component
public class MyFeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
//RequestContextHolder上下文,可以获取到当前线程的相关内容,详细可以百度
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String cookie = request.getHeader("Cookie");
template.header("Cookie",cookie);
}
}
- 本文标签: Spring Boot
- 本文链接: https://www.tianyajuanke.top/article/71
- 版权声明: 本文由吴沛芙原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权