天天看點

再見,HttpClient!再見,Okhttp!

1.背景

因為業務關系,要和許多不同第三方公司進行對接。這些服務商都提供基于http的api。但是每家公司提供api具體細節差别很大。有的基于RESTFUL規範,有的基于傳統的http規範;有的需要在header裡放置簽名,有的需要SSL的雙向認證,有的隻需要SSL的單向認證;有的以JSON 方式進行序列化,有的以XML方式進行序列化。類似于這樣細節的差别太多了。

不同的公司API規範不一樣,這很正常。但是對于我來說,我如果想要代碼變得優雅。我就必須解決一個痛點:

不同服務商API那麼多的差異點,如何才能維護一套不涉及業務的公共http調用套件。最好通過配置或者簡單的參數就能區分開來。進行友善的調用?

我當然知道有很多優秀的大名鼎鼎的http開源架構可以實作任何形式的http調用,在多年的開發經驗中我都有使用過。比如apache的httpClient包,非常優秀的Okhttp,jersey client。

這些http開源架構的接口使用相對來說,都不太一樣。不管選哪個,在我這個場景裡來說,我都不希望在調用每個第三方的http api時寫上一堆http調用代碼。

是以,在這個場景裡,我得對每種不同的http api進行封裝。這樣的代碼才能更加優雅,業務代碼和http調用邏輯耦合度更低。

可惜,我比較懶。一來覺得封裝起來比較費時間,二來覺對封裝這種底層http調用來說,應該有更好的選擇。不想自己再去造輪子。

于是,我發現了一款優秀的開源http架構,能屏蔽不同細節http api所帶來的所有差異。能通過簡單的配置像調用rpc架構一樣的去完成極為複雜的http調用。

Forest

https://gitee.com/dt_flys/forest
再見,HttpClient!再見,Okhttp!
再見,HttpClient!再見,Okhttp!

2.上手

Forest

支援了

Springboot

的自動裝配,是以隻需要引入一個依賴就行

<dependency>
  <groupId>com.dtflys.forest</groupId>
  <artifactId>spring-boot-starter-forest</artifactId>
  <version>1.3.0</version>
</dependency>      

定義自己的接口類

public interface MyClient {

    @Request(url = "http://baidu.com")
    String simpleRequest();

    @Request(
            url = "http://ditu.amap.com/service/regeo",
            dataType = "json"
    )
    Map getLocation(@DataParam("longitude") String longitude, @DataParam("latitude") String latitude);

}      

在啟動類裡配置代理接口類的掃描包

@SpringBootApplication
@ForestScan(basePackages = "com.example.demo.forest")
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}      

這時候,你就可以從spring容器中注入你的代理接口,像調用本地方法一樣去調用http的api了

@Autowired
private MyClient myClient;

@Override
public void yourMethod throws Exception {
    Map result = myClient.getLocation("124.730329","31.463683");
    System.out.println(JSON.toJSONString(result,true));
}      

日志列印,Forest列印了内部所用的http架構,和實際請求url和傳回。當然日志可以通過配置去控制開關。

再見,HttpClient!再見,Okhttp!

3.特點

我覺得對于尤其是做對接第三方api的開發同學來說,這款開源架構能幫你提高很多效率。

Forest 底層封裝了2種不同的http架構:Apache httpClient和OKhttp。是以這個開源架構并沒有對底層實作進行重複造輪子,而是在易用性上面下足了功夫。

我用Forest最終完成了和多個服務商api對接的項目,這些風格迥異的API,我僅用了1個小時時間就把他們轉化為了本地方法。然後項目順利上線。

Forest作為一款更加高層的http架構,其實你并不需要寫很多代碼,大多數時候,你僅通過一些配置就能完成http的本地化調用。而這個架構所能覆寫的面,卻非常之廣,滿足你絕大多數的http調用請求。

Forest有以下特點:

以Httpclient和OkHttp為後端架構

通過調用本地方法的方式去發送Http請求, 實作了業務邏輯與Http協定之間的解耦

相比Feign更輕量,不依賴Spring Cloud和任何注冊中心

支援所有請求方法:GET, HEAD, OPTIONS, TRACE, POST, DELETE, PUT, PATCH

支援靈活的模闆表達式

