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

 更新时间:2024年09月29日 15:02:12   作者:头未秃  
本文介绍了在服务端项目中如何解决资源访问限制问题,首先介绍了问题的产生,然后详细解析了源码,最后提出了解决方案,解决方案包括同步和异步两种,同步时直接向Spring容器注入RequestInterceptor拦截器

前言

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

问题

有时候,我们先行登录,然后访问服务A的某个方法,请求头中携带cookie,标识我们已经登录。

但若是我们访问的目标方法在执行过程中使用feign进行远程调用服务B,而服务B也要先判断登录状态,我们可能发现服务B会调用失败,或者说拿不到数据,理由是服务B认为我们并未登录。

而这时,如果我们直接从浏览器访问服务B的这个方法却能得到一个成功的响应。

查看源码

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

2、执行真正的远程调用的方法

3、根据模板template来创建请求request(默认的情况feign是不会帮我们把原请求头参数复制到新请求的请求头上的),然后进行客户端client调用

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

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

总结:

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

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

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

解决

1、同步时

直接向spring容器注入RequestInterceptor拦截器即可

/**
* Feign
* @return
*/
@Bean
public RequestInterceptor requestInterceptor(){
   return requestTemplate -> {
       //1、从RequestContextHolder获取原始请求的请求数据(请求参数、请求头等)
       ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
       HttpServletRequest request = attributes.getRequest();
       //2、同步请求头数据
       Enumeration<String> headerNames = request.getHeaderNames();
       if (headerNames != null) {
           while (headerNames.hasMoreElements()) {
               String name = headerNames.nextElement();
               String values = request.getHeader(name);
               // 跳过 content-length,没有跳过会导致请求参数无法接收
               if (HttpHeaderConsts.CONTENT_LENGTH.equalsIgnoreCase(name)) {
                   continue;
               }
               requestTemplate.header(name, values);
           }
       }
   };
}

2、异步时

如果请求中使用了异步,也就是多线程,就算配置了上面的配置也会导致feign请求头丢失,因为请求头信息是通过threadLocal保存的,也就是只有在同一个线程中才能使用请求中的请求头并且同步初始请求头信息到feign请求中

解决办法

在异步调用时主动将请求头覆盖到异步线程的请求上下文中

// 通过请求上下文获取请求信息
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
    // 远程查询所有的收货地址列表
    // 在该线程中添加请求信息
    RequestContextHolder.setRequestAttributes(requestAttributes);
    List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
    orderConfirmVo.setAdress(address);
}, executor);
​
CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
    // 远程查询购物车所有选中的购物想
    // 在该线程中添加请求信息
    RequestContextHolder.setRequestAttributes(requestAttributes);
    List<OrderItemVo> currentUserItems = cartFeignService.getCurrentUserItems();
    orderConfirmVo.setItems(currentUserItems);
}, executor);

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • MyBatis处理mysql主键自动增长出现的不连续问题解决

    MyBatis处理mysql主键自动增长出现的不连续问题解决

    本文主要介绍了MyBatis处理mysql主键自动增长出现的不连续问题解决,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • 深入理解Java设计模式之组合模式

    深入理解Java设计模式之组合模式

    这篇文章主要介绍了JAVA设计模式之组合模式的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下
    2021-11-11
  • SpringCloud Eureka搭建的方法步骤

    SpringCloud Eureka搭建的方法步骤

    这篇文章主要介绍了SpringCloud Eureka搭建的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-01-01
  • SpringCloud GateWay网关示例代码详解

    SpringCloud GateWay网关示例代码详解

    这篇文章主要介绍了SpringCloud GateWay网关,Spring cloud Gateway的功能很多很强大,文中提到了Spring Cloud Gateway中几个重要的概念,结合实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2022-04-04
  • springboot @ConfigurationProperties和@PropertySource的区别

    springboot @ConfigurationProperties和@PropertySource的区别

    这篇文章主要介绍了springboot @ConfigurationProperties和@PropertySource的区别,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • Netty网络编程实战之开发聊天室功能

    Netty网络编程实战之开发聊天室功能

    这篇文章主要为大家详细介绍了如何利用Netty实现聊天室功能,文中的示例代码讲解详细,对我们学习Netty网络编程有一定帮助,需要的可以参考一下
    2022-10-10
  • Java实现并查集示例详解

    Java实现并查集示例详解

    这篇文章主要通过一个题目示例为大家详细介绍Java如何实现并查集,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • MyBatis异常-Property ''configLocation'' not specified, using default MyBatis Configuration

    MyBatis异常-Property ''configLocation'' not specified, using d

    今天小编就为大家分享一篇关于MyBatis异常-Property 'configLocation' not specified, using default MyBatis Configuration,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • springboot集成RocketMQ过程及使用示例详解

    springboot集成RocketMQ过程及使用示例详解

    这篇文章主要为大家介绍了springboot集成RocketMQ过程及使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • Java+Swing实现中国象棋游戏

    Java+Swing实现中国象棋游戏

    这篇文章将通过Java+Swing实现经典的中国象棋游戏。文中可以实现开始游戏,悔棋,退出等功能。感兴趣的小伙伴可以跟随小编一起动手试一试
    2022-02-02

最新评论