Feign远程调用丢失请求头的问题

前言:

我们在写服务端项目的时候,总会限制对某些资源的访问,最常见的就是要求用户先登录才能访问资源,当用户登录后就会将此次会话信息保存进session,同时返回给浏览器指定的cookie键值,下次浏览器再次访问,请求头中就会携带这个cookie,我们也以次来识别用户的登录状态,做出正确响应。

问题:

有时候,我们先行登录,然后访问服务A的某个方法,请求头中携带cookie,标识我们已经登录。但若是我们访问的目标方法在执行过程中使用feign进行远程调用服务B,而服务B也要先判断登录状态,我们可能发现服务B会调用失败,或者说拿不到数据,理由是服务B认为我们并未登录。而这时,如果我们直接从浏览器访问服务B的这个方法却能得到一个成功的响应。

也就是说:

  • 浏览器—>服务A成功,服务A–>服务B失败;

  • 浏览器–>服务B成功

结合上面所说,服务A、B都会先判断用户登录状态,浏览器直接访问A、B时都会带上登录成功后保存的cookie,而服务A通过Feign远程调用服务B,却被认为未登录,显然,这部分请求头数据丢失。

原因:

使用feign进行远程调用时,首先判断目标方法类型,如果是 toString(),hashCode(),equals()这几个方法,那就是本地直接完成了;

 

真正的远程调用的流程:

先准备模板template来创建请求request,然后进行客户端client调用

 

在准备模板时,指定了url地址

 

创建请求时,会先调用所有的拦截器RequestInterceptors

可知,原先是没有拦截器RequestInterceptors的,所以,queries和headers是为0的

 

总结原因:

  1. 使用feign进行远程调用时,首先判断目标方法类型,如果是 toString(),hashCode(),equals()这几个方法,那就是本地直接完成了;

  2. 如果是真正的远程调用,执行的是 executeAndDecode 方法,在这个方法体内,会通过 targetRequest 方法创建出一个新的 request 对象,新的请求request是通过拦截器来返回请求模板template进而创建新的请求request,请求在远程调用之后请求头没有携带cookie信息,这个新的request会按照我们指定的参数和路径去发送请求,并获得响应结果。 这个新的request对象的请求头为空(所以会丢失原来的请求头)

 

解决:

自己向容器中注册一个拦截器RequestInterceptor,在这个拦截器中重写apply方法,在apply方法中把老请求的cookie复制到新request的请求头中,完成请求头的同步。(然后targetRequest 方法就会调用拦截器的apply方法,进行返回)

 

 

package com.atguigu.gulimall.order.config;

@Configuration
public class GulimallFeignConfig {
   /**
    * feign在远程调用之前会执行所有的RequestInterceptor拦截器
    *
    * @return
    */
   @Bean("requestInterceptor")
   public RequestInterceptor requestInterceptor() {
       return new RequestInterceptor() {
           @Override
           public void apply(RequestTemplate requestTemplate) {
               // 1、使用 RequestContextHolder 拿到请求数据,RequestContextHolder底层使用过线程共享数据 ThreadLocal<RequestAttributes>
               ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
               if (attributes != null) {
                   HttpServletRequest request = attributes.getRequest();
                   // 2、同步请求头数据,Cookie
                   String cookie = request.getHeader("Cookie");
                   // 给新请求同步了老请求的cookie
                   requestTemplate.header("Cookie", cookie);
              }
          }
      };
  }
}

总结:

  • feign远程调用,自己创建一个新的request对象,按照指定的路径和参数发起新的请求,并得到响应结果。但是这个新的request对象请求头为空,所以丢失了原先请求中的数据。

  • feign在创建新的request对象时,会调用一系列容器中的RequestInterceptor对象,执行其apply方法,对这个创建好的request进行增强,再去真正执行请求。但是默认情况下容器中不存在这类拦截器对象。

  • 我们可以自己向容器中注册一个RequestInterceptor,在其apply方法体内,获取到原始request,将其数据取出,赋值到新的request中,完成请求头的同步。RequestContextHolder借助ThreadLocal将每一个原始请求与tomcat为其分配的线程绑定,之后,只要在同个线程内,随时随地都可轻易获取到原始request。而我们是在apply方法体内,通过 RequestContextHolder.getRequestAttributes() 获取的。RequestContextHolder是借助ThreadLocal将每一个原始请求与tomcat为其分配的线程绑定,之后,只要在同个线程内,随时随地都可轻易获取到原始request。 

posted @ 2023-05-13 16:08  小謝同學  阅读(115)  评论(0编辑  收藏  举报