热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

SpringCloudFeign转发请求头(防止session失效)的解决方案

这篇文章主要介绍了SpringCloudFeign转发请求头(防止session失效)的解决方案,本文给大家分享两种解决方案供大家参考,感兴趣的朋友跟随小编一起看看吧

微服务开发中经常有这样的需求,公司自定义了通用的请求头,需要在微服务的调用链中转发,比如在请求头中加入了token,或者某个自定义的信息uniqueId,总之就是自定义的一个键值对的东东,A服务调用B服务,B服务调用C服务,这样通用的东西如何让他在一个调用链中不断地传递下去呢?以A服务为例:

方案1

最傻的办法,在程序中获取,调用B的时候再转发,怎么获取在Controller中国通过注解获取,或者通过request对象获取,这个不难,在请求B服务的时候,通过注解将值放进去即可;简代码如下:
获取:
@RequestMapping(value = "/api/test", method = RequestMethod.GET)
public String testFun(@RequestParam String name, @RequestHeader("uniqueId") String uniqueId) {
  if(uniqueId == null ){
     return "Must defined the uniqueId , it can not be null";
  }
  log.info(uniqueId, "begin testFun... ");
 return uniqueId;
}

然后A使用Feign调用B服务的时候,传过去:

@FeignClient(value = "DEMO-SERVICE")
public interface CallClient {

  /**
 * 访问DEMO-SERVICE服务的/api/test接口,通过注解将logId传递给下游服务
 */
 @RequestMapping(value = "/api/test", method = RequestMethod.GET)
  String callApiTest(@RequestParam(value = "name") String name, @RequestHeader(value = "uniqueId") String uniqueId);

}

方案弊端:毫无疑问,这方案不好,因为对代码有侵入,需要开发人员没次手动的获取和添加,因此舍弃

方案2

服务通过请求拦截器,在请求从A发送到B之后,在拦截器内将自己需要的东东加到请求头:
import com.intellif.log.LoggerUtilI;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;

/**
 * 自定义的请求头处理类,处理服务发送时的请求头;
 * 将服务接收到的请求头中的uniqueId和token字段取出来,并设置到新的请求头里面去转发给下游服务
 * 比如A服务收到一个请求,请求头里面包含uniqueId和token字段,A处理时会使用Feign客户端调用B服务
 * 那么uniqueId和token这两个字段就会添加到请求头中一并发给B服务;
 *
 * @author mozping
 * @version 1.0
 * @date 2018/6/27 14:13
 * @see FeignHeadConfiguration
 * @since JDK1.8
 */
@Configuration
public class FeignHeadConfiguration {
  private final LoggerUtilI logger = LoggerUtilI.getLogger(this.getClass().getName());

  @Bean
  public RequestInterceptor requestInterceptor() {
    return requestTemplate -> {
      ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
      if (attrs != null) {
        HttpServletRequest request = attrs.getRequest();
        Enumeration headerNames = request.getHeaderNames();
        if (headerNames != null) {
          while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            String value = request.getHeader(name);
            /**
             * 遍历请求头里面的属性字段,将logId和token添加到新的请求头中转发到下游服务
             * */
            if ("uniqueId".equalsIgnoreCase(name) || "token".equalsIgnoreCase(name)) {
              logger.debug("添加自定义请求头key:" + name + ",value:" + value);
              requestTemplate.header(name, value);
            } else {
              logger.debug("FeignHeadConfiguration", "非自定义请求头key:" + name + ",value:" + value + "不需要添加!");
            }
          }
        } else {
          logger.warn("FeignHeadConfiguration", "获取请求头失败!");
        }
      }
    };
  }

}

网上很多关于这种方法的博文或者资料,大同小异,但是有一个问题,在开启熔断器之后,这里的attrs就是null,因为熔断器默认的隔离策略是thread,也就是线程隔离,实际上接收到的对象和这个在发送给B不是一个线程,怎么办?有一个办法,修改隔离策略hystrix.command.default.execution.isolation.strategy=SEMAPHORE,改为信号量的隔离模式,但是不推荐,因为thread是默认的,而且要命的是信号量模式,熔断器不生效,比如设置了熔断时间hystrix.command.default.execution.isolation.semaphore.timeoutInMillisecOnds=5000,五秒,如果B服务里面sleep了10秒,非得等到B执行完毕再返回,因此这个方案也不可取;但是有什么办法可以在默认的Thread模式下让拦截器拿到上游服务的请求头?自定义策略:代码如下:

