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

本文详细解析了Feign远程调用过程中请求头信息丢失的问题,并提供了同步及异步场景下的解决策略,包括如何通过拦截器同步请求头信息,以及在异步调用中如何正确传递线程上下文。

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值