支援過濾器來過濾傳入的資料

基于注解、配置化的方式定義Http請求

支援Spring和Springboot內建

實作JSON和XML的序列化和反序列化

支援JSON轉換架構: Fastjson,Jackson, Gson

支援JAXB形式的XML轉換

支援SSL的單向和雙向加密

支援http連接配接池的設定

可以通過OnSuccess和OnError接口參數實作請求結果的回調

配置簡單,一般隻需要@Request一個注解就能完成絕大多數請求的定義

支援異步請求調用

4.兩個很棒的功能

這裡不對使用方式和配置方式一一描述,有興趣的可以去閱讀詳細文檔:

https://dt_flys.gitee.io/forest

這裡隻想分析這個架構2個我認為比較好的功能

4.1 模闆表達式和參數的映射綁定功能

模闆表達式在使用的時候特别友善,舉個栗子

@Request(
    url = "${0}/send?un=${1}&pw=${2}&ph=${3}&ct=${4}",
    type = "get",
    dataType = "json"
)
public Map send(
    String base,
    String userName,
    String password,
    String phone,
    String content
);      

上述是用序号下标進行取值,也可以通過名字進行取值:

@Request(
    url = "${base}/send?un=${un}&pw=${pw}&ph=${3}&ct=${ct}",
    type = "get",
    dataType = "json"
)
public Map send(
    @DataVariable("base") String base,
    @DataVariable("un") String userName,
    @DataVariable("pw") String password,
    @DataVariable("ph") String phone,
    @DataVariable("ct") String content
);      

甚至于可以這樣簡化寫:

@Request(
    url = "${base}/send",
    type = "get",
    dataType = "json"
)
public Map send(
    @DataVariable("base") String base,
    @DataParam("un") String userName,
    @DataParam("pw") String password,
    @DataParam("ph") String phone,
    @DataParam("ct") String content
);      

以上三種寫法是等價的

當然你也可以把參數綁定到header和body裡去,你甚至于可以用一些表達式簡單的把對象序列化成json或者xml:

@Request(
    url = "${base}/pay",
      contentType = "application/json",
    type = "post",
    dataType = "json",
    headers = {"Authorization: ${1}"},
    data = "${json($0)}"
)
public PayResponse pay(PayRequest request, String auth);      

當然資料綁定這塊詳情請參閱文檔

4.2 對HTTPS的支援

以前用其他http架構處理https的時候,總覺得特别麻煩,尤其是雙向證書。每次碰到問題也隻能去baidu。然後根據别人的經驗來修改自己的代碼。

Forest對于這方面也想的很周到,底層完美封裝了對https單雙向證書的支援。也是隻要通過簡單的配置就能迅速完成。舉個雙向證書栗子:

@Request(
    url = "${base}/pay",
      contentType = "application/json",
    type = "post",
    dataType = "json",
      keyStore = "pay-keystore",
      data = "${json($0)}"
)
public PayResponse pay(PayRequest request);      

其中

pay-keystore

對應着

application.yml

裡的

ssl-key-stores

forest:
  ...
  ssl-key-stores:
    - id: pay-keystore
      file: test.keystore
      keystore-pass: 123456
      cert-pass: 123456
      protocols: SSLv3      

這樣設定,就ok了,剩下的,就是本地代碼形式的調用了。

5.最後

Forest有很多其他的功能設定,如果感興趣的同學還請仔細去閱讀文檔和示例。

但是我想說的是,相信看到這裡,很多人一定會說,這不就是Feign嗎?

我在開發Spring Cloud項目的時候,也用過一段時間Feign,個人感覺Forest的确在配置和用法上和Feign的設計很像,但Feign的角色更多是作為Spring Cloud生态裡的一個成員。充當RPC通信的角色,其承擔的不僅是http通訊,還要對注冊中心下發的調用位址進行負載均衡。

而Forest這個開源項目其定位則是一個高階的http工具,主打友好和易用性。從使用角度出發,個人感覺Forest配置性更加簡單直接。提供的很多功能也能解決很多人的痛點。

開源精神難能可貴,好的開源需要大家的添磚加瓦和支援。希望這篇文章能給大家在選擇http用戶端架構時帶來一個新的選擇:Forest。