天天看點

Spring Cloud項目中通過Feign進行内部服務調用發生401\407錯誤無傳回資訊的問題

問題描述

最近在使用

Spring Cloud

改造現有服務的工作中,在内部服務的調用方式上選擇了

Feign

元件,由于服務與服務之間有權限控制,發現通過

Feign

來進行調用時如果發生了401、407錯誤時,調用方不能夠取回被調用方傳回的錯誤資訊。

産生原因

Feign預設使用

java.net.HttpURLConnection

進行通信,通過檢視其子類

sun.net.www.protocol.http.HttpURLConnection

源碼發現代碼中在進行通信時單獨對錯誤碼為401\407的錯誤請求做了處理,當請求的錯誤碼為401\407時,會關閉請求流,由于此時還并沒有将傳回的錯誤資訊寫入響應流中,是以接收的傳回資訊中僅僅能擷取到

response.status()

,而

response.body()

null

HttpURLConnection相關資訊的源碼連結:http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/net/www/protocol/http/HttpURLConnection.java#1079

解決思路

關于此問題産生的原因已經很明顯了,就是

feign.Client

實作通信的方式選用了我們不想使用的

HttpURLConnection

。想到通常在Spring的代碼中OCP都是運用得很好的,是以基本上有解決此問題的信心了,最不濟就是自己擴充

Feign

,實作一個自己想要的

feign.Client

,當然這種事情

Spring Cloud

基本都會自己搞定,這也是

Spring Cloud

強大完善的一個地方。

通過這個思路檢視源碼,果然看到了

Spring Cloud

在使用

Feign

提前内置了三種通信方式(

feign.Client.Default

feign.httpclient.ApacheHttpClient

feign.okhttp.OkHttpClient

),其中預設的情況使用的就是

feign.Client.Default

,這個就是使用

HttpURLConnection

通信的方式。

源碼解析

Spring Cloud

項目中使用了Ribbon的元件,其會幫助我們管理使用

Feign

,檢視

org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClientAutoConfiguration

源碼

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignRibbonClientAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                SpringClientFactory clientFactory) {
        return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory, clientFactory);
    }

    @Configuration
    @ConditionalOnClass(ApacheHttpClient.class)
    @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
    protected static class HttpClientFeignLoadBalancedConfiguration {

        @Autowired(required = false)
        private HttpClient httpClient;

        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                    SpringClientFactory clientFactory) {
            ApacheHttpClient delegate;
            if (this.httpClient != null) {
                delegate = new ApacheHttpClient(this.httpClient);
            }
            else {
                delegate = new ApacheHttpClient();
            }
            return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
        }
}

    @Configuration
    @ConditionalOnClass(OkHttpClient.class)
    @ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true)
    protected static class OkHttpFeignLoadBalancedConfiguration {

        @Autowired(required = false)
        private okhttp3.OkHttpClient okHttpClient;

        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                    SpringClientFactory clientFactory) {
            OkHttpClient delegate;
            if (this.okHttpClient != null) {
                delegate = new OkHttpClient(this.okHttpClient);
            }
            else {
                delegate = new OkHttpClient();
            }
            return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
        }
    }
}
           
  • feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory)

    方法結合其上注解我們可以很清楚的知道,當沒有

    feign.Client

    Bean的時候會預設生成

    feign.Client.Default

    來進行通信,這就是之前說的預設通信方式
  • HttpClientFeignLoadBalancedConfiguration

    OkHttpFeignLoadBalancedConfiguration

    ,我們可以看到其生效的條件,當classpath中有

    feign.httpclient.ApacheHttpClient

    并且配置

    feign.httpclient.enabled=true

    (預設為true)、

    feign.okhttp.OkHttpClient

    feign.okhttp.enabled=true

    (預設為true)
  • 當使用ApacheHttpClient或者OkHttpClient進行通信時就不會導緻發生401\407錯誤時,取不到傳回的錯誤資訊了

解決方法

通過其上的分析,解決方法已經顯而易見了,

  1. pom.xml

    檔案中新增
<dependency>
            <groupId>com.netflix.feign</groupId>
            <artifactId>feign-okhttp</artifactId>
            <version>8.18.0</version>
      </dependency>
           

或者

<dependency>
            <groupId>com.netflix.feign</groupId>
            <artifactId>feign-httpclient</artifactId>
            <version>8.18.0</version>
      </dependency>
           
  1. application.properties

    檔案中增加(可預設,如有切換調用方式需求可添加配置進行控制)

    feign.okhttp.enabled=true

    feign.httpclient.enabled=true

總結

  • 由于新增的依賴沒有被starter管理,并且預設不會導緻程式啟動異常,并且傳回響應為null與此依賴沒有直接關系,是以不友善定位到問題,特此記錄下來,希望能幫助到遇到同樣問題的人,如對文章有不同的看法,望給予指正。
  • 本文建立在已經搭建完成Feign的調用基礎之上,沒有講述Feign的使用,因為此類文章很多,在此就不重複了。

繼續閱讀