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

2.

3.远程调用实际是new 了一个新的RequestTemplate,并没有把之前请求的header给设置进去

4.

5.可以通过拦截器,拦截请求,然后在给请求设置上,原请求的header信息即可

图解:

解决:
@Component
public class FeignInterceptor implements RequestInterceptor {
public FeignInterceptor() {
}
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
HttpServletRequest request = requestAttributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while(headerNames.hasMoreElements()) {
String name = (String)headerNames.nextElement();
String value = request.getHeader(name);
requestTemplate.header(name, new String[]{value});
}
}
}
}
}

Feign异步调用丢失请求头问题
还是上面的服务,只是改成异步调用
@Override
public OrderConfirmVo confirmOrder() {
OrderConfirmVo orderConfirmVo=new OrderConfirmVo();
//当前登录的用户
MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
//1.异步任务1 - 远程查询所有的收货地址列表
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
orderConfirmVo.setMemberAddressVos(address);
}, threadPoolExecutor);
//2.异步任务2 - 远程查询购物车所有选中的购物项
CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.runAsync(() -> {
List<OrderItemVo> currentCartItems = cartFeignService.getCurrentCartItems();
orderConfirmVo.setItems(currentCartItems);
}, threadPoolExecutor);
//feign在远程调用之前要构造请求,调用很多的拦截器
try {
//等待所有任务完成
CompletableFuture.allOf(voidCompletableFuture,voidCompletableFuture1).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//3.查询用户积分
Integer integration = memberResponseVo.getIntegration();
orderConfirmVo.setIntegration(integration);
//4.其他数据自动计算
return orderConfirmVo;
}
改为了异步,发现之前的FeignConfig配置类在获取ServletRequestAttributes时,获取不到了

原因:旧请求是在线程A上,当使用异步时,是新开了一条线程去执行,新开的线程自然没有线程A的各种属性以及数据
图解:

解决:将旧线程中的属性RequestAttributes,设置到新线程中即可
@Override
public OrderConfirmVo confirmOrder() {
OrderConfirmVo orderConfirmVo=new OrderConfirmVo();
//当前登录的用户
MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
//解决:--------------------------------------
//获取旧线程的RequestAttributes
//RequestContextHolder底层使用的是ThreadLocal
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//1.异步任务1 - 远程查询所有的收货地址列表
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
//解决:--------------------------------------
//将旧线程中的RequestAttributes,设置到线程中的RequestContextHolder里
RequestContextHolder.setRequestAttributes(requestAttributes);
List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
orderConfirmVo.setMemberAddressVos(address);
}, threadPoolExecutor);
//2.异步任务2 - 远程查询购物车所有选中的购物项
CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.runAsync(() -> {
//解决:--------------------------------------
//将旧线程中的RequestAttributes,设置到线程中的RequestContextHolder里
RequestContextHolder.setRequestAttributes(requestAttributes);
List<OrderItemVo> currentCartItems = cartFeignService.getCurrentCartItems();
orderConfirmVo.setItems(currentCartItems);
}, threadPoolExecutor);
//feign在远程调用之前要构造请求,调用很多的拦截器
try {
//等待所有任务完成
CompletableFuture.allOf(voidCompletableFuture,voidCompletableFuture1).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//3.查询用户积分
Integer integration = memberResponseVo.getIntegration();
orderConfirmVo.setIntegration(integration);
//4.其他数据自动计算
return orderConfirmVo;
}
配置类加个非空判断即可
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor requestInterceptor(){
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
//1.RequestContextHolder拿到当前请求的数据,相当与拿到controller入参的HttpServletRequest
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes!=null){
//老请求
HttpServletRequest request = requestAttributes.getRequest();
if (request!=null){
//2.同步请求头信息->cookie
String cookie = request.getHeader("Cookie");
requestTemplate.header("Cookie",cookie);
}
}
}
};
}
}
再次测试:

ok,解决
hystrix线程策略导致FeignInterceptor失效问题
理论上RequestInterceptor处理了header问题就没啥问题了。
但是如果项目中使用了hystrix,并且开启了线程策略:
feign.hystrix.enabled=true
hystrix.command.default.execution.isolation.strategy=THREAD
默认线程隔离(THREAD),在这种策略下,每个Hystrix命令都会在独立的线程上执行,确保了不同命令之间
的资源隔离。
信号量隔离(SEMAPHORE),信号量隔离策略下,Hystrix命令会在调用它的同一线程中执行,不会启用新的
线程。
这种情况下会导致FeignInterceptor失效,原因是在进行feign调用时会使用Hystrix的独立线程去进行feign调用,而主线程的header信息如果没有复制到Hystrix的独立线程中,会导致Hystrix的独立线程没有header信息,而Hystrix的线程去执行FeignInterceptor的时候 ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();这行代码的返回结果就会是null。
解决方法:
将主线程的header信息设置到Hystrix的线程中
Hystrix提供了HystrixConcurrencyStrategy 接口去实现
@Component
public class RequestAttributeHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
public static final Logger logger = LoggerFactory.getLogger(RequestAttributeHystrixConcurrencyStrategy.class);
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
RequestAttributes currentRequestAttributes = RequestContextHolder.currentRequestAttributes();
RequestContextHolder.setRequestAttributes(currentRequestAttributes, Boolean.TRUE);
logger.info("子线程继承父线程请求");
return callable;
}
}
并添加如下配置:
hystrix.plugin.HystrixConcurrencyStrategy.implementation=com.xxx.interceptor.RequestAttributeHystrixConcurrencyStrategy
本文详细解析了Feign远程调用过程中请求头信息丢失的问题,并提供了同步及异步场景下的解决策略,包括如何通过拦截器同步请求头信息,以及在异步调用中如何正确传递线程上下文。

797

被折叠的 条评论
为什么被折叠?



