原创

Feign远程调用时丢失请求头分析

温馨提示:
本文最后更新于 2022年11月03日,已超过 911 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

一 背景

最近在学习大型分布式商城系统时,用到了feign远程调用。集群会话使用的是spring session,使用ThreadLocal保存当前用户登录状态,在一次使用feign调用中,在被调用的服务(购物车服务)中无法获取到当前线程用户数据(从订单服务调用购物车服务),使用断点检查发现,购物车服务的请求头中并没有包含对应的会话信息(cookie)。

springboot版本为2.1.8

openfeign版本为2.1.3 不同版本源码会有差异,但大致流程应差不多。

二 分析

1.业务流程

1. 流程图

image-20221103115236904

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. 处理

  1. 先看分析中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);
        }
      }
    
  2. 查看谁在调用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);
        }
    
  3. 查看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;
        }
    
     }
    
  4. 通过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);
    }
}
正文到此结束