大家好户辫,我是二哥呀渔欢!
今天來給大家推薦一款直擊痛點(diǎn)的 HTTP 客戶端框架苫幢,可以超高效率地完成和第三方接口的對(duì)接韩肝。
在介紹本篇的主角之前哀峻,我們先來了解下 Java 生態(tài)中的 HTTP 組件庫剩蟀,大致可以分為三類:
- JDK 自帶的 HttpURLConnection 標(biāo)準(zhǔn)庫育特;
- Apache HttpComponents HttpClient且预;
- OkHttp。
使用 HttpURLConnection 發(fā)起 HTTP 請(qǐng)求最大的優(yōu)點(diǎn)是不需要引入額外的依賴涮拗,但是使用起來非常繁瑣三热,也缺乏連接池管理、域名機(jī)械控制等特性支持抑堡。
使用標(biāo)準(zhǔn)庫的最大好處就是不需要引入額外的依賴首妖,但使用起來比較繁瑣,就像直接使用 JDBC 連接數(shù)據(jù)庫那樣,需要很多模板代碼棚壁。來發(fā)起一個(gè)簡(jiǎn)單的 HTTP POST 請(qǐng)求吧杯矩。
public class HttpUrlConnectionDemo {
public static void main(String[] args) throws IOException {
String urlString = "https://httpbin.org/post";
String bodyString = "password=123";
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
os.write(bodyString.getBytes("utf-8"));
os.flush();
os.close();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream is = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
System.out.println("響應(yīng)內(nèi)容:" + sb.toString());
} else {
System.out.println("響應(yīng)碼:" + conn.getResponseCode());
}
}
}
HttpURLConnection 發(fā)起的 HTTP 請(qǐng)求比較原始,基本上算是對(duì)網(wǎng)絡(luò)傳輸層的一次淺層次的封裝灌曙;有了 HttpURLConnection 對(duì)象后菊碟,就可以獲取到輸出流,然后把要發(fā)送的內(nèi)容發(fā)送出去在刺;再通過輸入流讀取到服務(wù)器端響應(yīng)的內(nèi)容逆害;最后打印。
不過 HttpURLConnection 不支持 HTTP/2.0蚣驼,為了解決這個(gè)問題纯陨,Java 9 的時(shí)候官方的標(biāo)準(zhǔn)庫增加了一個(gè)更高級(jí)別的 HttpClient阴颖,再發(fā)起 POST 請(qǐng)求就顯得高大上多了偎肃,不僅支持異步,還支持順滑的鏈?zhǔn)秸{(diào)用稀火。
public class HttpClientDemo {
public static void main(String[] args) throws URISyntaxException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString("二哥牛逼"))
.build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
}
}
Apache HttpComponents HttpClient 支持的特性也非常豐富:
- 基于標(biāo)準(zhǔn)团甲、純凈的Java語言,實(shí)現(xiàn)了HTTP1.0和HTTP1.1牛隅;
- 以可擴(kuò)展的面向?qū)ο蟮慕Y(jié)構(gòu)實(shí)現(xiàn)了HTTP全部的方法丈攒;
- 支持加密的HTTPS協(xié)議(HTTP通過SSL協(xié)議)显设;
- Request的輸出流可以避免流中內(nèi)容體直接從socket緩沖到服務(wù)器慷妙;
- Response的輸入流可以有效的從socket服務(wù)器直接讀取相應(yīng)內(nèi)容狞山。
public class HttpComponentsDemo {
public static void main(String[] args) throws IOException, IOException, ParseException {
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
HttpPost httpPost = new HttpPost("http://httpbin.org/post");
List<NameValuePair> nvps = new ArrayList<>();
nvps.add(new BasicNameValuePair("name", "二哥"));
httpPost.setEntity(new UrlEncodedFormEntity(nvps, Charset.forName("UTF-8")));
try (CloseableHttpResponse response2 = httpclient.execute(httpPost)) {
System.out.println(response2.getCode() + " " + EntityUtils.toString(response2.getEntity()));
}
}
}
}
OkHttp 是一個(gè)執(zhí)行效率比較高的 HTTP 客戶端:
- 支持 HTTP/2.0屡律,當(dāng)多個(gè)請(qǐng)求對(duì)應(yīng)同一個(gè) Host 地址時(shí),可共用同一個(gè) Socket穿挨;
- 連接池可減少請(qǐng)求延遲厉萝;
- 支持 GZIP 壓縮彩郊,減少網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)大小惠险;
- 支持 Response 數(shù)據(jù)緩存强经,避免重復(fù)網(wǎng)絡(luò)請(qǐng)求鲸伴;
public class OkHttpPostDemo {
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
public static void main(String[] args) throws IOException {
OkHttpPostDemo example = new OkHttpPostDemo();
String json = "{'name':'二哥'}";
String response = example.post("https://httpbin.org/post", json);
System.out.println(response);
}
}
那今天介紹的這款輕量級(jí)的 HTTP 客戶端框架 Forest毕谴,正是基于 Httpclient和OkHttp 的,屏蔽了不同細(xì)節(jié)的 HTTP 組件庫所帶來的所有差異。
Forest 的字面意思是森林的意思,更內(nèi)涵點(diǎn)的話,可以拆成For和Rest兩個(gè)單詞站超,也就是“為了Rest”(Rest為一種基于HTTP的架構(gòu)風(fēng)格)。 而合起來就是森林,森林由很多樹木花草組成(可以理解為各種不同的服務(wù)),它們表面上看獨(dú)立耙箍,實(shí)則在地下根莖交錯(cuò)縱橫、相互連接依存猾骡,這樣看就有點(diǎn)現(xiàn)代分布式服務(wù)化的味道了曼振。 最后脐瑰,這兩個(gè)單詞反過來讀就像是Resultful妖枚。
項(xiàng)目地址:
雖然 star 數(shù)還不是很多,但 star 趨勢(shì)圖正在趨于爬坡階段苍在,大家可以拿來作為一個(gè)練手項(xiàng)目绝页,我覺得還是不錯(cuò)的選擇。
Forest 本身是處理前端過程的框架寂恬,是對(duì)后端 HTTP API 框架的進(jìn)一步封裝续誉。
前端部分:
- 通過RPC方式去發(fā)送HTTP請(qǐng)求, 方便解耦
- 支持GET, HEAD, POST等所有請(qǐng)求方法
- 支持Spring和Springboot集成
- JSON字符串到Java對(duì)象的自動(dòng)化解析
- XML文本到Java對(duì)象的自動(dòng)化解析
- 支持靈活的模板表達(dá)式
- 支持?jǐn)r截器處理請(qǐng)求的各個(gè)生命周期
- 支持自定義注解
后端部分:
- 支持OkHttp
- 支持Httpclient
Forest 容易上手,不需要調(diào)用HTTP底層接口初肉,而是像 Dubbo 那樣的 RPC 框架一樣酷鸦,只需要定義接口、調(diào)用接口即可牙咏。幾分鐘內(nèi)就可完成請(qǐng)求的定義井佑、發(fā)送、接收響應(yīng)眠寿、數(shù)據(jù)解析躬翁、錯(cuò)誤處理、日志打印等過程盯拱。
配置輕量盒发,遵循約定優(yōu)于配置的原則,只需在需要的時(shí)候進(jìn)行配置狡逢,不配置也不會(huì)影響Forest請(qǐng)求的正常調(diào)用宁舰。
簡(jiǎn)單優(yōu)雅,將 HTTP 請(qǐng)求細(xì)節(jié)封裝成 Java 接口 + 注解的形式奢浑,不必再關(guān)心發(fā)送 HTTP 請(qǐng)求的具體過程蛮艰。使得 HTTP 請(qǐng)求信息與業(yè)務(wù)代碼解耦,方便管理大量 HTTP 的 URL雀彼、Header壤蚜、Body 等信息即寡。
擴(kuò)展靈活,允許自定義攔截器袜刷、甚至是自定義注解聪富,以此來擴(kuò)展Forest的能力。
Forest 不需要我們編寫具體的 HTTP 調(diào)用過程著蟹,只需要定義一個(gè)接口墩蔓,然后通過 Forest 注解將 HTTP 請(qǐng)求的信息添加到接口的方法上即可。請(qǐng)求發(fā)送方通過調(diào)用定義的接口就能自動(dòng)發(fā)送請(qǐng)求和接受請(qǐng)求的響應(yīng)萧豆。
Forest 之所以能做到這樣奸披,是因?yàn)樗鼘⒍x好的接口通過動(dòng)態(tài)代理的方式生成了一個(gè)具體的實(shí)現(xiàn)類,然后組織涮雷、驗(yàn)證 HTTP 請(qǐng)求信息源内,綁定動(dòng)態(tài)數(shù)據(jù),轉(zhuǎn)換數(shù)據(jù)形式份殿,SSL 驗(yàn)證簽名,調(diào)用后端 HTTP API執(zhí)行實(shí)際請(qǐng)求嗽交,等待響應(yīng)卿嘲,失敗重試,轉(zhuǎn)換響應(yīng)數(shù)據(jù)到 Java 類型等臟活累活都由這動(dòng)態(tài)代理的實(shí)現(xiàn)類給包了夫壁。
廢話就不再多說拾枣,直接開始實(shí)戰(zhàn)。
第一步盒让,添加 Maven 依賴梅肤。
<dependency>
<groupId>com.dtflys.forest</groupId>
<artifactId>forest-core</artifactId>
<version>1.5.1</version>
</dependency>
第二步,構(gòu)建 HTTP 請(qǐng)求邑茄。
在 Forest 中姨蝴,所有的 HTTP 請(qǐng)求信息都要綁定到某一個(gè)接口的方法上,不需要編寫具體的代碼去發(fā)送請(qǐng)求肺缕。請(qǐng)求發(fā)送方通過調(diào)用事先定義好 HTTP 請(qǐng)求信息的接口方法左医。
public interface ForRestClient {
@Request(
url = "http://httpbin.org/post",
type = "POST"
)
String simplePost(@Body("name") String name);
}
通過 @Post
注解,將上面的ForRestClient接口中的 simplePost()
方法綁定了一個(gè) HTTP 請(qǐng)求同木,使用 POST 方式浮梢,可以使用@Body注解修飾參數(shù)的方式,將傳入?yún)?shù)的數(shù)據(jù)綁定到 HTTP 請(qǐng)求體中彤路。然后將請(qǐng)求響應(yīng)的數(shù)據(jù)以String的方式返回給調(diào)用者秕硝。
第三步,調(diào)用接口洲尊。
public class ForRestDemo {
public static void main(String[] args) {
// 實(shí)例化Forest配置對(duì)象
ForestConfiguration configuration = ForestConfiguration.configuration();
configuration.setBackendName("httpclient");
// 通過Forest配置對(duì)象實(shí)例化Forest請(qǐng)求接口
ForRestClient myClient = configuration.createInstance(ForRestClient.class);
// 調(diào)用Forest請(qǐng)求接口远豺,并獲取響應(yīng)返回結(jié)果
String result = myClient.simplePost("二哥");
System.out.println(result);
}
}
ForestConfiguration為 Forest 的全局配置對(duì)象類奈偏,所有的 Forest 的全局基本配置信息由此類進(jìn)行管理。
可以來看一下運(yùn)行后的日志信息:
{
"args": {},
"data": "",
"files": {},
"form": {
"name": "\u4e8c\u54e5"
},
"headers": {
"Content-Length": "23",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Apache-HttpClient/4.5.2 (Java/11.0.8)",
"X-Amzn-Trace-Id": "Root=1-60b533aa-58b41e4967803d99593c53a0"
},
"json": null,
"origin": "161.81.21.32",
"url": "http://httpbin.org/post"
}
此時(shí)憋飞,一個(gè)簡(jiǎn)單的 Forest 上手小栗子就跑通了霎苗。
如果是 Spring Boot 項(xiàng)目的話,就不需要 ForestConfiguration 了榛做,只需要在啟動(dòng)類或者配置類上添加 @ForestScan
注解就可以了唁盏。
@SpringBootApplication
@Configuration
@ForestScan(basePackages = "com.yoursite.client")
public class MyApp {
...
}
Forest 除了支持GET和POST,也支持其他幾種 HTTP 請(qǐng)求方式检眯,比如PUT厘擂、HEAD、 OPTIONS锰瘸、DELETE刽严。只需要在構(gòu)建接口的時(shí)候使用對(duì)應(yīng)的注解就可以了,比如說 PUT:
// PUT請(qǐng)求
@Put("http://localhost:8080/hello")
String simplePut();
在POST和PUT請(qǐng)求方法中避凝,通常使用 HTTP 請(qǐng)求體進(jìn)行數(shù)據(jù)傳輸舞萄,在 Forest 中,可以使用 @Body
管削、@JSONBody
倒脓、@XMLBody
等多種方式設(shè)置請(qǐng)求體數(shù)據(jù)。
/**
* 直接修飾一個(gè)JSON字符串
*/
@Post("http://localhost:8080/hello/user")
String helloUser(@JSONBody String userJson);
Forest 請(qǐng)求會(huì)自動(dòng)將響應(yīng)的返回?cái)?shù)據(jù)反序列化成對(duì)應(yīng)的數(shù)據(jù)類型含思,分兩步走崎弃。
第一步:定義dataType屬性
dataType屬性指定了該請(qǐng)求響應(yīng)返回的數(shù)據(jù)類型,可選的數(shù)據(jù)類型有三種: text, json, xml含潘,默認(rèn)為 text饲做。
/**
* dataType為json或xml時(shí),F(xiàn)orest會(huì)進(jìn)行相應(yīng)的反序列化
*/
@Request(
url = "http://localhost:8080/text/data",
dataType = "json"
)
Map getData();
第二步:指定反序列化的目標(biāo)類型
反序列化需要一個(gè)目標(biāo)類型遏弱,而該類型其實(shí)就是方法的返回值類型盆均,如返回值為String就會(huì)反序列成String字符串,返回值為Map就會(huì)反序列化成一個(gè)HashMap對(duì)象漱逸,也可以指定為自定義的Class類型缀踪。
如果有這樣一個(gè) User 類:
public class User {
private String username;
private String score;
// Setter和Getter ...
}
返回的數(shù)據(jù)為 JSON 字符串:
{"username": "Foo", "score": "82"}
那請(qǐng)求接口就應(yīng)該定義成這樣:
/**
* dataType屬性指明了返回的數(shù)據(jù)類型為JSON
*/
@Get(
url = "http://localhost:8080/user?id=${0}",
dataType = "json"
)
User getUser(Integer id)
另外,大家需要了解一下 Gzip虹脯,它是現(xiàn)在一種流行的文件壓縮算法驴娃,有相當(dāng)廣泛的應(yīng)用范圍。尤其是當(dāng)Gzip用來壓縮存文本文件的時(shí)候效果尤為明顯循集,大概能減少70%以上的文件大小唇敞。很多 HTTP 服務(wù)器都支持 Gzip,比如 Tomcat,經(jīng)過這些服務(wù)壓縮過的數(shù)據(jù)可以降低網(wǎng)絡(luò)傳輸?shù)牧髁拷幔岣呖蛻舳说捻憫?yīng)速度咒精。
Forest從1.5.2-BETA版本開始支持Gzip的解壓,其解壓的方式也很簡(jiǎn)單旷档,在方法或接口類上加上 @DecompressGzip 注解即可模叙。
/**
* 為請(qǐng)求方法添加Gzip解壓能力
*/
@Get("/transaction")
@DecompressGzip
String transaction(String infno);
更重要的一點(diǎn)是,F(xiàn)orest 可以通過設(shè)置@Request注解的async屬性為true來實(shí)現(xiàn)異步請(qǐng)求鞋屈。
@Request(
url = "http://localhost:8080/hello/user?username=${0}",
async = true,
headers = {"Accept:text/plain"}
)
void asyncGet(String username范咨, OnSuccess<String> onSuccess);
異步請(qǐng)求時(shí),通過 OnSuccess<T>
回調(diào)函數(shù)來接受響應(yīng)數(shù)據(jù)厂庇,而不是通過接口方法的返回值渠啊,所以這里的返回值類型一般會(huì)定義為void。
調(diào)用該接口方法時(shí)权旷,可以通過下面的方式:
myClient.send("foo", (String resText, ForestRequest request, ForestResponse response) -> {
// 成功響應(yīng)回調(diào)
System.out.println(resText);
},
(ForestRuntimeException ex, ForestRequest request, ForestResponse response) -> {
// 異程骝龋回調(diào)
System.out.println(ex.getMessage());
});
除了上面提到的這些功能,F(xiàn)orset 還支持更高級(jí)的用法:
- HTTPS
- 文件上傳下載
- 攔截器
- 使用代理
- 自定義注解
大家可以去看一下 Forset 的官方文檔拄氯,然后在本地實(shí)踐一下躲查,還是能學(xué)到不少知識(shí)的,尤其是 HTTPS 和文件上傳下載這塊译柏,只需要簡(jiǎn)單的配置就能完成镣煮,我個(gè)人感覺還是挺值得去學(xué)習(xí)和借鑒的。
開源精神難能可貴艇纺,好的開源需要大家的添磚加瓦和支持。希望這篇文章能給大家在選擇 HTTP 客戶端框架時(shí)帶來一個(gè)新的選擇邮弹,對(duì)黔衡,就是 Forest。
這篇文章不僅介紹了 Forest 這個(gè)輕量級(jí)的 HTTP 客戶端框架腌乡,還回顧了它的底層實(shí)現(xiàn):HttpClient 和 OkHttp盟劫,希望能對(duì)大家有所幫助。
我是二哥呀与纽,我們下期見~