因為一個bug,我掀開了openfeign的神秘面紗
來源:think123
報錯
最近專案中訪問一個外部api報錯了,報錯資訊如下
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
看著像是證書問題,這個時候我首先想到的是百度下,看看怎麼解決。
解決方案
百度告訴我說如果你open-feign中使用的是http client,那麼可以透過下面的配置來讓跳過SSL驗證
feign:
httpclient:
disable-ssl-validation: false
結果還是報同樣的錯誤。於是我又百度,又重新找了一個解決方法,這次的方案是讓我自己重寫Client了,具體操作如下
@Configuration
public class FeignConfiguration {
@Bean
public Client feignClient() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext ctx = SSLContext.getInstance("SSL");
X509TrustManager tm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[]{tm}, null);
return new Client.Default(ctx.getSocketFactory(), (hostName, session) -> true);
}
}
這把我感覺要起飛了, 一切盡在掌握中,重新deploy,開啟postman,測試測試我的介面。
測試後感覺好了但是看日誌又沒有完全好。這個介面倒是不報錯了,但是我呼叫內部服務給我報錯了,比如我這裡的內部服務名稱叫做
pro-file, 就現在它沒法根據我這個pro-file名字找到對應的IP了,從而導致我這個服務使用不了了。
百度誤我!
求人不如求己
此刻我自信的開啟了IDEA, 輸入了類名 FeignAutoConfiguration
, Spring Cloud關於某個元件的自動注入類大多是XXXConfiguration, 所以按照這麼找準沒錯。
然後我有自信的把斷點打在了這個部分 FeignAutoConfiguration:246
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Conditional(HttpClient5DisabledConditions.class)
protected static class HttpClientFeignConfiguration {
// 省略其他程式碼
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
}
重新啟動專案,好傢伙斷點沒進來呀。沒進來的原因大機率可能是不滿足條件,我趕緊看看這裡對應的Conditional, 發現了我的程式碼中沒有
設定feign.httpclient.enabled
屬性的值, 而且這裡也沒有設定havingValue, 根據原始碼可以知道, 如果沒有設定havingValue, 那麼這個屬性的值會被和false進行比較
//org.springframework.boot.autoconfigure.condition.OnPropertyCondition.Spec#isMatch
// 這裡的requiredValue是havingValue
private boolean isMatch(String value, String requiredValue) {
if (StringUtils.hasLength(requiredValue)) {
return requiredValue.equalsIgnoreCase(value);
}
return !"false".equalsIgnoreCase(value);
}
搞半天這個Configurtion相當於沒起作用。
好好好,這麼玩是吧。
既然這個配置不生效,那肯定有其他配置生效,我就找找其他配置,最終我在spring-cloud-openfeign-core這個jar包的loadbalancer這個包下面找到了我想要的配置
@ConditionalOnClass(Feign.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@AutoConfigureBefore(FeignAutoConfiguration.class)
@AutoConfigureAfter({ BlockingLoadBalancerClientAutoConfiguration.class, LoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties(FeignHttpClientProperties.class)
@Configuration(proxyBeanMethods = false)
// Order is important here, last should be the default, first should be optional
// see
//
@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class,
HttpClient5FeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {
}
因為我們專案是採用springcloud alibaba進行開發,所以引入了spring-cloud-loadbalancer這個包,因此這個這個配置類就會生效,由於我們沒有配置使用httpclient,同樣也未使用okhttp,所以生效的配置類只有一個,那就是 DefaultFeignLoadBalancerConfiguration
這個配置類中retryClient會被載入,因為我們引入了spring-retry.
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnBean(LoadBalancedRetryFactory.class)
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
matchIfMissing = true)
public Client feignRetryClient(LoadBalancerClient loadBalancerClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
return new RetryableFeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,
loadBalancedRetryFactory, loadBalancerClientFactory);
}
這也是為什麼上面我們自己配置了自己的Client後,訪問其他spring cloud服務會找不到地址,這是因為預設的client不會去透過LoadBalancer去獲取服務地址。
小插曲
期間debug的時候,還發現最終的Client的SeataFeignClient,我一看才發現某個公共包引入了Seata,但是沒有使用Seata功能,然後Seata會把我們最終使用的FeignClient在給封裝一次,所以後面我就把seata從專案中移除了。
解決方案
既然問題找到了,那麼就好修改了,修改方式有兩種,一種是建立自己的RetryableFeignBlockingLoadBalancerClient
, 就把上面的程式碼拿過來抄一遍,只是自己指定SSLContext,另一種是啟用httpclient
方案一
@Bean
public Client feignRetryClient(LoadBalancerClient loadBalancerClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) throws NoSuchAlgorithmException, KeyManagementException {
SSLContext ctx = SSLContext.getInstance("SSL");
X509TrustManager tm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[]{tm}, null);
return new RetryableFeignBlockingLoadBalancerClient(new Client.Default(ctx.getSocketFactory(), (hostname, session) -> true), loadBalancerClient,
loadBalancedRetryFactory, loadBalancerClientFactory);
}
方案二
另一種方案就是啟用httpclient,並且禁用ssl驗證,配置如下
feign:
httpclient:
enabled: true
disable-ssl-validation: true
自此這個問題解決了,當然在使用中更加傾向使用方案二,因為Feign預設的Client採用的是HttpURLConnection
,它沒有連線池,當然你也可以使用okhttp。
寫到最後
這個問題看起來簡單,但是排查起來還是頗費心思,很多細節隱藏到了框架之下,所以我想看原始碼還是有好處的,因為網上的文章別人的情況可能和你不一樣,與其遨遊在各個文章裡面,還不如debug一把。
來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70024923/viewspace-3006752/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 解開“QUIC”的神秘面紗UI
- 揭開“信創”的神秘面紗
- 揭開Kotlin協程的神秘面紗Kotlin
- 扯下@EventListener這個註解的神秘面紗。
- 揭開神秘面紗——深入淺出ThreadLocalthread
- Dive into TensorFlow系列(3)- 揭開Tensor的神秘面紗
- 一文揭開JDK21虛擬執行緒的神秘面紗JDK執行緒
- 揭開華為雲CodeArts TestPlan啟發式測試設計神秘面紗!
- 揭開神秘面紗,會stream流就會大資料大資料
- 【譯】用 GitHub Copilot 提交註釋揭開歷史的神秘面紗Github
- NYDIG交易所揭開區塊鏈節點神秘的面紗區塊鏈
- 揭開AI、機器學習和深度學習的神秘面紗AI機器學習深度學習
- 揭開二維碼背後的神秘面紗用二維碼識別 API 就夠了API
- 因為 GitHub Actions 我發現了 Jake Wharton 的一個倉庫Github
- 我好像發現了一個Go的Bug?Go
- 帶你揭開神秘的javascript AST面紗之AST 基礎與功能JavaScriptAST
- 揭開SSL的神秘面紗,瞭解如何用SSL保護資料
- 在Axon框架中揭開跟蹤事件處理器的神秘面紗框架事件
- 從一個Demo開始,揭開Netty的神祕面紗Netty
- 因為你這個人,我選擇了這個公司
- 探索古諾爾斯語:揭開維京時代語言的神秘面紗
- 我們被一個 kong 的效能 bug 折騰了一個通宵
- MyApp({super.key})——因為一個語法糖,我研究了一晚上。APP
- 一個Bug,讓我發現了 Java 界的.AJ(錐)!Java
- 揭開ThreadLocal的面紗thread
- 帶了6個月的徒弟當了面試官,而身為高階工程師的我天天修Bug......面試工程師
- 帶你揭開神秘的Javascript AST面紗之Babel AST 四件套的使用方法JavaScriptASTBabel
- 揭開網際網路公司的神秘面紗,資料解讀那些slay整個行業的網際網路公司行業
- 我為 VS Code 開發了一個 Deno 外掛
- 揭開Redux神祕面紗:手寫一個min-ReduxRedux
- 終於,幫開發寫了一個bug
- 化腐朽為神奇!揭開ISP影像處理的神秘面紗,基於瑞芯微RK3568J工業平臺!
- Spring Boot 中的同一個 Bug,竟然把我坑了兩次!Spring Boot
- 因為我最近忙、所以我寫了它
- 因為這個好用便籤,我放棄了紙質便籤
- 因為我的一個低階錯誤,生產資料庫崩潰了將近半個小時資料庫
- 揭開 Kubernetes 的神祕面紗
- 揭開“QUIC”的神祕面紗UI