😊你好,我是小航,一个正在变秃、变强的文艺倾年。
🔔本文讲解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远程调用请求头丢失的原因:

我们继续深入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});
}
}
找到原因所在就好解决问题了,于是我写了一个feign拦截器,这里面注入了一个RequestInterceptor的对象,它是一个接口,我重写了它的apply方法,在里面拿到老请求中的请求头信息,放到这个新的请求模板里,我这里更新的是cookie。
@Configuration
public class GuliFeignConfig {
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor() {
RequestInterceptor requestInterceptor = new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
//1、使用RequestContextHolder拿到刚进来的请求数据
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
//老请求
HttpServletRequest request = requestAttributes.getRequest();
if (request != null) {
//2、同步请求头的数据(主要是cookie)
//把老请求的cookie值放到新请求上来,进行一个同步
String cookie = request.getHeader("Cookie");
template.header("Cookie", cookie);
}
}
}
};
return requestInterceptor;
}
}
至此问题完美解决!
最后我们小结一下:
在分布式项目中,发送请求大致就两种,一种是浏览器访问,第二种是服务与服务之间通过OpenFeign远程调用。浏览器发送请求时,它会带上请求头的信息的,所以不会导致cookie丢失,这样用户真实登录的情况下不会判断未登录的异常情况。深入源码发现,Feign会重新创建一个request,这个请求是没有任何请求头的,这个请求模板会遍历请求拦截器的apply方法来丰富这个请求模板。所以我们可以写一个feign拦截器,里面注入一个RequestInterceptor的对象,重写它的apply方法,在里面拿到老请求中的请求头信息,放到这个新的请求模板里。
在实际项目中我们很可能需要异步调用多个远程服务,这个时候我们会发现 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;
}
}
解决方案:(这里只提供了一种 变量复制)
// 1.获取之前的请求头数据
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
//2.每一个线程都共享之前的请求数据
RequestContextHolder.setRequestAttributes(requestAttributes);
//远程查询
....
}, executor);
示例代码:


我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po
尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby中使用两个参数异步运行exe吗?我已经尝试过ruby命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何rubygems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除
在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这
我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search
由于fast-stemmer的问题,我很难安装我想要的任何rubygem。我把我得到的错误放在下面。Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingfast-stemmer:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcreatingMakefilemake"DESTDIR="cleanmake"DESTDIR=
我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file
当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub
如何在ruby中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL
rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送