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

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、为什么会丢失请求头

问题描述:在微服务项目中,我们做了单点登录,在项目使用feign 调用另一个模块的远程服务时,发现提示无权限调用。

//1.在远程调用的方法上打个断点
List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());

//2.进入方法内部 ReflectiveFeign.class
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			//判断调用是不是equal方法
            if (!"equals".equals(method.getName())) {
            	//判断是不是调用hashCode
                if ("hashCode".equals(method.getName())) {
                    return this.hashCode();
                } else {
                	//判断是不是调用toString 都不是就执行  ((MethodHandler)this.dispatch.get(method)).invoke(args);
                    return "toString".equals(method.getName()) ? this.toString() : ((MethodHandler)this.dispatch.get(method)).invoke(args);
                }
            } else {
                try {
                    Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
                    return this.equals(otherHandler);
                } catch (IllegalArgumentException var5) {
                    return false;
                }
            }
        }

//3. ((MethodHandler)this.dispatch.get(method)).invoke(args); 
//点击进入invoke 方法  SynchronousMethodHandler.class
 public Object invoke(Object[] argv) throws Throwable {
 		//就是在这 构建了一个新的RequestTemplate ,而浏览器带给我们的请求头都会丢失
        RequestTemplate template = this.buildTemplateFromArgs.create(argv);
        Retryer retryer = this.retryer.clone();

        while(true) {
            try {
            //在这即将执行该方法
                return this.executeAndDecode(template);
            } catch (RetryableException var8) {
                RetryableException e = var8;

                try {
                    retryer.continueOrPropagate(e);
                } catch (RetryableException var7) {
                    Throwable cause = var7.getCause();
                    if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
                        throw cause;
                    }

                    throw var7;
                }

                if (this.logLevel != Level.NONE) {
                    this.logger.logRetry(this.metadata.configKey(), this.logLevel);
                }
            }
        }
    }


至此,我们找到了feign远程调用请求头丢失的原因

在这里插入图片描述

二、解决方法

1.单线程

继续深入executeAndDecode方法 查看原因

    Object executeAndDecode(RequestTemplate template) throws Throwable {
    //这里 它会对我们的请求进行一些包装 
        Request request = this.targetRequest(template);
        if (this.logLevel != Level.NONE) {
            this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
        }

        long start = System.nanoTime();

        Response response;
        try {
            response = this.client.execute(request, this.options);
        } catch (IOException var15) {
            if (this.logLevel != Level.NONE) {
                this.logger.logIOException(this.metadata.configKey(), this.logLevel, var15, this.elapsedTime(start));
            }

            throw FeignException.errorExecuting(request, var15);
        }


//下面我们查看一下targetRequest方法
Request targetRequest(RequestTemplate template) {
		//拿到对应的所有请求拦截器的迭代器
        Iterator var2 = this.requestInterceptors.iterator();

		//遍历所有的请求拦截器
        while(var2.hasNext()) {
            RequestInterceptor interceptor = (RequestInterceptor)var2.next();
            //这里是每个请求拦截器 依次对该方法进行包装
            interceptor.apply(template);
        }

        return this.target.apply(template);
    }


//我们发现它是一个接口 所以可以重写一下这个方法 对我们的请求做一些包装 借鉴一下别的实现方法
public interface RequestInterceptor {
    void apply(RequestTemplate var1);
}

public class BasicAuthRequestInterceptor implements RequestInterceptor {
  public void apply(RequestTemplate template) {
        template.header("Authorization", new String[]{this.headerValue});
    }
}
  

//解决方法----》 我自己项目的你们可以借鉴
@Configuration
public class GuliFeignConfig {

    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor(){
    	//给容器中放一个RequestInterceptor 重写apply 在里面给请求设置上自己需要的请求头等信息
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                //1.RequestContextHolder 拿到刚进来的这个请求
                ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                HttpServletRequest request = attributes.getRequest();//老请求
                if(request!=null){
                    //同步请求头数据,Cookie
                    String cookie = request.getHeader("Cookie");
                    //给新请求同步老请求的cookie
                    requestTemplate.header("Cookie",cookie);
                }
                System.out.println("feign 在远程调用之前先进行 RequestInterceptor.apply");
            }
        };
    }
}      

至此 问题完美解决

在这里插入图片描述

2.异步远程调用时,请求头丢失

上面 同步的情况下 我们已经解决请求头丢失的问题,然而 在实际项目中 我们很可能需要异步调用多个远程服务,这个时候 我们会发现 feign 请求头丢失的问题又出现了

//1.问题主要出在 RequestContextHolder.getRequestAttributes();上,点进这个方法 看下源码
 @Nullable
    public static RequestAttributes getRequestAttributes() {
    	//它是从requestAttributesHolder这里面取出来的
        RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
        if (attributes == null) {
            attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
        }

        return attributes;
    }

//2.接着追 我们发现requestAttributesHolder是一个NamedThreadLocal对象
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");

//3.我们发现NamedThreadLocal继承自ThreadLocal
//而ThreadLocal是一个线程局部变量,在不同线程之间是独立的所以我们获取不到 原先主线程的请求属性,即给请求头添加cookie失败

public class NamedThreadLocal<T> extends ThreadLocal<T> {
    private final String name;

    public NamedThreadLocal(String name) {
        Assert.hasText(name, "Name must not be empty");
        this.name = name;
    }

    public String toString() {
        return this.name;
    }
}

//4.解决方法(这里只提供了一种 变量复制)
  //获取之前的请求
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();


        CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
            //每一个线程都共享之前的请求数据
            RequestContextHolder.setRequestAttributes(requestAttributes);
            //1.远程查询所有的收货地址列表
            List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
            confirmVo.setAddress(address);
            System.out.println("副线程1。。。"+Thread.currentThread().getId());
        }, executor);



总结

Feign远程调用请求头丢失问题目前就了解了这么多,有兴趣的同学可以一起探讨探讨,也可以自己去谷粒学院看看 (ps:我是看视频学到的方法)

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值