Feign远程调用丢失请求头问题解决方案
我们在写服务端项目的时候,总会限制对某些资源的访问,最常见的就是要求用户先登录才能访问资源,当用户登录后就会将此次会话信息保存进session
,同时返回给浏览器指定的cookie
键值,下次浏览器再次访问,请求头中就会携带这个cookie
,我们也以次来识别用户的登录状态,做出正确响应。
1、问题描述
有时候,我们先行登录,然后访问服务A的某个方法,请求头中携带cookie,标识我们已经登录。但若是我们访问的目标方法在执行过程中使用feign进行原程调用服务B(假设不存在跨域),而服务B也要先判断登录状态,我们可能发现服务B会调用失败,或者说拿不到数据,理由是服务B认为我们并未登录。而这时,如果我们直接从浏览器访问服务B的这个方法却能得到一个成功的响应。.png
结合上图所示,浏览器在调用order服务时,请求头里有Cookie,但在order服务远程调用cart服务时,请求头不再携带之前的Cookie。这是因为Feign在远程调用的时候会创建一个新的request请求 ,因此远程调用便不会再携带之前的Cookie。
2、解决方案
解决的方法其实很简单,无非就是将之前请求头的Cookie拿到,然后再放入新的请求头当中即可。创建一个拦截器,在远程调用的时候执行更新新的request请求头的操作。feign在创建新的request对象时,会调用一系列容器中的RequestInterceptor
对象,执行其apply
方法,对这个创建好的request进行增强,再去真正执行请求。但是默认情况下容器中不存在这类拦截器对象。
我们可以自己向容器中注册一个RequestInterceptor
,在其apply
方法体内,获取到原始request
,将其数据取出,赋值到新的request
中,完成请求头的同步。
RequestContextHolder
借助ThreadLocal
将每一个原始请求与tomcat
为其分配的线程绑定,之后,只要在同个线程内,随时随地都可轻易获取到原始request
。
而我们是在apply
方法体内,通过 RequestContextHolder.getRequestAttributes()
获取的。RequestContextHolder
是借助ThreadLocal
将每一个原始请求与tomcat为其分配的线程绑定,之后,只要在同个线程内,随时随地都可轻易获取到原始request。
1 |
|
但是如果你的feign调用出现在异步线程体内,RequestInterceptor
拦截到你时,你再使用RequestContextHolder
,获取的已经不是原来线程,必然无法获取到原请求,只能拿到与当下线程绑定的request,甚至直接得到一个null,引发空指针异常。
异步编排解决
这要如何解决?很简单,提前同步数据。进入新线程之前,拿出原线程绑定的requestAttributes
,在新的线程体内,feign调用之前,将其赋值到本线程绑定的request中,这样,在执行feign方法,被拦截器拦截时,当前线程绑定的request已不为空。(RequestContextHolder写在哪就绑定的是哪个线程)
1 | //获取之前的请求 |