feign bug源码分析feign.RetryableException: Unrecognized SSL message

发布于:2023-01-11 ⋅ 阅读:(477) ⋅ 点赞:(0)

一、报错信息:

feign.RetryableException: Unrecognized SSL message, plaintext connection? executing POST http://:【此处是请求其他服务的地址】
        at feign.FeignException.errorExecuting(FeignException.java:67)
        at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:104)
        at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76)
        at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108)

二、原因分析

Unrecognized SSL message, plaintext connection

这种错误一般是出现在HTTPS请求没有证书信任或者未能通过身份校验导致的情况,但是我是feign调用的注册服务,并且对应的调用是http形式并非https。陷入疑惑,继续在报错信息找到定位入口。从报错信息中第一行的类入手FeignException.java:67

三、源码跟进

FeignException.java:67行的errorExecuting方法只是一个异常的抛出,与报错信息一致,抛出异常的同时告诉你原因,方法,url。没太多信息能体现关键信息。继续看该报错信息的引用,还好只有一个地方引用这个方法,SynchronousMethodHandler.executeAndDecode方法

 Object executeAndDecode(RequestTemplate template) throws Throwable {
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
      response = client.execute(request, options);//feign请求发起
      // ensure the request is set. TODO: remove in Feign 10
      response.toBuilder().request(request).build();
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);//异常抛出
    }

 之前看过feign调用的源码,对这里有印象是feign调用发起的开始。feign对http请求发起支持3种,一种是feign自带的HttpURLConnection,一种是okhttpClient,最后是ribbon负载均衡loadBalancer调用

由于我调用的是注册在eureka上的服务所以使用的是loadBalancer调用方式,所以继续跟进去看LoadBalancerFeignClient.execute方法。

execute方法主要是构造RibbonRequest对象用来发起负载均衡的feign请求。真正发起在executeWithLoadBalancer方法。这个方法是在RetryableFeignLoadBalancer和FeignLoadBalancer发起调用,具体哪一个不用关心,因为这两个loadBancer都继承AbstractLoadBalancerAwareClient,而executeWithLoadBalancer方法的代码在AbstractLoadBalancerAwareClient中。所以直接进入查看具体处理。


 

看方法先看注释,翻译绿字注释大意为: 该方法调用时在需要调度负载均衡到某一个服务时,而非指定url请求,调用的url最终通过reconstructURIWithServer方法执行后被executeWithLoadBalancer方法所调用。方法字面意思大致可以理解为先重构URL,然后执行负载均衡请求,直接进入reconstructURIWithServer方法查看。

 reconstructURIWithServer方法内调用了updateToHttpsIfNeeded方法

这里字面意思又在告诉我更新为HTTPS如果需要!这里大概就是问题根源了所以继续进入。

 四、定位原因

 updateToHttpsIfNeeded方法上来三个判断

1.url不为空字符串,异常信息中打印了url,不为空条件成立。

2.urlhttps开头,异常信息中也可以看到url为http调用,条件成立。

3.isSecure方法,不知道干什么的,进入查看。

 这里可以看到如果config不为空则判断配置的CommonClientConfigKey.IsSecure是否为空

CommonClientConfigKey是客户端的配置类,我搜了下IsSecure这个配置是没有的,所以这里一定是空,而isSecure空的话则会return serverIntrospector.isSecure(server)。

isSecure被 DefaultServerIntrospector和EurekaServerIntrospector两个类实现,由于我不是eureke端所以这里直接进入DefaultServerIntrospector.isSecure()方法。

 Can we do better? 不知道是什么意思先看逻辑,逻辑很简单判断你所请求服务的端口是以443结尾的则会返回true,我看了eureka上注册的服务果然有一个实例端口443结尾的。所以上面3个判断条件中第三条是成立的。

再回看updateToHttpsIfNeeded方法成立后的逻辑

UriComponentsBuilder把请求方式更改为了HTTPS的,而我们的feign是不支持HTTPS请求的,所以导致报错。
这里为什么要把url改成HTTPS请求方式大概是tomcat默认SSL服务器端口是8443,所以这里处理的不是很严谨,但有所顾虑的
因为docker容器化的端口每次启动是不固定的,不一定什么时候就会出现xx443结尾的端口,所以会引发该逻辑而不是全部服务都会出现这个错误,并且
回看isSecure方法中写的注释//can we do better? 源码作者也是有所担心。

五、解决方式

1.在配置文件中增加配置ribbon.IsSecure=false即可,这样第三个条件不会成立则不会更改HTTP请求为HTTPS的。

 2.重写DefaultServerIntrospector的isSecure方法。

我选择了第一种,配置方式相对来说灵活一些。

在我本地调试并且写死请求服务URL的端口为8843想复现这个情况的时候是不会复现的,因为就像上面注释中写的,如果写死URL是不会走到reconstructURIWithServer方法的,因为当你写死URL请求意味着不需要走负载均衡loadBanlance去选择server,所以会走okHttpClient的请求方式。

 

欢迎大家订阅我

本文含有隐藏内容,请 开通VIP 后查看