欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Feign远程调用丢失请求头

程序员文章站 2022-07-14 18:43:25
...

场景铺垫

A服务通过Feign远程调用服务B,但是B服务又做了身份校验!主要就是通过用户的Cookie来判断用户身份的,但是A服远程调用时结果请求头到B服就丢失了

默认的Feign远程调用执行流程就是这样的
Feign远程调用丢失请求头
那么这样的流程肯定是无法满足我们的业务需求滴!!!

那么我们修改一下流程,添加一个拦截器!
Feign远程调用丢失请求头

单线程业务场景

1.配置拦截器

@Configuration
@Slf4j
public class MyFeignConfig {
    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor() {
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                log.info("进入feign拦截器...");
                ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                HttpServletRequest request = requestAttributes.getRequest();// 老请求
                String cookie = request.getHeader("Cookie");
                log.info(cookie);
                requestTemplate.header("Cookie", cookie);
            }
        };
    }
}

2.业务层远程调用

	@RequestMapping("/openGetCookie")
    public String openGetCookie(HttpServletRequest requests) throws ExecutionException, InterruptedException {
        log.info("进入demo服务器的openGetCookie方法");
        String res=demoFeign.getCookie();
        log.info(res);
        return "demo";
    }

3.本地服/远程服务控制台输出
Feign远程调用丢失请求头

实际上在业务层调用的时候,并没什么不同,但是在拦截器中

ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

这行代码就是关键,这个是spring提供获取请求上下文的包装类!在同一个线程中能共享数据!其实也就是对ThreadLocal包装了
Feign远程调用丢失请求头
这里有一篇关于ThreadLocal-同一个线程共享数据的简单介绍

注意:配置拦截器并且开启Hystrix熔断保护后,会自动触发服务熔断
Feign配置拦截器后直接触发Hystrix服务熔断

多线程业务场景

这样貌似搞定了,但是还有问题,在远程调用时,如果存在多个远程调用时往往会通过异步编排任务来执行业务逻辑,线程-CompletableFutrue-异步编排那么多线程会不会存在什么问题呢!

1.使用CompletableFutrue进行任务编排

 @RequestMapping("/openGetCookie")
    public String openGetCookie(HttpServletRequest requests) throws ExecutionException, InterruptedException {
        log.info("进入demo服务器的openGetCookie方法");
        CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> {
            log.info("进入f1");
            log.info("模拟远程调用其他服务");
            log.info("f1远程调用结果ok");
        });

        CompletableFuture<Void> f2 = CompletableFuture.runAsync(() -> {
            log.info("进入f2");
            String res=demoFeign.getCookie();
            log.info("f2远程调用结果"+res);
        });
        CompletableFuture.allOf(f1, f2).get();
        log.info("f1、f2两个编排任务结束");

        return "demo";
    }

2.修改拦截器代码

@Configuration
@Slf4j
public class MyFeignConfig {
    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor() {
          return template -> {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            log.info("进入feign拦截器...");
            if (!ObjectUtils.isEmpty(requestAttributes)) {
              HttpServletRequest request = requestAttributes.getRequest();// 老请求
              if (!ObjectUtils.isEmpty(request)) {
                  String cookie = request.getHeader("Cookie");
                  log.info(cookie);
                  template.header("Cookie", cookie);
              }
            }
        };
    }
}

其实也就添加了几行非空判断,反正逻辑报错罢了

3.测试访问
Feign远程调用丢失请求头
在这里采用异步编排后,远程服务又出现了同样的问题,请求头丢失了!!!
问题在上面的日志中已经打印出来了,这里使用异步编排,远程调用的时和请求进来的时候,并不是同一个线程,而上面刚开始也描述了RequestContextHolder就是对ThreadLocal的包装,而ThreadLocal又只能在当前线程共享数据,所以问题就再这里!

4.从新使用CompletableFutrue进行任务编排

 @RequestMapping("/openGetCookie")
    public String openGetCookie(HttpServletRequest requests) throws ExecutionException, InterruptedException {
        log.info("进入demo服务器的openGetCookie方法");
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            log.info("进入f1");
            log.info("模拟远程调用其他服务");
            log.info("f1远程调用结果ok");
        });

        CompletableFuture<Void> f2 = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            log.info("进入f2");
            String res=demoFeign.getCookie();
            log.info("f2远程调用结果"+res);
        });
        CompletableFuture.allOf(f1, f2).get();
        log.info("f1、f2两个编排任务结束");

        return "demo";
    }

在当前远程调用前获取请求的上下文,这是还在同一个线程中,所以是能获取到请求上下文内容的!

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

然后在异步编排任务中调用远程服务之前,将获取到的请求上下文注入给当前的异步任务,也就是注入给当前线程,共享数据!

 RequestContextHolder.setRequestAttributes(requestAttributes);

5.访问测试
Feign远程调用丢失请求头
这里从控制台中打印的线程也不是同一个线程,请求上下文也能实现数据共享,那么问题也就解决了!

这里还有个小坑,就是Feign配置拦击器后并且开启Hystrix服务熔断的时候,Hystrix的隔离策略会自动触发服务熔断,导致我们的请求还没到达远程服务是就已经自己熔断了!
Feign配置拦截器后直接触发Hystrix服务熔断