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

网友投稿 357 2022-11-15


SpringCloud Feign转发请求头(防止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 {

http:// 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


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Spring基于advisor配置aop过程解析
下一篇:SpringBoot 导出数据生成excel文件返回方式
相关文章

 发表评论

暂时没有评论,来抢沙发吧~