感興趣的朋友,可以關(guān)注微信服務(wù)號(hào)“猿學(xué)堂社區(qū)”,或加入“猿學(xué)堂社區(qū)”微信交流群
版權(quán)聲明:本文由作者自行翻譯,未經(jīng)作者授權(quán),不得隨意轉(zhuǎn)發(fā)。
現(xiàn)代應(yīng)用很少位于孤島之上,因?yàn)樗鼈冃枰c其它(分布式)應(yīng)用和服務(wù)集成。集成通常采用API實(shí)現(xiàn),這些API通過(guò)多功能的HTTP協(xié)議公開(kāi)。
本章展示了如何使用Vert.x的HTTP客戶端API與第三方Web服務(wù)集成。
6.1 場(chǎng)景:備份到GitHub Gist
GitHub Gist服務(wù)(https://gist.github.com/)因分享代碼片段到全世界而廣受歡迎。其它服務(wù)可以使用它,一個(gè)例子就是Medium publishing platform,在該平臺(tái),Gist鏈接使代碼片段可以嵌入到出版物內(nèi)部。
GitHub公開(kāi)了一個(gè)詳細(xì)的API用于獲取、創(chuàng)建、更新和刪除Gist。該API使用以http://api.github.com/開(kāi)始的HTTPS端點(diǎn)和JSON荷載。
雖然許多操作使用OAuth認(rèn)證從客戶端獲取授權(quán),但在匿名的情況下創(chuàng)建Gist是可能的。我們將利用這個(gè)特征備份我們的wiki頁(yè)面為Gist。
一個(gè)新的按鈕將被添加到wiki index頁(yè)面:
點(diǎn)擊backup按鈕將觸發(fā)一個(gè)Gist的創(chuàng)建操作:
備份Gist由每個(gè)Wiki頁(yè)面一個(gè)文件組成,內(nèi)容是未加工的Markdown文本:
6.2 更新數(shù)據(jù)庫(kù)服務(wù)
在我們深入Web客戶端API,并執(zhí)行到其它服務(wù)的HTTP請(qǐng)求之前,我們需要更新數(shù)據(jù)庫(kù)服務(wù)API,一次性獲取所有的Wiki頁(yè)面。這相當(dāng)于添加下面的SQL查詢到db-queries.properties:
all-pages-data=select * from Pages
添加一個(gè)新的方法到WikiDatabaseService接口:
@Fluent
WikiDatabaseService fetchAllPagesData(Handler<AsyncResult<List<JsonObject>>> resultHandler);
WikiDatabaseServiceImpl中的實(shí)現(xiàn)如下:
@Override
public WikiDatabaseService fetchAllPagesData(Handler<AsyncResult<List<JsonObject>>> resultHandler) {
dbClient.query(sqlQueries.get(SqlQuery.ALL_PAGES_DATA), queryResult -> {
if (queryResult.succeeded()) {
resultHandler.handle(Future.succeededFuture(queryResult.result().getRows()));
} else {
LOGGER.error("Database query error", queryResult.cause());
resultHandler.handle(Future.failedFuture(queryResult.cause()));
}
});
return this;
}
6.3 Web客戶端API
Vert.x核心庫(kù)在vertx上下文對(duì)象提供了一個(gè)createHttpClient方法。io.vertx.core.http.HttpClient實(shí)例提供了底層的方法用于執(zhí)行所有類型的HTTP請(qǐng)求,并對(duì)協(xié)議和事件流進(jìn)行細(xì)粒度控制。
Web客戶端API提供了一個(gè)簡(jiǎn)單的外觀,尤其是簡(jiǎn)化了荷載的編組與解組。該API以一種新的依賴的形式出現(xiàn):
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
<version>${vertx.version}</version>
</dependency>
以下是來(lái)自一個(gè)單元測(cè)試的樣例用法。它啟動(dòng)一個(gè)HTTP服務(wù)器,然后使用Web客戶端API執(zhí)行一個(gè)HTTP GET請(qǐng)求,檢查到服務(wù)器的請(qǐng)求是否成功:
@Test
public void start_http_server(TestContext context) {
Async async = context.async();
vertx.createHttpServer()
.requestHandler(req -> req.response().putHeader("Content-Type", "text/plain").end("Ok"))
.listen(8080,context.asyncAssertSuccess(server -> {
WebClient webClient = WebClient.create(vertx);
webClient.get(8080, "localhost", "/").send(ar -> {
if (ar.succeeded()) {
HttpResponse<Buffer> response = ar.result();
context.assertTrue(response.headers().contains("Content-Type"));
context.assertEquals("text/plain",response.getHeader("Content-Type"));
context.assertEquals("Ok", response.body().toString());
webClient.close();
async.complete();
} else {
async.resolve(Future.failedFuture(ar.cause()));
}
});
}));
}
6.4 創(chuàng)建匿名的Gist
我們首先創(chuàng)建一個(gè)Web客戶端對(duì)象,然后向Gist API發(fā)出HTTP請(qǐng)求:
webClient = WebClient.create(vertx, new WebClientOptions()
.setSsl(true)
.setUserAgent("vert-x3"));
由于請(qǐng)求使用HTTPS構(gòu)造,我們需要配置Web客戶端支持SSL。
?
GitHub API需要一個(gè)有效的User-Agent頭,并請(qǐng)求一個(gè)GitHub賬號(hào)或者組織標(biāo)識(shí)。我們復(fù)寫(xiě)默認(rèn)的User-Agent為vert-x3,但是你可以選擇你自己的值替代。
我們接下來(lái)修改HttpServerVerticle類中的Web路由配置,為觸發(fā)備份添加一個(gè)新的路由:
router.get("/backup").handler(this::backupHandler);
該處理器的代碼如下:
private void backupHandler(RoutingContext context) {
dbService.fetchAllPagesData(reply -> {
if (reply.succeeded()) {
JsonObject filesObject = new JsonObject();
JsonObject gistPayload = new JsonObject() ①
.put("files", filesObject)
.put("description", "A wiki backup")
.put("public", true);
reply.result().forEach(page -> {
JsonObject fileObject = new JsonObject(); ②
filesObject.put(page.getString("NAME"), fileObject);
fileObject.put("content", page.getString("CONTENT"));
});
webClient.post(443, "api.github.com", "/gists") ③
.putHeader("Accept", "application/vnd.github.v3+json") ④
.putHeader("Content-Type", "application/json")
.as(BodyCodec.jsonObject()) ⑤
.sendJsonObject(gistPayload, ar -> { ⑥
if (ar.succeeded()) {
HttpResponse<JsonObject> response = ar.result();
if (response.statusCode() == 201) {
context.put("backup_gist_url", response.body().getString("html_url")); ⑦
indexHandler(context);
} else {
StringBuilder message = new StringBuilder()
.append("Could not backup the wiki: ")
.append(response.statusMessage());
JsonObject body = response.body();
if (body != null) {
message.append(System.getProperty("line.separator"))
.append(body.encodePrettily());
}
LOGGER.error(message.toString());
context.fail(502);
}
} else {
Throwable err = ar.cause();
LOGGER.error("HTTP Client error", err);
context.fail(err);
}
});
} else {
context.fail(reply.cause());
}
});
}
① Gist創(chuàng)建的請(qǐng)求載荷(payload)是一個(gè)JSON文檔,如GitHub API文檔(https://developer.github.com/v3/gists/#create-a-gist)中所描述。
② 每個(gè)文件是載荷files對(duì)象中的一條記錄,title為鍵,值為文本。
③ web客戶端需要發(fā)出一個(gè)POST請(qǐng)求到443端口(HTTPS),路徑必須為/gists。
④ 請(qǐng)求中強(qiáng)制包含一個(gè)Accept頭,使用application/vnd.github.v3+json MIME類型,否則請(qǐng)求會(huì)失敗。在下一行中指定有效載荷是JSON對(duì)象也很重要。
⑤ BodyCodec類提供了一個(gè)助手,明確規(guī)定將響應(yīng)直接轉(zhuǎn)換為Vert.x的JsonObject實(shí)例。它也可以使用BodyCodec#json(Class<T>)方法,將JSON數(shù)據(jù)將映射為一個(gè)Java對(duì)象,類型為T(mén)(底層使用了Jackson數(shù)據(jù)映射)。
⑥ sendJsonObject是一個(gè)助手方法,用于觸發(fā)JSON載荷的HTTP請(qǐng)求。
⑦ 當(dāng)成功時(shí),我們可以通過(guò)JSON數(shù)據(jù)(html_url鍵)來(lái)獲得新建Gist的用戶友好的URL。