import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.netflix.hystrix.strategy.properties.HystrixProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 自定义Feign的隔离策略;
 * 在转发Feign的请求头的时候,如果开启了Hystrix,Hystrix的默认隔离策略是Thread(线程隔离策略),因此转发拦截器内是无法获取到请求的请求头信息的,可以修改默认隔离策略为信号量模式:hystrix.command.default.execution.isolation.strategy=SEMAPHORE,这样的话转发线程和请求线程实际上是一个线程,这并不是最好的解决方法,信号量模式也不是官方最为推荐的隔离策略;另一个解决方法就是自定义Hystrix的隔离策略,思路是将现有的并发策略作为新并发策略的成员变量,在新并发策略中,返回现有并发策略的线程池、Queue;将策略加到Spring容器即可;
 *
 * @author mozping
 * @version 1.0
 * @date 2018/7/5 9:08
 * @see FeignHystrixConcurrencyStrategyIntellif
 * @since JDK1.8
 */
@Component
public class FeignHystrixConcurrencyStrategyIntellif extends HystrixConcurrencyStrategy {

  private static final Logger log = LoggerFactory.getLogger(FeignHystrixConcurrencyStrategyIntellif.class);
  private HystrixConcurrencyStrategy delegate;

  public FeignHystrixConcurrencyStrategyIntellif() {
    try {
      this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
      if (this.delegate instanceof FeignHystrixConcurrencyStrategyIntellif) {
        // Welcome to singleton hell...
        return;
      }
      HystrixCommandExecutionHook commandExecutiOnHook=
          HystrixPlugins.getInstance().getCommandExecutionHook();
      HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
      HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
      HystrixPropertiesStrategy propertiesStrategy =
          HystrixPlugins.getInstance().getPropertiesStrategy();
      this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy);
      HystrixPlugins.reset();
      HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
      HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
      HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
      HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
      HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
    } catch (Exception e) {
      log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
    }
  }

  private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier,
                         HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) {
    if (log.isDebugEnabled()) {
      log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy ["
          + this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher ["
          + metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]");
      log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
    }
  }

  @Override
  public  Callable wrapCallable(Callable callable) {
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    return new WrappedCallable<>(callable, requestAttributes);
  }

  @Override
  public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                      HystrixProperty corePoolSize, HystrixProperty maximumPoolSize,
                      HystrixProperty keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {
    return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime,
        unit, workQueue);
  }

  @Override
  public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                      HystrixThreadPoolProperties threadPoolProperties) {
    return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties);
  }

  @Override
  public BlockingQueue getBlockingQueue(int maxQueueSize) {
    return this.delegate.getBlockingQueue(maxQueueSize);
  }

  @Override
  public  HystrixRequestVariable getRequestVariable(HystrixRequestVariableLifecycle rv) {
    return this.delegate.getRequestVariable(rv);
  }

  static class WrappedCallable implements Callable {
    private final Callable target;
    private final RequestAttributes requestAttributes;

    public WrappedCallable(Callable target, RequestAttributes requestAttributes) {
      this.target = target;
      this.requestAttributes = requestAttributes;
    }

    @Override
    public T call() throws Exception {
      try {
        RequestContextHolder.setRequestAttributes(requestAttributes);
        return target.call();
      } finally {
        RequestContextHolder.resetRequestAttributes();
      }
    }
  }
}

然后使用默认的熔断器隔离策略,也可以在拦截器内获取到上游服务的请求头信息了;
这里参考的博客,感谢这位大牛:https://blog.csdn.net/Crystalqy/article/details/79083857

到此这篇关于SpringCloud Feign转发请求头(防止session失效)的解决方案的文章就介绍到这了,更多相关SpringCloud Feign转发请求头内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了电流源并联合并的方法,以及谐振电路的原理。谐振电路具有很强的选频能力,通过将电感和电容连接在一起,电流和电压会产生震荡。谐振频率的大小取决于电感和电容的大小,而电路中的电阻会逐渐降低震荡的幅度。电阻和电容组成的电路中,当电容放完电后,电阻两端的电压为0,电流不再流过电容。然而,电感是一种特殊的器件,当有电流流过时,线圈会产生感应磁场,阻止电流的流动,从而使电流不会减小。 ... [详细]
  • 标题: ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
  • 本文介绍了在Docker容器技术中限制容器对CPU的使用的方法,包括使用-c参数设置容器的内存限额,以及通过设置工作线程数量来充分利用CPU资源。同时,还介绍了容器权重分配的情况,以及如何通过top命令查看容器在CPU资源紧张情况下的使用情况。 ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • position属性absolute与relative的区别和用法详解
    本文详细解读了CSS中的position属性absolute和relative的区别和用法。通过解释绝对定位和相对定位的含义,以及配合TOP、RIGHT、BOTTOM、LEFT进行定位的方式,说明了它们的特性和能够实现的效果。同时指出了在网页居中时使用Absolute可能会出错的原因,即以浏览器左上角为原始点进行定位,不会随着分辨率的变化而变化位置。最后总结了一些使用这两个属性的技巧。 ... [详细]
  • 开发笔记:Docker 上安装启动 MySQL
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Docker上安装启动MySQL相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
author-avatar
学海无涯
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有