前言
最近翻譯了vert.x官網的兩篇pdf,一個講的的是做一個web應用-wiki,使用了數據庫連接,自動生成服務代碼,生成多個實例,verticle通過event loop通信,這些我們經常用到的知識。
另一個講的是微服務,講了集羣,服務註冊,event loop調用,等。
雖然我也按示例把代碼運行起來了,但是還是感覺到迷茫。
通過那兩篇教程,我知道了Vert.x的一些特性,比如不能阻塞event loop,如何用命令部署集羣,如何監聽http端口,如何調用數據庫,但是還是無法再腦子中構想出一整套完成的系統實現,這大概就是缺乏經驗吧,獲取是基礎還不夠紮實。
於是我想到了翻譯一遍Vert.x core,這樣也相當於自己也看了一遍,雖然我不翻譯直接看英文比較快些,但是翻譯一下並加入自己的想法就相當於做筆記了。
Vert.x core 提供了這些功能:
- 寫TCP客戶端和服務端
- 寫HTTP客戶端和服務端包括支持WebSockets
- 事件總線
- 共享數據-本地的map和集羣分佈式map
- 週期和延遲行動(這個不理解)
- Datagram Sockets
- DNS客戶端
- 文件系統訪問
- 高可用性
- 本地傳輸
- 集羣
Vert.core是底層的架構,你不會找到數據庫訪問這樣的web高級功能,這樣的功能在Vert.x ext中。
Vert.x core很輕巧。你可以只是用你需要的部分。它也完全可以嵌入到您現有的應用程序中——我們不會強制您以一種特殊的方式來構造您的應用程序,這樣您就可以使用Vert.x了。
你可以用其他語言,包括JavaScript Ruby。
從現在開始我們用core代替Vert.x core.
如果你用Maven添加依賴在pom.xml中
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>3.5.1</version>
</dependency>
如果你用Gradle,添加在build.gradle中
dependencies {
compile 'io.vertx:vertx-core:3.5.1'
}
在最開始添加Vert.x
如何實例化Vert.x?
Vertx vertx = Vertx.vertx();
在大多數應用中你只需要實例化一次Vert.x,但是在一些系統中也需要實例化很多次,裏如果孤立的verticle,不同組的服務端和客戶端。
指定參數當創建Vertx對象時
當創建Vertx對象時你可以指定參數:
Vertx vertx = Vertx.vertx(new VertxOptions().setWorkerPoolSize(40));
詳情查看VertxOptions
創建集羣的Vertx對象
你需要使用異步變體來創建Vertx,因爲不同的Vertx啓動需要時間,在他們啓動時我們不希望去調用他們,會導致阻塞。(我是這麼理解的)
Are you fluent?
funent就是幾個方法可以連接在一起,例如:
request.response().putHeader("Content-Type", "text/plain").write("some text").end();
這在vert.x中很常見。連接調用可以減少代碼冗餘,但是如果你不喜歡,我們也不強迫你用,你可以繼續用這種弱智纔會用的方法:
HttpServerResponse response = request.response();
response.putHeader("Content-Type", "text/plain");
response.write("some text");
response.end();
不要調用我們,我們來調用你
Vert.x使用大量的事件驅動,那意味着,如果你關注的事件發生了,我們就會通知你。
這些事件例如:
- 一個定時器到期了
- 一些數據發送到了socket
- 一些數據被從硬盤上讀取
- 發生了異常
- HTTP收到了請求
你可以通過向Vert.x API提供處理程序來處理事件。例如你可以這樣做來接受timer每秒發出的事件。
vertx.setPeriodic(1000, id -> {
// This handler will get called every second
System.out.println("timer fired!");
});
或者接收HTTP請求:
server.requestHandler(request -> {
// This handler will be called every time an HTTP request is received at the server
request.response().end("hello world!");
});
一段時間後,當Vert.x有一個事件傳遞給你的處理程序時,Vert.x會異步調用它。
不要阻塞我
除了極少的例外(例如以‘Sync’結尾的文件),沒有Vert.x API會阻塞調用線程。
如果一個結果可以立馬提供,那麼他將會立馬被提供。否則你通常需要提供一個處理來在一段時間後接收事件。
因爲沒有任何Vert.x API會阻塞線程,這意味着您可以使用Vert.x來使用少量線程處理大量併發。
使用傳統的阻塞API時,調用線程可能會在以下情況下阻塞:
- 從socket讀取字節
- 將數據寫入磁盤
- 發送消息並等待回覆
- 許多其他情況
在所有上述情況下,當你的線程在等待結果時,他不能做其他任何事情--這實際上是沒用的。
反應堆和多反應堆
我們之前提到Vert.x API是事件驅動的 - Vert.x在事件可用時將事件傳遞給處理程序。
在大多數情況下,Vert.x使用稱爲事件循環的線程調用處理程序。
由於Vert.x或您的應用程序塊中沒有任何內容,因此事件循環可以在到達時連續不斷地向不同的處理程序傳遞事件。
由於沒有任何東西阻塞,事件循環可能會在很短的時間內發送大量事件。例如,單個事件循環可以很快處理數千個HTTP請求。
我們稱之爲反應堆模式。
你可能以前聽說過這個 - 例如Node.js實現了這種模式。
在一個標準的反應器實現中,有一個事件循環線程在循環中運行,並在所有處理程序到達時將所有事件傳遞給所有處理程序。
單線程的問題在於它只能在單個內核上運行,所以如果您希望單線程反應器應用程序(例如Node.js應用程序)在多核服務器上擴展,您必須啓動並管理許多不同的流程。
Vert.x在這裏工作方式不同。每個Vertx實例不是單個事件循環,而是維護多個事件循環。默認情況下,我們根據機器上可用內核的數量來選擇數量,但這可以被覆蓋。
這意味着與Node.js不同,單個Vertx進程可以跨服務器進行擴展。
我們稱這種模式爲多反應堆模式,將其與單線程反應堆模式區分開來。
注意:即使Vertx實例維護多個事件循環,任何特定的處理程序也不會同時執行,並且在大多數情況下(除了工作者Verticle)將始終使用完全相同的事件循環進行調用。
黃金法則 - 不要阻止事件循環
我們已經知道Vert.x API是非阻塞的,並且不會阻塞事件循環,但是如果您自己在處理程序中阻止事件循環,這並沒有多大幫助。
如果你這樣做,那麼事件循環在被阻塞時將無法做任何事情。如果您阻止Vertx實例中的所有事件循環,那麼您的應用程序將完全停止!
所以不要這樣做!你已被警告。
阻止的例子包括:
Thread.sleep()方法
等待鎖定
等待互斥或監視器(例如同步段)
做一個長期的數據庫操作並等待結果
做一個複雜的計算,需要一些時間。
旋轉循環
如果以上任何一種情況都會阻止事件循環在很長一段時間內做其他事情,那麼您應該立即進入調皮步驟,並等待進一步的指示。
那麼...什麼是相當長的時間?
一段繩子有多長?這實際上取決於您的應用程序和您需要的併發量。
如果您有一個事件循環,並且您希望每秒處理10000個HTTP請求,那麼很明顯每個請求的處理時間不能超過0.1毫秒,因此您無法再阻止超過此時間。
數學不難,應該留給讀者作爲練習。
如果你的應用程序沒有響應,它可能是一個跡象表明你在某個地方阻塞了一個事件循環。爲了幫助您診斷這些問題,Vert.x會在檢測到事件循環未返回一段時間時自動記錄警告。如果您在日誌中看到類似這樣的警告,那麼您應該進行調查。
線程vertex-eventloop-thread-3已被阻止20458毫秒
Vert.x還將提供堆棧跟蹤以精確確定發生阻塞的位置。
如果您想關閉這些警告或更改設置,可以 VertxOptions在創建Vertx對象之前在對象中執行此操作。
運行阻塞代碼
在一個完美的世界裏,不會有戰爭或飢餓感,所有的API都將被異步書寫,兔子兔子會在陽光明媚的綠色草地上與小羊羔手拉手。
但是......現實世界並非如此。(你最近看過這個消息嗎?)
事實上,即使不是大多數庫,尤其是在JVM生態系統中也有很多同步API,並且許多方法可能會被阻止。一個很好的例子就是JDBC API--它本質上是同步的,不管它如何努力,Vert.x都不能在它上面噴灑魔法精靈來使其異步。
我們不會將所有內容重寫爲異步,因此我們需要爲您提供一種在Vert.x應用程序中安全地使用“傳統”阻止API的方法。
正如前面所討論的,你不能直接從事件循環調用阻塞操作,因爲這會阻止它做任何其他有用的工作。那麼你怎麼能做到這一點?
這是通過調用executeBlocking指定要執行的阻止代碼以及在阻止代碼執行時返回異步的結果處理程序來完成的。
vertx.executeBlocking(future -> {
// Call some blocking API that takes a significant amount of time to return
String result = someAPI.blockingMethod("hello");
future.complete(result);
}, res -> {
System.out.println("The result is: " + res.result());
});
默認情況下,如果從相同的上下文中調用多次executeBlocking(例如,相同的Verticle實例),則不同的executeBlocking會連續執行(即一個接一個地執行)。
如果你不關心訂購,你可以調用executeBlocking
指定false
作爲參數ordered
。在這種情況下,可以在工作池上並行執行任何executeBlocking。
運行阻止代碼的另一種方法是使用工作者Verticle
工作者Verticle始終使用來自工作池的線程執行。
默認情況下,阻止代碼在Vert.x工作池上執行,配置爲setWorkerPoolSize
。
可以爲不同的目的創建額外的池:
WorkerExecutor executor = vertx.createSharedWorkerExecutor("my-worker-pool");
executor.executeBlocking(future -> {
// Call some blocking API that takes a significant amount of time to return
String result = someAPI.blockingMethod("hello");
future.complete(result);
}, res -> {
System.out.println("The result is: " + res.result());
});
工人執行者在不再需要時必須關閉:
executor.close();
當幾個工人以同樣的名字創建時,他們將共享相同的池。當所有使用它的工作執行者都關閉時,工作者池被銷燬。在Verticle中創建執行程序時,Verticle將在解除部署後自動爲您自動關閉。
工人執行者可以在創建時進行配置:
int poolSize = 10;
// 2 minutes
long maxExecuteTime = 120000;
WorkerExecutor executor = vertx.createSharedWorkerExecutor("my-worker-pool", poolSize, maxExecuteTime);
注意:該配置是在創建工作池時設置的異步協調
Vert.x可以實現多個異步結果的協調futures。它支持併發組合(並行運行多個異步操作)和順序組合(鏈式異步操作)。
同時組成
CompositeFuture.all需要幾個期貨參數(最多6個),並返回一個未來的 成功,當所有的期貨和失敗時的期貨至少一次失敗:Future<HttpServer> httpServerFuture = Future.future();
httpServer.listen(httpServerFuture.completer());
Future<NetServer> netServerFuture = Future.future();
netServer.listen(netServerFuture.completer());
CompositeFuture.all(httpServerFuture, netServerFuture).setHandler(ar -> {
if (ar.succeeded()) {
// All servers started
} else {
// At least one server failed
}
});
這些操作同時運行,Handler在完成構圖時調用附加到返回的未來。當其中一個操作失敗時(其中一個未來被標記爲失敗),結果未來也被標記爲失敗。當所有的行動取得成功後,最終的未來就會取得成功。
或者,您可以傳遞一份期貨清單(可能爲空):
CompositeFuture.all(Arrays.asList(future1, future2, future3));
雖然all組成等待,直到所有的期貨是成功的(或一個發生故障),則any組成 等待第一個成功的未來。CompositeFuture.any需要幾個期貨論點(高達6),並返回一個未來,當一個期貨是成功時,而所有期貨都失敗時失敗:
CompositeFuture.any(future1, future2).setHandler(ar -> {
if (ar.succeeded()) {
// At least one is succeeded
} else {
// All failed
}
});
A list of futures can be used also:
CompositeFuture.any(Arrays.asList(f1, f2, f3));
CompositeFuture.any(Arrays.asList(f1, f2, f3));
該join
組成等待,直到所有的期貨都完成,要麼成功或失敗。 CompositeFuture.join
需要幾個期貨論點(高達6),並返回一個未來,當所有的期貨都成功時,成功,而當所有的期貨完成並且至少其中一個失敗時,失敗:
CompositeFuture.join(future1, future2, future3).setHandler(ar -> {
if (ar.succeeded()) {
// All succeeded
} else {
// All completed and at least one failed
}
});
期貨清單也可以使用:
CompositeFuture.join(Arrays.asList(future1, future2, future3));
順序組成
而all
與any
正在執行的併發組合物,compose
可用於鏈接期貨(所以順序組合)。
FileSystem fs = vertx.fileSystem();
Future<Void> startFuture = Future.future();
Future<Void> fut1 = Future.future();
fs.createFile("/foo", fut1.completer());
fut1.compose(v -> {
// When the file is created (fut1), execute this:
Future<Void> fut2 = Future.future();
fs.writeFile("/foo", Buffer.buffer(), fut2.completer());
return fut2;
}).compose(v -> {
// When the file is written (fut2), execute this:
fs.move("/foo", "/bar", startFuture.completer());
},
// mark startFuture it as failed if any step fails.
startFuture);
在這個例子中,3個操作是鏈接的:
一個文件被創建(
fut1
)(
fut2
)文件中寫入了一些東西該文件被移動(
startFuture
)
當這3個步驟成功時,最後的未來(startFuture
)成功了。但是,如果其中一個步驟失敗,那麼最終的未來將失敗。
這個例子使用:
在第二種情況下,Handler
應完成next
未來報告其成敗。
您可以使用completer
它來完成未來的操作結果或失敗。它避免了必須寫傳統:if success then complete the future else fail the future
。
Verticles
Vert.x提供了一個簡單的,可擴展的,類似actor的部署和併發模型,您可以使用它來保存自己編寫的代碼。
這個模型完全是可選的,如果你不想要,Vert.x不會強迫你以這種方式創建你的應用程序。。
該模型並沒有聲稱是一個嚴格的actor模型實現,但它確實有共同之處,特別是在併發性,擴展性和部署方面。
要使用這個模型,你需要將你的代碼編寫成一套Verticle。
Verticle是由Vert.x部署和運行的代碼塊。Vert.x實例默認維護N個事件循環線程(默認情況下N是core * 2)。Verticle可以使用Vert.x支持的任何語言編寫,並且單個應用程序可以包括使用多種語言編寫的Verticle。
應用程序通常由同一Vert.x實例中同時運行的許多Verticle實例組成。不同的Verticle實例通過在事件總線上發送消息來相互通信。
寫Verticles
Verticle類必須實現Verticle
接口。
如果你喜歡,他們可以直接實現它,但通常擴展抽象類更簡單AbstractVerticle
。
以下是一個示例Verticle:
公共類MyVerticle擴展AbstractVerticle { //在Verticle部署時調用 public void start(){ } //可選 - 在展開Verticle時調用 public void stop(){ } }
通常情況下,您可以像上例中那樣覆蓋啓動方法。
當Vert.x部署Verticle時,它將調用start方法,並且當方法完成時,Verticle將被視爲啓動。
您也可以選擇覆蓋停止方法。當Verticle被取消部署並且當方法完成Verticle時,Vert.x會調用這個函數將被視爲停止。
異步Verticle啓動和停止
有時候,你想在垂直啓動中做一些事情,這需要一些時間,並且你不希望Verticle被部署到這種情況發生。例如,您可能想要在start方法中啓動HTTP服務器並傳播服務器listen
方法的異步結果。
您不能阻止等待HTTP服務器在您的開始方法中綁定,因爲這會破壞黃金法則。
那麼你怎麼能做到這一點?
做到這一點的方法是實現異步啓動方法。該版本的方法將Future作爲參數。當方法返回時,Verticle將不被視爲已部署。
一段時間後,當你完成了所有你需要做的事情(例如啓動HTTP服務器)之後,你可以在Future上調用complete(或失敗)來表明你已經完成了。
這是一個例子:
public class MyVerticle extends AbstractVerticle { private HttpServeer server; public void start(Future<Void> startFuture) { server = vertx.createHttpServer().requestHandler(req -> { req.response() .putHeader("content-type", "text/plain") .end("Hello from Vert.x!"); }); // Now bind the server: server.listen(8080, res -> { if (res.succeeded()) { startFuture.complete(); } else { startFuture.fail(res.cause()); } }); } }
同樣,也有停止方法的異步版本。如果你想做一些需要一些時間的Verticle清理,你可以使用它。
public class MyVerticle extends AbstractVerticle { public void start() { // Do something } public void stop(Future<Void> stopFuture) { obj.doSomethingThatTakesTime(res -> { if (res.succeeded()) { stopFuture.complete(); } else { stopFuture.fail(); } }); } }
信息:您不需要在Verticle的停止方法中手動啓動Verticle的HTTP服務器。Vert.x將在卸載Verticle時自動停止正在運行的服務器。
Verticle Types
有三種不同類型的垂直軸:
標準垂直這些是最常見和有用的類型 - 它們總是使用事件循環線程執行。我們將在下一節中進一步討論這一點。
工人Verticles這些使用來自工作池的線程運行。一個實例永遠不會由多個線程同時執行。
多線程工作者垂直這些使用來自工作池的線程運行。一個實例可以由多個線程同時執行。
標準Verticle
標準Verticle在創建時會分配一個事件循環線程,並且使用該事件循環調用start方法。當您調用任何其他方法從事件循環接收核心API的處理程序時,Vert.x將確保這些處理程序在被調用時將在相同的事件循環中執行。
這意味着我們可以保證Verticle實例中的所有代碼始終在相同的事件循環中執行(只要您不創建自己的線程並將其調用!)。
這意味着您可以將應用程序中的所有代碼編寫爲單線程,並讓Vert.x擔心線程和縮放。再也不用擔心同步和易失性了,而且在進行手動“傳統”多線程應用程序開發時,您也避免了許多其他競爭條件和死鎖情況。
工人垂直
工作者Verticle就像標準Verticle一樣,但是它使用Vert.x工作線程池中的線程執行,而不是使用事件循環。
Worker Verticle專門用於調用阻止代碼,因爲它們不會阻止任何事件循環。
如果您不想使用工作者Verticle運行阻止代碼,則還可以 在事件循環中直接運行內嵌阻止代碼。
如果你想部署一個Verticle作爲工作者Verticle,你可以使用它setWorker
。
DeploymentOptions options = new DeploymentOptions().setWorker(true);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options);
工作者Verticle實例絕不會由Vert.x由多個線程同時執行,但可以由不同時間的不同線程執行。
多線程工作者垂直
多線程工作者Verticle就像普通工作者Verticle,但可以由不同的線程同時執行。
警告 | 多線程工作者Verticle是一項高級功能,大多數應用程序都不需要它們。由於這些Verticle中的併發性,您必須非常小心地使用標準Java技術來保持Verticle處於一致狀態,以便進行多線程編程。 |
以編程方式部署Verticle
您可以使用其中一種deployVerticle
方法來部署Verticle ,指定一個Verticle名稱,或者您可以傳入您已經創建的Verticle實例。
注意 | 部署Verticle 實例僅限Java。 |
Verticle myVerticle = new MyVerticle();
vertx.deployVerticle(myVerticle);
您也可以通過指定垂直名稱來部署垂直。
Verticle名稱用於查找VerticleFactory
將用於實例化實際Verticle實例的具體內容。
不同的Verticle工廠可用於實例化不同語言的Verticle,以及各種其他原因,例如加載服務並在運行時從Maven獲取Verticle。
這使您可以使用Vert.x支持的任何其他語言來部署以任何語言編寫的Verticle。
以下是部署一些不同類型的Verticle的示例:
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle");
// Deploy a JavaScript verticle
vertx.deployVerticle("verticles/myverticle.js");
// Deploy a Ruby verticle verticle
vertx.deployVerticle("verticles/my_verticle.rb");
用於將垂直名稱映射到垂直工廠的規則
使用名稱部署Verticle時,該名稱用於選擇將實例化Verticle的實際Verticle工廠。
Verticle名稱可以有一個前綴 - 這是一個字符串,後跟一個冒號,如果存在的話將用於查找工廠,例如
js:foo.js //使用JavaScript verticle工廠 groovy:com.mycompany.SomeGroovyCompiledVerticle //使用Groovy Verticle工廠 服務:com.mycompany:myorderservice //使用服務Verticle工廠
如果沒有前綴,Vert.x將查找後綴並使用它來查找工廠,例如
foo.js //也將使用JavaScript Verticle工廠 SomeScript.groovy //將使用Groovy Verticle工廠
如果沒有前綴或後綴存在,Vert.x會認爲它是一個Java完全限定類名(FQCN)並嘗試實例化它。
Verticle工廠如何定位?
大多數Verticle工廠都從類路徑加載並在Vert.x啓動時註冊。
你也可以用編程的方式註冊和取消註冊垂直工廠registerVerticleFactory
,unregisterVerticleFactory
如果你願意的話。
等待部署完成
Verticle部署是異步的,並且在部署調用返回後可能會完成一段時間。
如果您希望在部署完成時收到通知,您可以部署指定完成處理程序:
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", res -> {
if (res.succeeded()) {
System.out.println("Deployment id is: " + res.result());
} else {
System.out.println("Deployment failed!");
}
});
如果部署成功,則完成處理程序將傳遞包含部署標識字符串的結果。
如果您想取消部署部署,稍後可以使用此部署標識。
取消部署Verticle部署
部署可以用部署解除undeploy
。
Un-deployment本身是異步的,所以如果你想在un-deployment完成時得到通知,你可以部署指定一個完成處理程序:
vertx.undeploy(deploymentID, res -> {
if (res.succeeded()) {
System.out.println("Undeployed ok");
} else {
System.out.println("Undeploy failed!");
}
});
指定Verticle實例的數量
使用Verticle名稱部署Verticle時,可以指定要部署的Verticle實例的數量:
DeploymentOptions options = new DeploymentOptions().setInstances(16);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options);
這對於跨多個內核輕鬆擴展很有用。例如,您可能需要在機器上部署Web服務器Verticle和多個核心,因此您需要部署多個實例以利用所有核心。
將配置傳遞給Verticle
可以在部署時將JSON形式的配置傳遞給Verticle:
JsonObject config = new JsonObject().put("name", "tim").put("directory", "/blah");
DeploymentOptions options = new DeploymentOptions().setConfig(config);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options);
該配置作爲JSON對象返回,以便您可以按如下方式檢索數據:
System.out.println("Configuration: " + config().getString("name"));
訪問Verticle中的環境變量
使用Java API可訪問環境變量和系統屬性:
System.getProperty("prop");
System.getenv("HOME");
垂直隔離組
默認情況下,Vert.x具有扁平的類路徑。也就是說,當Vert.x使用當前類加載器部署Verticle時 - 它不會創建一個新類。在大多數情況下,這是最簡單,最清晰和最令人敬畏的事情。
但是,在某些情況下,您可能需要部署Verticle,以便將Verticle的類與應用程序中的其他類隔離開來。
例如,如果您想在同一個Vert.x實例中部署具有相同類名稱的兩個不同版本的Verticle,或者如果您有兩個使用同一個jar庫的不同版本的不同版本,則可能是這種情況。
當使用隔離組時,您提供了您想要使用的類名稱列表setIsolatedClasses
- 一個條目可以是完全限定的類名稱, 例如com.mycompany.myproject.engine.MyClass
或可以是匹配包中的任何類和任何子包的通配符,例如com.mycompany.myproject.*
將匹配包中的任何類com.mycompany.myproject
或任何子包。
請注意,只有匹配的類纔會被隔離 - 任何其他類都將被當前的類加載器加載。
還可以提供額外的類路徑條目,setExtraClasspath
因此如果要加載主類路徑中尚不存在的類或資源,您可以添加它。
警告 | 謹慎使用此功能。類加載器可能會成爲蠕蟲的一部分,並且可能會使調試變得困難等等。 |
以下是使用隔離組來隔離垂直部署的示例。
DeploymentOptions options = new DeploymentOptions().setIsolationGroup("mygroup");
options.setIsolatedClasses(Arrays.asList("com.mycompany.myverticle.*",
"com.mycompany.somepkg.SomeClass", "org.somelibrary.*"));
vertx.deployVerticle("com.mycompany.myverticle.VerticleClass", options);
高可用性
可以在啓用高可用性(HA)的情況下部署Verticle。在這種情況下,當verticle部署在突然死亡的vert.x實例上時,Verticle會從集羣中重新部署到另一個vert.x實例上。
要在啓用高可用性的情況下運行Verticle,只需追加-ha
交換機:
vertx run my-verticle.js -ha
啓用高可用性時,無需添加-cluster
。
有關高可用性和故障切換 部分中高可用性功能和配置的更多詳細信息。
從命令行運行Verticles
您可以通過向Vert.x核心庫添加依賴項並從那裏進行黑客入侵,以正常方式直接在Maven或Gradle項目中使用Vert.x。
但是,如果您願意,您也可以直接從命令行運行Vert.x Verticle。
爲此,您需要下載並安裝Vert.x發行版,並將bin
安裝目錄添加到您的PATH
環境變量中。還要確保你的Java 8 JDK PATH
。
注意 | JDK需要支持即時編譯Java代碼。 |
您現在可以使用該vertx run命令運行Verticle 。這裏有些例子:
# Run a JavaScript verticle vertx run my_verticle.js # Run a Ruby verticle vertx run a_n_other_verticle.rb # Run a Groovy script verticle, clustered vertx run FooVerticle.groovy -cluster
您甚至可以運行Java源代碼Verticle,而無需先編譯它們!
vertx run SomeJavaSourceFile.java
運行Vert.x之前,Vert.x將立即編譯Java源文件。這對快速構建Verticle原型非常有用,對於演示非常有用。無需首先設置Maven或Gradle構建即可開始!
有關vertx
在命令行上執行的各種可用選項的完整信息,請在命令行鍵入vertx
。
導致Vert.x退出
由Vert.x實例維護的線程不是守護線程,因此它們將阻止JVM退出。
如果您正在嵌入Vert.x,並且已經完成了它,則可以調用close
以關閉它。
這將關閉所有內部線程池並關閉其他資源,並允許JVM退出。
上下文對象
當Vert.x向處理程序提供事件或調用a的開始或停止方法時 Verticle
,執行與a相關聯Context
。通常,上下文是 事件循環上下文,並且與特定事件循環線程綁定。所以對於該上下文的執行總是發生在完全相同的事件循環線程上。在工作者Verticle和運行內聯阻塞代碼的情況下,工作者上下文將與將使用來自工作者線程池的線程的執行相關聯。
要檢索上下文,請使用以下getOrCreateContext
方法:
Context context = vertx.getOrCreateContext();
如果當前線程具有與其關聯的上下文,則它將重用該上下文對象。如果沒有創建新的上下文實例。您可以測試您檢索的上下文的類型:
Context context = vertx.getOrCreateContext();
if (context.isEventLoopContext()) {
System.out.println("Context attached to Event Loop");
} else if (context.isWorkerContext()) {
System.out.println("Context attached to Worker Thread");
} else if (context.isMultiThreadedWorkerContext()) {
System.out.println("Context attached to Worker Thread - multi threaded worker");
} else if (! Context.isOnVertxThread()) {
System.out.println("Context not attached to a thread managed by vert.x");
}
當你檢索到上下文對象時,你可以在這個上下文中異步運行代碼。換句話說,您提交的任務最終將在相同的上下文中運行,但稍後:
vertx.getOrCreateContext().runOnContext( (v) -> {
System.out.println("This will be executed asynchronously in the same context");
});
當幾個處理程序在相同的上下文中運行時,他們可能想共享數據。上下文對象提供方法來存儲和檢索上下文中共享的數據。例如,它可以讓你傳遞數據給一些運行 runOnContext
:
final Context context = vertx.getOrCreateContext();
context.put("data", "hello");
context.runOnContext((v) -> {
String hello = context.get("data");
});
上下文對象還允許您使用該config
方法訪問垂直配置。檢查將配置傳遞給Verticle部分以獲取有關此配置的更多詳細信息。
執行定期和延遲的行動
Vert.x非常常見,希望在延遲之後或定期執行操作。
在標準Verticle中,你不能讓線程睡眠來引入延遲,因爲這會阻塞事件循環線程。
而是使用Vert.x定時器。定時器可以是一次性的或定期的。我們將討論兩者
一次性定時器
一次性計時器在一定的延遲之後調用事件處理程序,以毫秒爲單位表示。
一旦你使用setTimer
方法傳入延遲和處理程序,就設置一個定時器來觸發
long timerID = vertx.setTimer(1000, id -> {
System.out.println("And one second later this is printed");
});
System.out.println("First this is printed");
返回值是一個唯一的定時器ID,稍後可以用來取消定時器。處理程序也傳遞了定時器ID。
定期計時器
您還可以設置一個定時器,通過使用定期觸發setPeriodic
。
會有一個初始延遲等於期間。
返回值setPeriodic
是一個唯一的計時器ID(長)。如果定時器需要取消,可以稍後使用。
傳遞給定時器事件處理程序的參數也是唯一的定時器ID:
請記住,計時器會定期啓動。如果您的定期治療需要很長時間才能繼續,您的計時器事件可能會持續或甚至更糟:堆疊。
在這種情況下,你應該考慮使用setTimer
。一旦你的治療結束,你可以設置下一個計時器。
long timerID = vertx.setPeriodic(1000, id -> {
System.out.println("And every second this is printed");
});
System.out.println("First this is printed");
自動清理Verticle
如果您從垂直內部創建定時器,那麼在解除垂直部署時,這些定時器將自動關閉。
Verticle工作者池
Verticle使用Vert.x工作池來執行阻止操作,即executeBlocking
工作者Verticle。
可以在部署選項中指定不同的工作池:
vertx.deployVerticle("the-verticle", new DeploymentOptions().setWorkerPoolName("the-specific-pool"));
事件總線
這event bus
是Vert.x 的神經系統。
每個Vert.x實例都有一個事件總線實例,並使用該方法獲取eventBus
。
事件總線允許應用程序的不同部分彼此進行通信,而不管它們使用何種語言編寫,以及它們是否位於同一個Vert.x實例中,或者位於不同的Vert.x實例中。
它甚至可以被橋接,以允許在瀏覽器中運行的客戶端JavaScript在相同的事件總線上進行通信。
事件總線形成跨多個服務器節點和多個瀏覽器的分佈式對等消息系統。
事件總線支持發佈/訂閱,點對點和請求 - 響應消息。
事件總線API非常簡單。它基本上涉及註冊處理程序,取消註冊處理程序以及發送和發佈消息。
首先是一些理論:
理論
解決
消息在事件總線上發送到一個地址。
Vert.x不打擾任何奇特的尋址方案。在Vert.x中,地址只是一個字符串。任何字符串都有效。然而,使用某種方案是明智的,例如使用句點來劃分命名空間。
有效地址的一些例子是europe.news.feed1,acme.games.pacman,香腸和X.
處理程序
消息在處理程序中收到。你在一個地址註冊一個處理程序。
許多不同的處理程序可以在同一地址註冊。
一個處理程序可以在許多不同的地址註冊。
發佈/訂閱消息
事件總線支持發佈消息。
消息發佈到一個地址。發佈意味着將消息傳遞給在該地址註冊的所有處理程序。
這是熟悉的發佈/訂閱消息模式。
點對點和請求 - 響應消息
事件總線還支持點對點消息。
消息被髮送到一個地址。然後,Vert.x會將其路由到僅在該地址註冊的其中一個處理程序。
如果在地址上註冊了多個處理程序,則將使用非嚴格的循環算法選擇一個處理程序。
使用點對點消息傳遞,可以在發送消息時指定可選的答覆處理程序。
當收件人收到郵件並已處理郵件時,收件人可以選擇決定回覆郵件。如果他們這樣做,回覆處理程序將被調用。
當發件人收到回覆時,也可以回覆。這可以無限重複,並允許在兩個不同的垂直間設置對話框。
這是一種常見的消息傳遞模式,稱爲請求 - 響應模式。
盡力而爲的交付
Vert.x最好傳遞消息,並且不會有意識地將它們丟棄。這被稱爲盡力而爲的交付。
但是,如果全部或部分事件總線出現故障,則可能會丟失信息。
如果您的應用程序關心丟失的消息,則應該將您的處理程序編碼爲冪等,並且發送者要在恢復後重試。
事件總線API
我們來看看API
獲得活動巴士
您可以參考以下事件總線:
EventBus eb = vertx.eventBus();
每個Vert.x實例有一個事件總線實例。
註冊處理程序
註冊處理程序的最簡單方法是使用consumer
。這是一個例子:
EventBus eb = vertx.eventBus();
eb.consumer("news.uk.sport", message -> {
System.out.println("I have received a message: " + message.body());
});
當消息到達您的處理程序時,您的處理程序將被調用,傳入message
。
從調用consumer()返回的對象是一個實例 MessageConsumer
隨後可以使用此對象註銷處理程序,或將該處理程序用作流。
或者,您可以使用consumer
來返回沒有處理程序集的MessageConsumer,然後設置處理程序。例如:
EventBus eb = vertx.eventBus();
MessageConsumer<String> consumer = eb.consumer("news.uk.sport");
consumer.handler(message -> {
System.out.println("I have received a message: " + message.body());
});
在羣集事件總線上註冊處理程序時,註冊需要一段時間才能到達羣集的所有節點。
如果您希望在此完成時收到通知,您可以completion handler
在MessageConsumer對象上註冊一個。
consumer.completionHandler(res -> {
if (res.succeeded()) {
System.out.println("The handler registration has reached all nodes");
} else {
System.out.println("Registration failed!");
}
});
取消註冊處理程序
要註銷處理程序,請致電unregister
。
如果您在羣集事件總線上,如果您希望在完成使用時收到通知,則取消註冊可能需要一段時間才能在節點上傳播unregister
。
consumer.unregister(res -> {
if (res.succeeded()) {
System.out.println("The handler un-registration has reached all nodes");
} else {
System.out.println("Un-registration failed!");
}
});
發佈消息
發佈消息很簡單。只需使用publish
指定地址將其發佈到。
eventBus.publish("news.uk.sport", "Yay! Someone kicked a ball");
然後該消息將被傳遞給所有針對地址news.uk.sport註冊的處理程序。
發送消息
發送消息將導致只有一個處理程序在接收消息的地址註冊。這是點對點消息模式。處理程序以非嚴格的循環方式選擇。
您可以發送消息 send
eventBus.send("news.uk.sport", "Yay! Someone kicked a ball");
在消息上設置標題
通過事件總線發送的消息也可以包含標題。
這可以通過提供DeliveryOptions
發送或發佈時指定 :
DeliveryOptions options = new DeliveryOptions();
options.addHeader("some-header", "some-value");
eventBus.send("news.uk.sport", "Yay! Someone kicked a ball", options);
消息排序
Vert.x將按照它們從任何特定發件人發送的相同順序將郵件傳遞給任何特定處理程序。
確認消息/發送回覆
當使用send
事件總線嘗試將消息傳送到 MessageConsumer
註冊了事件總線的事件時。
在某些情況下,發件人知道消費者何時收到消息並“處理”它是有用的。
要確認消息已被處理,消費者可以通過調用回覆消息reply
。
發生這種情況時,會導致將回復發送回發件人,並且回覆處理程序將與回覆一起調用。
一個例子會說明這一點:
收件人:
MessageConsumer<String> consumer = eventBus.consumer("news.uk.sport");
consumer.handler(message -> {
System.out.println("I have received a message: " + message.body());
message.reply("how interesting!");
});
發件人:
eventBus.send("news.uk.sport", "Yay! Someone kicked a ball across a patch of grass", ar -> {
if (ar.succeeded()) {
System.out.println("Received reply: " + ar.result().body());
}
});
回覆可以包含一個消息主體,其中可以包含有用的信息。
“處理”實際上是指應用程序定義的內容,完全取決於消息使用者所做的事情,而不是Vert.x事件總線本身知道或關心的事情。
一些例子:
一個簡單的消息使用者實現一個返回一天中的時間的服務將通過包含回覆正文中的時間的消息來確認
實現持久隊列的消息使用者可能會確認
true
消息是否已成功保存在存儲器中,false
如果不成功,處理訂單的消息使用者可能會確認訂單
true
何時成功處理,因此可以從數據庫中刪除訂單
消息編解碼器
如果您爲事件總線定義並註冊一個對象,則可以在事件總線上發送任何您喜歡的對象message codec
。
消息編解碼器有一個名稱,並DeliveryOptions
在發送或發佈消息時指定該名稱:
eventBus.registerCodec(myCodec);
DeliveryOptions options = new DeliveryOptions().setCodecName(myCodec.name());
eventBus.send("orders", new MyPOJO(), options);
如果您始終希望將相同的編碼解碼器用於特定類型,那麼您可以爲其註冊默認編解碼器,那麼您無需在傳送選項中指定每個發送的編解碼器:
eventBus.registerDefaultCodec(MyPOJO.class, myCodec);
eventBus.send("orders", new MyPOJO());
您註銷一個消息編解碼器unregisterCodec
。
消息編解碼器並不總是必須按照相同的類型進行編碼和解碼。例如,您可以編寫允許發送MyPOJO類的編解碼器,但是當該消息發送給處理程序時,它將作爲MyOtherPOJO類來到。
集羣事件總線
事件總線不僅僅存在於一個Vert.x實例中。通過在網絡上聚合不同的Vert.x實例,它們可以形成單一的分佈式事件總線。
以編程方式進行羣集
如果以編程方式創建Vert.x實例,則通過將Vert.x實例配置爲集羣來獲得集羣事件總線;
VertxOptions options = new VertxOptions();
Vertx.clusteredVertx(options, res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
EventBus eventBus = vertx.eventBus();
System.out.println("We now have a clustered event bus: " + eventBus);
} else {
System.out.println("Failed: " + res.cause());
}
});
您還應該確保ClusterManager
在類路徑中有一個實現,例如默認HazelcastClusterManager
。
在命令行上進行羣集
您可以使用命令行運行Vert.x集羣
vertx運行my-verticle.js -cluster
自動清理Verticle
如果您從Verticle內部註冊事件總線處理程序,那麼在卸載Verticle時,這些處理程序將自動取消註冊。
配置事件總線
事件總線可以配置。當事件總線聚集時它特別有用。事件總線使用TCP連接來發送和接收消息,因此EventBusOptions
可以配置這些TCP連接的所有方面。由於事件總線充當服務器和客戶端,因此配置接近NetClientOptions
並且NetServerOptions
。
VertxOptions options = new VertxOptions()
.setEventBusOptions(new EventBusOptions()
.setSsl(true)
.setKeyStoreOptions(new JksOptions().setPath("keystore.jks").setPassword("wibble"))
.setTrustStoreOptions(new JksOptions().setPath("keystore.jks").setPassword("wibble"))
.setClientAuth(ClientAuth.REQUIRED)
);
Vertx.clusteredVertx(options, res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
EventBus eventBus = vertx.eventBus();
System.out.println("We now have a clustered event bus: " + eventBus);
} else {
System.out.println("Failed: " + res.cause());
}
});
前面的代碼片段描述瞭如何使用事件總線的SSL連接,而不是普通的TCP連接。
警告:要在集羣模式下強制執行安全性,必須將集羣管理器配置爲使用加密或強制實施安全性。有關更多詳細信息,請參閱集羣管理器的文檔。
事件總線配置需要在所有羣集節點中保持一致。
該EventBusOptions
還允許您指定的事件總線是否被聚集,端口和主機,因爲你將與做setClustered
,getClusterHost
和getClusterPort
。
在容器中使用時,您還可以配置公共主機和端口:
VertxOptions options = new VertxOptions()
.setEventBusOptions(new EventBusOptions()
.setClusterPublicHost("whatever")
.setClusterPublicPort(1234)
);
Vertx.clusteredVertx(options, res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
EventBus eventBus = vertx.eventBus();
System.out.println("We now have a clustered event bus: " + eventBus);
} else {
System.out.println("Failed: " + res.cause());
}
});
JSON
與其他一些語言不同,Java沒有對JSON的一流支持,所以我們提供了兩個類來讓您在Vert.x應用程序中處理JSON變得更容易一些。
JSON對象
本JsonObject
類代表JSON對象。
JSON對象基本上只是一個具有字符串鍵的映射,而值可以是JSON支持的類型之一(字符串,數字,布爾值)。
JSON對象也支持空值。
創建JSON對象
可以使用默認構造函數創建空的JSON對象。
您可以按如下方式從字符串JSON表示中創建JSON對象:
String jsonString = "{\"foo\":\"bar\"}";
JsonObject object = new JsonObject(jsonString);
您可以按如下方式從地圖創建JSON對象:
Map<String, Object> map = new HashMap<>();
map.put("foo", "bar");
map.put("xyz", 3);
JsonObject object = new JsonObject(map);
將條目放入JSON對象中
使用這些put
方法將值放入JSON對象中。
由於流暢的API,方法調用可以被鏈接:
JsonObject object = new JsonObject();
object.put("foo", "bar").put("num", 123).put("mybool", true);
從JSON對象獲取值
您可以使用getXXX
方法從JSON對象中獲取值,例如:
String val = jsonObject.getString("some-key");
int intVal = jsonObject.getInteger("some-other-key");
JSON對象和Java對象之間的映射
您可以從Java對象的字段中創建JSON對象,如下所示:
您可以實例化一個Java對象並從JSON對象填充它的字段,如下所示:
request.bodyHandler(buff -> {
JsonObject jsonObject = buff.toJsonObject();
User javaObject = jsonObject.mapTo(User.class);
});
請注意,上述兩個映射方向都使用Jackson's ObjectMapper#convertValue()
來執行映射。有關字段和構造函數可見性影響的信息,有關對象引用的序列化和反序列化等的信息,請參閱Jackson文檔。
然而,在簡單的情況下,無論是mapFrom
和mapTo
應該會成功,如果Java類的所有字段都是公共的(或具有公共getter / setter方法),如果有一個公共的默認構造函數(或無定義的構造函數)。
只要對象圖是非循環的,引用的對象就會被順序地序列化/反序列化到嵌套的JSON對象。
將JSON對象編碼爲字符串
您用於encode
將對象編碼爲字符串形式。
JSON數組
所述JsonArray
類表示JSON陣列。
JSON數組是一系列值(字符串,數字,布爾值)。
JSON數組也可以包含空值。
創建JSON數組
空的JSON數組可以使用默認的構造函數創建。
您可以從字符串JSON表示中創建JSON數組,如下所示:
String jsonString = "[\"foo\",\"bar\"]";
JsonArray array = new JsonArray(jsonString);
將條目添加到JSON數組中
您可以使用這些add
方法將條目添加到JSON數組中。
JsonArray array = new JsonArray();
array.add("foo").add(123).add(false);
從JSON數組獲取值
您可以使用getXXX
方法從JSON數組中獲取值,例如:
String val = array.getString(0);
Integer intVal = array.getInteger(1);
Boolean boolVal = array.getBoolean(2);
將JSON數組編碼爲字符串
您用於encode
將數組編碼爲字符串形式。
緩衝區
大多數數據使用緩衝區在Vert.x內部進行混洗。
緩衝區是可以讀取或寫入的零個或多個字節的序列,並且根據需要自動擴展以適應寫入到它的任何字節。您也許可以將緩衝區視爲智能字節數組。
創建緩衝區
緩衝區可以使用其中一種靜態Buffer.buffer
方法創建。
緩衝區可以從字符串或字節數組初始化,或者可以創建空緩衝區。
以下是創建緩衝區的一些示例:
創建一個新的空緩衝區:
Buffer buff = Buffer.buffer();
從String創建一個緩衝區。該字符串將使用UTF-8編碼到緩衝區中。
Buffer buff = Buffer.buffer("some string");
從字符串創建緩衝區:字符串將使用指定的編碼進行編碼,例如:
Buffer buff = Buffer.buffer("some string", "UTF-16");
從一個byte []創建一個緩衝區
byte[] bytes = new byte[] {1, 3, 5};
Buffer buff = Buffer.buffer(bytes);
用初始大小提示創建一個緩衝區。如果你知道你的緩衝區將有一定數量的數據寫入它,你可以創建緩衝區並指定這個大小。這使得緩衝區最初分配的內存很多,並且比寫入數據時緩衝區自動調整多次的效率更高。
請注意,以這種方式創建的緩衝區爲空。它不會創建一個填充了零的緩衝區,直到指定的大小。
Buffer buff = Buffer.buffer(10000);
寫入緩衝區
有兩種寫入緩衝區的方式:追加和隨機訪問。無論哪種情況,緩衝區總是會自動擴展以包含字節。無法獲得IndexOutOfBoundsException
一個緩衝區。
追加到緩衝區
要追加到緩衝區,可以使用這些appendXXX
方法。追加方法用於追加各種不同的類型。
這些appendXXX
方法的返回值是緩衝區本身,所以這些可以鏈接在一起:
Buffer buff = Buffer.buffer();
buff.appendInt(123).appendString("hello\n");
socket.write(buff);
隨機訪問緩衝區寫入
您也可以使用這些setXXX
方法在特定索引處寫入緩衝區。設置方法存在於各種不同的數據類型中。所有設置方法都將索引作爲第一個參數 - 這表示緩衝區中開始寫入數據的位置。
緩衝區將根據需要隨時擴展以適應數據。
Buffer buff = Buffer.buffer();
buff.setInt(1000, 123);
buff.setString(0, "hello");
從緩衝區讀取
使用這些getXXX
方法從緩衝區中讀取數據。獲取各種數據類型的方法。這些方法的第一個參數是從哪裏獲取數據的緩衝區中的索引。
Buffer buff = Buffer.buffer();
for (int i = 0; i < buff.length(); i += 4) {
System.out.println("int value at " + i + " is " + buff.getInt(i));
}
使用無符號數字
無符號數可以讀取或附加/設置爲與一個緩衝器getUnsignedXXX
, appendUnsignedXXX
並setUnsignedXXX
方法。當爲實現最小化帶寬消耗而優化的網絡協議實現編解碼器時,這非常有用。
在以下示例中,值200僅在一個字節的指定位置處設置:
Buffer buff = Buffer.buffer(128);
int pos = 15;
buff.setUnsignedByte(pos, (short) 200);
System.out.println(buff.getUnsignedByte(pos));
控制檯顯示'200'。
緩衝區長度
使用length
得到的緩衝區的長度。緩衝區的長度是索引+ 1最大的緩衝區中字節的索引。
複製緩衝區
使用copy
使緩衝區的副本
切片緩衝區
分片緩衝區是一個新的緩衝區,它返回到原始緩衝區,即它不復制底層數據。使用slice
創建切片緩衝區
緩衝區重用
在將緩衝區寫入套接字或其他類似位置之後,它們不能被重新使用。
編寫TCP服務器和客戶端
Vert.x允許您輕鬆編寫非阻塞的TCP客戶端和服務器。
創建一個TCP服務器
使用所有默認選項創建TCP服務器的最簡單方法如下所示:
NetServer server = vertx.createNetServer();
配置TCP服務器
如果您不需要默認值,則可以通過在NetServerOptions
創建實例時傳入實例來配置服務器:
NetServerOptions options = new NetServerOptions().setPort(4321);
NetServer server = vertx.createNetServer(options);
啓動服務器偵聽
要告訴服務器偵聽傳入請求,請使用其中一種listen
替代方法。
要讓服務器按照以下選項中指定的方式監聽主機和端口:
NetServer server = vertx.createNetServer();
server.listen();
或者要指定要監聽的呼叫中的主機和端口,則忽略選項中配置的內容:
NetServer server = vertx.createNetServer();
server.listen(1234, "localhost");
默認主機是0.0.0.0
指“監聽所有可用地址”,默認端口是0
,這是一個特殊值,指示服務器找到一個隨機未使用的本地端口並使用它。
實際的綁定是異步的,所以服務器可能實際上並沒有收聽,直到調用listen 之後的一段時間返回。
如果您希望在服務器實際正在偵聽時收到通知,則可以爲listen
呼叫提供處理程序。例如:
NetServer server = vertx.createNetServer();
server.listen(1234, "localhost", res -> {
if (res.succeeded()) {
System.out.println("Server is now listening!");
} else {
System.out.println("Failed to bind!");
}
});
聽隨機端口
如果0
用作偵聽端口,則服務器將找到一個未使用的隨機端口進行偵聽。
要找出服務器正在監聽的真實端口,您可以調用actualPort
。
NetServer server = vertx.createNetServer();
server.listen(0, "localhost", res -> {
if (res.succeeded()) {
System.out.println("Server is now listening on actual port: " + server.actualPort());
} else {
System.out.println("Failed to bind!");
}
});
獲取傳入連接的通知
要在建立連接時收到通知,您需要設置connectHandler
:
NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
// Handle the connection in here
});
在建立連接時,處理程序將被調用一個實例NetSocket
。
這是一個與實際連接類似的套接字接口,可以讀寫數據以及執行其他各種操作,如關閉套接字。
從套接字讀取數據
從您在套接字上設置的套接字讀取數據handler
。
Buffer
每次在套接字上接收數據時,將使用該處理程序的實例來調用此處理程序。
NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
socket.handler(buffer -> {
System.out.println("I received some bytes: " + buffer.length());
});
});
將數據寫入套接字
您使用其中一個寫入套接字write
。
Buffer buffer = Buffer.buffer().appendFloat(12.34f).appendInt(123);
socket.write(buffer);
// Write a string in UTF-8 encoding
socket.write("some data");
// Write a string using the specified encoding
socket.write("some data", "UTF-16");
寫入操作是異步的,直到寫入調用返回後纔可能發生。
封閉處理程序
如果您希望在套接字關閉時收到通知,您可以closeHandler
在其上設置:
socket.closeHandler(v -> {
System.out.println("The socket has been closed");
});
處理異常
您可以設置exceptionHandler
爲接收套接字上發生的任何異常。
您可以設置exceptionHandler
爲接收連接傳遞給該連接之前發生的任何異常connectHandler
,例如在TLS握手期間。
事件總線寫處理程序
每個套接字都會在事件總線上自動註冊一個處理程序,並且在此處理程序中接收到任何緩衝區時,它會將它寫入自身。
這使您可以通過將緩衝區發送到該處理程序的地址,將數據寫入可能位於完全不同Verticle或甚至不同Vert.x實例中的套接字。
處理程序的地址由給定 writeHandlerID
從類路徑發送文件或資源
文件和類路徑資源可以直接使用寫入套接字sendFile
。這可以是發送文件的一種非常有效的方式,因爲它可以由操作系統支持的操作系統內核直接處理。
socket.sendFile("myfile.dat");
升級到SSL / TLS的連接
非SSL / TLS連接可以使用升級到SSL / TLS upgradeToSsl
。
服務器或客戶端必須配置爲SSL / TLS才能正常工作。 有關更多信息,請參閱SSL / TLS一章。
關閉TCP服務器
調用close
關閉服務器。關閉服務器將關閉所有打開的連接並釋放所有服務器資源。
關閉實際上是異步的,並且可能在呼叫返回後的一段時間才能完成。如果您想在實際關閉完成時收到通知,那麼您可以傳入處理程序。
這個處理程序將在關閉完成時被調用。
server.close(res -> {
if (res.succeeded()) {
System.out.println("Server is now closed");
} else {
System.out.println("close failed");
}
});
自動清理Verticle
如果您從Verticle內部創建TCP服務器和客戶端,那麼當解除Verticle時,這些服務器和客戶端將自動關閉。
擴展 - 共享TCP服務器
任何TCP服務器的處理程序總是在相同的事件循環線程上執行。
這意味着,如果您運行的核心數量很多,並且只部署了一個實例,那麼您的服務器上最多隻能使用一個核心。
爲了使用更多的服務器核心,您需要部署更多的服務器實例。
您可以在代碼中以編程方式實例化更多實例:
for (int i = 0; i < 10; i++) {
NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
socket.handler(buffer -> {
// Just echo back the data
socket.write(buffer);
});
});
server.listen(1234, "localhost");
}
或者,如果您正在使用Verticle,則可以通過使用-instances
命令行上的選項簡單地部署更多服務器Verticle實例:
vertx運行com.mycompany.MyVerticle -instances 10
或者以編程方式部署Verticle時
DeploymentOptions options = new DeploymentOptions().setInstances(10);
vertx.deployVerticle("com.mycompany.MyVerticle", options);
一旦你這樣做了,你會發現echo服務器在功能上與之前的工作方式相同,但是你服務器上的所有內核都可以使用,並且可以處理更多的工作。
此時,您可能會問自己'如何讓多臺服務器在相同的主機和端口上偵聽?當你嘗試並部署多個實例時,你一定會遇到端口衝突?“
Vert.x在這裏有一點魔力。*
當您在與現有服務器相同的主機和端口上部署另一臺服務器時,它實際上不會嘗試創建偵聽同一主機/端口的新服務器。
相反,它內部只維護一臺服務器,並且隨着傳入連接到達,它將以循環方式將它們分發給任何連接處理程序。
因此,Vert.x TCP服務器可以擴展可用內核,而每個實例仍然是單線程的。
創建一個TCP客戶端
使用所有默認選項創建TCP客戶端的最簡單方法如下:
NetClient client = vertx.createNetClient();
配置TCP客戶端
如果您不需要默認值,則可以通過在NetClientOptions
創建實例時傳入實例來配置客戶端:
NetClientOptions options = new NetClientOptions().setConnectTimeout(10000);
NetClient client = vertx.createNetClient(options);
建立聯繫
NetClientOptions options = new NetClientOptions().setConnectTimeout(10000);
NetClient client = vertx.createNetClient(options);
client.connect(4321, "localhost", res -> {
if (res.succeeded()) {
System.out.println("Connected!");
NetSocket socket = res.result();
} else {
System.out.println("Failed to connect: " + res.cause().getMessage());
}
});
配置連接嘗試
客戶端可以配置爲在無法連接的情況下自動重試連接到服務器。這是用setReconnectInterval
和 配置的setReconnectAttempts
。
注意 | 目前Vert.x在連接失敗時不會嘗試重新連接,重新連接嘗試和間隔僅適用於創建初始連接。 |
NetClientOptions options = new NetClientOptions().
setReconnectAttempts(10).
setReconnectInterval(500);
NetClient client = vertx.createNetClient(options);
默認情況下,多個連接嘗試被禁用。
記錄網絡活動
出於調試目的,可以記錄網絡活動:
NetServerOptions options = new NetServerOptions().setLogActivity(true);
NetServer server = vertx.createNetServer(options);
爲客戶
NetClientOptions options = new NetClientOptions().setLogActivity(true);
NetClient client = vertx.createNetClient(options);
網絡活動由Netty以DEBUG
級別和io.netty.handler.logging.LoggingHandler
名稱記錄。在使用網絡活動記錄時,需要記住一些事項:
日誌記錄不是由Vert.x記錄執行,而是由Netty執行
這不是一個生產功能
您應該閱讀Netty日誌記錄部分。
配置服務器和客戶端以使用SSL / TLS
TCP客戶端和服務器可以配置爲使用傳輸層安全性 - 較早版本的TLS被稱爲SSL。
在服務器和客戶端的API是相同的SSL / TLS是否被使用,並且它是通過配置的啓用NetClientOptions
或NetServerOptions
用於創建服務器或客戶端的情況。
指定服務器的密鑰/證書
SSL / TLS服務器通常會向客戶端提供證書,以便向客戶端驗證其身份。
可以通過多種方式爲服務器配置證書/密鑰:
第一種方法是指定包含證書和私鑰的Java密鑰庫的位置。
Java密鑰存儲可以 使用JDK附帶的keytool實用程序進行管理。
還應該提供密鑰存儲的密碼:
NetServerOptions options = new NetServerOptions().setSsl(true).setKeyStoreOptions(
new JksOptions().
setPath("/path/to/your/server-keystore.jks").
setPassword("password-of-your-keystore")
);
NetServer server = vertx.createNetServer(options);
或者,您可以自己將密鑰存儲區作爲緩衝區讀取並直接提供:
Buffer myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-keystore.jks");
JksOptions jksOptions = new JksOptions().
setValue(myKeyStoreAsABuffer).
setPassword("password-of-your-keystore");
NetServerOptions options = new NetServerOptions().
setSsl(true).
setKeyStoreOptions(jksOptions);
NetServer server = vertx.createNetServer(options);
PKCS#12格式的密鑰/證書(http://en.wikipedia.org/wiki/PKCS_12)通常使用.pfx
或.p12
擴展名,也可以以與JKS密鑰庫類似的方式加載:
NetServerOptions options = new NetServerOptions().setSsl(true).setPfxKeyCertOptions(
new PfxOptions().
setPath("/path/to/your/server-keystore.pfx").
setPassword("password-of-your-keystore")
);
NetServer server = vertx.createNetServer(options);
緩衝區配置也被支持:
Buffer myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-keystore.pfx");
PfxOptions pfxOptions = new PfxOptions().
setValue(myKeyStoreAsABuffer).
setPassword("password-of-your-keystore");
NetServerOptions options = new NetServerOptions().
setSsl(true).
setPfxKeyCertOptions(pfxOptions);
NetServer server = vertx.createNetServer(options);
另一種使用.pem
文件分別提供服務器私鑰和證書的方法。
NetServerOptions options = new NetServerOptions().setSsl(true).setPemKeyCertOptions(
new PemKeyCertOptions().
setKeyPath("/path/to/your/server-key.pem").
setCertPath("/path/to/your/server-cert.pem")
);
NetServer server = vertx.createNetServer(options);
緩衝區配置也被支持:
Buffer myKeyAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-key.pem");
Buffer myCertAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-cert.pem");
PemKeyCertOptions pemOptions = new PemKeyCertOptions().
setKeyValue(myKeyAsABuffer).
setCertValue(myCertAsABuffer);
NetServerOptions options = new NetServerOptions().
setSsl(true).
setPemKeyCertOptions(pemOptions);
NetServer server = vertx.createNetServer(options);
支持以PEM塊格式封裝的PKCS8,PKCS1和X.509證書。
警告 | 請記住,pem配置,私鑰不會被加密。 |
指定服務器的信任
SSL / TLS服務器可以使用證書頒發機構來驗證客戶端的身份。
可以通過多種方式爲服務器配置證書頒發機構:
Java信任庫可以 使用隨JDK 提供的keytool實用程序進行管理。
還應該提供信任商店的密碼:
NetServerOptions options = new NetServerOptions().
setSsl(true).
setClientAuth(ClientAuth.REQUIRED).
setTrustStoreOptions(
new JksOptions().
setPath("/path/to/your/truststore.jks").
setPassword("password-of-your-truststore")
);
NetServer server = vertx.createNetServer(options);
或者,您可以自己將信任存儲作爲緩衝區讀取並直接提供:
Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.jks");
NetServerOptions options = new NetServerOptions().
setSsl(true).
setClientAuth(ClientAuth.REQUIRED).
setTrustStoreOptions(
new JksOptions().
setValue(myTrustStoreAsABuffer).
setPassword("password-of-your-truststore")
);
NetServer server = vertx.createNetServer(options);
PKCS#12格式的證書頒發機構(http://en.wikipedia.org/wiki/PKCS_12)通常使用.pfx
或.p12
擴展名也可以以與JKS信任庫類似的方式加載:
NetServerOptions options = new NetServerOptions().
setSsl(true).
setClientAuth(ClientAuth.REQUIRED).
setPfxTrustOptions(
new PfxOptions().
setPath("/path/to/your/truststore.pfx").
setPassword("password-of-your-truststore")
);
NetServer server = vertx.createNetServer(options);
緩衝區配置也被支持:
Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.pfx");
NetServerOptions options = new NetServerOptions().
setSsl(true).
setClientAuth(ClientAuth.REQUIRED).
setPfxTrustOptions(
new PfxOptions().
setValue(myTrustStoreAsABuffer).
setPassword("password-of-your-truststore")
);
NetServer server = vertx.createNetServer(options);
使用列表.pem
文件提供服務器證書頒發機構的另一種方法。
NetServerOptions options = new NetServerOptions().
setSsl(true).
setClientAuth(ClientAuth.REQUIRED).
setPemTrustOptions(
new PemTrustOptions().
addCertPath("/path/to/your/server-ca.pem")
);
NetServer server = vertx.createNetServer(options);
緩衝區配置也被支持:
Buffer myCaAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-ca.pfx");
NetServerOptions options = new NetServerOptions().
setSsl(true).
setClientAuth(ClientAuth.REQUIRED).
setPemTrustOptions(
new PemTrustOptions().
addCertValue(myCaAsABuffer)
);
NetServer server = vertx.createNetServer(options);
在客戶端上啓用SSL / TLS
Net Clients can also be easily configured to use SSL. They have the exact same API when using SSL as when using standard sockets.
To enable SSL on a NetClient the function setSSL(true) is called.
Client trust configuration
If the trustALl
is set to true on the client, then the client will trust all server certificates. The connection will still be encrypted but this mode is vulnerable to 'man in the middle' attacks. I.e. you can’t be sure who you are connecting to. Use this with caution. Default value is false.
NetClientOptions options = new NetClientOptions().
setSsl(true).
setTrustAll(true);
NetClient client = vertx.createNetClient(options);
If trustAll
is not set then a client trust store must be configured and should contain the certificates of the servers that the client trusts.
默認情況下,客戶端禁用主機驗證。要啓用主機驗證,請設置要在客戶端上使用的算法(目前僅支持HTTPS和LDAPS):
NetClientOptions options = new NetClientOptions().
setSsl(true).
setHostnameVerificationAlgorithm("HTTPS");
NetClient client = vertx.createNetClient(options);
同樣的服務器配置,可以通過幾種方式配置客戶端信任:
第一種方法是指定包含證書頒發機構的Java信任存儲的位置。
它只是一個標準的Java密鑰存儲區,與服務器端的密鑰存儲區相同。客戶端信任存儲位置是通過使用該函數path
設置的 jks options
。如果服務器在連接期間提供證書不在客戶端信任庫中,則連接嘗試將不會成功。
NetClientOptions options = new NetClientOptions().
setSsl(true).
setTrustStoreOptions(
new JksOptions().
setPath("/path/to/your/truststore.jks").
setPassword("password-of-your-truststore")
);
NetClient client = vertx.createNetClient(options);
緩衝區配置也被支持:
Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.jks");
NetClientOptions options = new NetClientOptions().
setSsl(true).
setTrustStoreOptions(
new JksOptions().
setValue(myTrustStoreAsABuffer).
setPassword("password-of-your-truststore")
);
NetClient client = vertx.createNetClient(options);
PKCS#12格式的證書頒發機構(http://en.wikipedia.org/wiki/PKCS_12)通常使用.pfx
或.p12
擴展名也可以以與JKS信任庫類似的方式加載:
NetClientOptions options = new NetClientOptions().
setSsl(true).
setPfxTrustOptions(
new PfxOptions().
setPath("/path/to/your/truststore.pfx").
setPassword("password-of-your-truststore")
);
NetClient client = vertx.createNetClient(options);
緩衝區配置也被支持:
Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.pfx");
NetClientOptions options = new NetClientOptions().
setSsl(true).
setPfxTrustOptions(
new PfxOptions().
setValue(myTrustStoreAsABuffer).
setPassword("password-of-your-truststore")
);
NetClient client = vertx.createNetClient(options);
使用列表.pem
文件提供服務器證書頒發機構的另一種方法。
NetClientOptions options = new NetClientOptions().
setSsl(true).
setPemTrustOptions(
new PemTrustOptions().
addCertPath("/path/to/your/ca-cert.pem")
);
NetClient client = vertx.createNetClient(options);
緩衝區配置也被支持:
Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/ca-cert.pem");
NetClientOptions options = new NetClientOptions().
setSsl(true).
setPemTrustOptions(
new PemTrustOptions().
addCertValue(myTrustStoreAsABuffer)
);
NetClient client = vertx.createNetClient(options);
指定客戶端的密鑰/證書
如果服務器需要客戶端身份驗證,則客戶端連接時必須向服務器提供自己的證書。客戶端可以通過幾種方式進行配置:
第一種方法是指定包含密鑰和證書的Java密鑰庫的位置。這又是一個普通的Java密鑰存儲。客戶端密鑰庫的位置是通過使用功能設置 path
上 jks options
。
NetClientOptions options = new NetClientOptions().setSsl(true).setKeyStoreOptions(
new JksOptions().
setPath("/path/to/your/client-keystore.jks").
setPassword("password-of-your-keystore")
);
NetClient client = vertx.createNetClient(options);
緩衝區配置也被支持:
Buffer myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-keystore.jks");
JksOptions jksOptions = new JksOptions().
setValue(myKeyStoreAsABuffer).
setPassword("password-of-your-keystore");
NetClientOptions options = new NetClientOptions().
setSsl(true).
setKeyStoreOptions(jksOptions);
NetClient client = vertx.createNetClient(options);
PKCS#12格式的密鑰/證書(http://en.wikipedia.org/wiki/PKCS_12)通常使用.pfx
或.p12
擴展名,也可以以與JKS密鑰庫類似的方式加載:
NetClientOptions options = new NetClientOptions().setSsl(true).setPfxKeyCertOptions(
new PfxOptions().
setPath("/path/to/your/client-keystore.pfx").
setPassword("password-of-your-keystore")
);
NetClient client = vertx.createNetClient(options);
緩衝區配置也被支持:
Buffer myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-keystore.pfx");
PfxOptions pfxOptions = new PfxOptions().
setValue(myKeyStoreAsABuffer).
setPassword("password-of-your-keystore");
NetClientOptions options = new NetClientOptions().
setSsl(true).
setPfxKeyCertOptions(pfxOptions);
NetClient client = vertx.createNetClient(options);
另一種使用.pem
文件分別提供服務器私鑰和證書的方法。
NetClientOptions options = new NetClientOptions().setSsl(true).setPemKeyCertOptions(
new PemKeyCertOptions().
setKeyPath("/path/to/your/client-key.pem").
setCertPath("/path/to/your/client-cert.pem")
);
NetClient client = vertx.createNetClient(options);
緩衝區配置也被支持:
Buffer myKeyAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-key.pem");
Buffer myCertAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-cert.pem");
PemKeyCertOptions pemOptions = new PemKeyCertOptions().
setKeyValue(myKeyAsABuffer).
setCertValue(myCertAsABuffer);
NetClientOptions options = new NetClientOptions().
setSsl(true).
setPemKeyCertOptions(pemOptions);
NetClient client = vertx.createNetClient(options);
請記住,pem配置,私鑰不會被加密。
用於測試和開發目的的自簽名證書
警告 | 不要在生產設置中使用它,並且請注意生成的密鑰非常不安全。 |
通常情況下,需要自簽名證書,無論是單元/集成測試還是運行應用程序的開發版本。
SelfSignedCertificate
可用於提供自簽名的PEM證書助手並給予KeyCertOptions
和TrustOptions
配置:
SelfSignedCertificate certificate = SelfSignedCertificate.create();
NetServerOptions serverOptions = new NetServerOptions()
.setSsl(true)
.setKeyCertOptions(certificate.keyCertOptions())
.setTrustOptions(certificate.trustOptions());
NetServer server = vertx.createNetServer(serverOptions)
.connectHandler(socket -> socket.write("Hello!").end())
.listen(1234, "localhost");
NetClientOptions clientOptions = new NetClientOptions()
.setSsl(true)
.setKeyCertOptions(certificate.keyCertOptions())
.setTrustOptions(certificate.trustOptions());
NetClient client = vertx.createNetClient(clientOptions);
client.connect(1234, "localhost", ar -> {
if (ar.succeeded()) {
ar.result().handler(buffer -> System.out.println(buffer));
} else {
System.err.println("Woops: " + ar.cause().getMessage());
}
});
客戶端也可以配置爲信任所有證書:
NetClientOptions clientOptions = new NetClientOptions()
.setSsl(true)
.setTrustAll(true);
請注意,自簽名證書也適用於HTTPS等其他TCP協議:
SelfSignedCertificate certificate = SelfSignedCertificate.create();
vertx.createHttpServer(new HttpServerOptions()
.setSsl(true)
.setKeyCertOptions(certificate.keyCertOptions())
.setTrustOptions(certificate.trustOptions()))
.requestHandler(req -> req.response().end("Hello!"))
.listen(8080);
撤銷證書頒發機構
可以將信任配置爲使用證書吊銷列表(CRL)來吊銷不再可信的撤銷證書。該crlPath
配置使用CRL列表:
NetClientOptions options = new NetClientOptions().
setSsl(true).
setTrustStoreOptions(trustOptions).
addCrlPath("/path/to/your/crl.pem");
NetClient client = vertx.createNetClient(options);
緩衝區配置也被支持:
Buffer myCrlAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/crl.pem");
NetClientOptions options = new NetClientOptions().
setSsl(true).
setTrustStoreOptions(trustOptions).
addCrlValue(myCrlAsABuffer);
NetClient client = vertx.createNetClient(options);
配置密碼套件
默認情況下,TLS配置將使用運行Vert.x的JVM的Cipher套件。這個Cipher套件可以配置一套啓用的密碼:
NetServerOptions options = new NetServerOptions().
setSsl(true).
setKeyStoreOptions(keyStoreOptions).
addEnabledCipherSuite("ECDHE-RSA-AES128-GCM-SHA256").
addEnabledCipherSuite("ECDHE-ECDSA-AES128-GCM-SHA256").
addEnabledCipherSuite("ECDHE-RSA-AES256-GCM-SHA384").
addEnabledCipherSuite("CDHE-ECDSA-AES256-GCM-SHA384");
NetServer server = vertx.createNetServer(options);
密碼套件可以在指定NetServerOptions
或NetClientOptions
配置。
配置TLS協議版本
默認情況下,TLS配置將使用以下協議版本:SSLv2Hello,TLSv1,TLSv1.1和TLSv1.2。協議版本可以通過明確添加啓用的協議進行配置:
NetServerOptions options = new NetServerOptions().
setSsl(true).
setKeyStoreOptions(keyStoreOptions).
removeEnabledSecureTransportProtocol("TLSv1").
addEnabledSecureTransportProtocol("TLSv1.3");
NetServer server = vertx.createNetServer(options);
協議版本可以在NetServerOptions
或NetClientOptions
配置中指定。
SSL引擎
引擎實現可以配置爲使用OpenSSL而不是JDK實現。OpenSSL提供比JDK引擎更好的性能和CPU使用率,以及JDK版本獨立性。
要使用的引擎選項是
getSslEngineOptions
設置時的選項除此以外
JdkSSLEngineOptions
NetServerOptions options = new NetServerOptions().
setSsl(true).
setKeyStoreOptions(keyStoreOptions);
// Use JDK SSL engine explicitly
options = new NetServerOptions().
setSsl(true).
setKeyStoreOptions(keyStoreOptions).
setJdkSslEngineOptions(new JdkSSLEngineOptions());
// Use OpenSSL engine
options = new NetServerOptions().
setSsl(true).
setKeyStoreOptions(keyStoreOptions).
setOpenSslEngineOptions(new OpenSSLEngineOptions());
服務器名稱指示(SNI)
服務器名稱指示(SNI)是一種TLS擴展,客戶端通過該擴展指定嘗試連接的主機名:在TLS握手過程中,客戶端提供服務器名稱,服務器可以使用該名稱響應此服務器名稱的特定證書,而不是默認部署證書。如果服務器需要客戶端身份驗證,則服務器可以根據指定的服務器名稱使用特定的可信CA證書。
當SNI處於活動狀態時,服務器使用
證書CN或SAN DNS(使用DNS的主題備用名稱)進行完全匹配,例如
www.example.com
證書CN或SAN DNS證書匹配通配符名稱,例如
*.example.com
否則當客戶端不提供服務器名稱或提供的服務器名稱時,第一個證書不能匹配
當服務器另外需要客戶端認證時:
如果
JksOptions
用於設置信任選項(options
),則完成與信任存儲區別名的完全匹配否則可用的CA證書以與沒有SNI的情況相同的方式使用
您可以通過設置服務器上啓用SNI setSni
到true
並配置了多個密鑰/證書對服務器。
Java KeyStore文件或PKCS12文件可以存儲多個密鑰/證書對。
JksOptions keyCertOptions = new JksOptions().setPath("keystore.jks").setPassword("wibble");
NetServer netServer = vertx.createNetServer(new NetServerOptions()
.setKeyStoreOptions(keyCertOptions)
.setSsl(true)
.setSni(true)
);
PemKeyCertOptions
可以配置爲保存多個條目:
PemKeyCertOptions keyCertOptions = new PemKeyCertOptions()
.setKeyPaths(Arrays.asList("default-key.pem", "host1-key.pem", "etc..."))
.setCertPaths(Arrays.asList("default-cert.pem", "host2-key.pem", "etc...")
);
NetServer netServer = vertx.createNetServer(new NetServerOptions()
.setPemKeyCertOptions(keyCertOptions)
.setSsl(true)
.setSni(true)
);
客戶端隱式發送連接主機作爲完全限定域名(FQDN)的SNI服務器名稱。
連接套接字時,您可以提供顯式的服務器名稱
NetClient client = vertx.createNetClient(new NetClientOptions()
.setTrustStoreOptions(trustOptions)
.setSsl(true)
);
// Connect to 'localhost' and present 'server.name' server name
client.connect(1234, "localhost", "server.name", res -> {
if (res.succeeded()) {
System.out.println("Connected!");
NetSocket socket = res.result();
} else {
System.out.println("Failed to connect: " + res.cause().getMessage());
}
});
它可以用於不同的目的:
提供與服務器主機不同的服務器名稱
在連接到IP時顯示服務器名稱
強制在使用短名稱時顯示服務器名稱
應用層協議協商(ALPN)
應用層協議協商(ALPN)是用於應用層協議協商的TLS擴展。它由HTTP / 2使用:在TLS握手過程中,客戶端提供它接受的應用程序協議列表,服務器使用它支持的協議進行響應。
如果您使用的是Java 9,那麼您沒有問題,並且無需額外步驟即可使用HTTP / 2。
Java 8不支持開箱即用的ALPN,因此應該通過其他方式啓用ALPN:
OpenSSL支持
Jetty-ALPN支持
要使用的引擎選項是
getSslEngineOptions
設置時的選項JdkSSLEngineOptions
當ALPN可用於JDK時OpenSSLEngineOptions
當ALPN可用於OpenSSL時否則失敗
OpenSSL ALPN支持
OpenSSL提供本地ALPN支持。
OpenSSL需要在類路徑上配置setOpenSslEngineOptions
和使用netty-ticative jar。使用tcnative可能需要將OpenSSL安裝在您的操作系統上,具體取決於具體的實現。
Jetty-ALPN支持
Jetty-ALPN是一個小的jar,它覆蓋了幾個Java 8發行版以支持ALPN。
在JVM必須與啓動alpn啓動- $ {}版本的.jar在bootclasspath
:
-Xbootclasspath / p:/路徑/到/ alpn啓動$ {}版本的.jar
其中$ {}版本依賴於JVM版本,如8.1.7.v20160121爲OpenJDK的1.8.0u74。完整列表在Jetty-ALPN頁面上提供。
主要缺點是版本取決於JVM。
爲了解決這個問題,可以使用Jetty ALPN代理。代理是一個JVM代理,它將爲運行它的JVM選擇正確的ALPN版本:
-javaagent:/路徑/到/ alpn /劑
使用代理進行客戶端連接
所述NetClient
支持A HTTP / 1.x的CONNECT,SOCKS4A或SOCKS5代理。
可以NetClientOptions
通過設置ProxyOptions
包含代理類型,主機名,端口以及可選的用戶名和密碼的 對象來配置代理。
這是一個例子:
NetClientOptions options = new NetClientOptions()
.setProxyOptions(new ProxyOptions().setType(ProxyType.SOCKS5)
.setHost("localhost").setPort(1080)
.setUsername("username").setPassword("secret"));
NetClient client = vertx.createNetClient(options);
DNS解析總是在代理服務器上完成的,爲了實現SOCKS4客戶端的功能,有必要在本地解析DNS地址。
編寫HTTP服務器和客戶端
Vert.x允許您輕鬆編寫非阻塞HTTP客戶端和服務器。
Vert.x支持HTTP / 1.0,HTTP / 1.1和HTTP / 2協議。
HTTP的基本API對於HTTP / 1.x和HTTP / 2是相同的,特定的API功能可用於處理HTTP / 2協議。
創建一個HTTP服務器
使用所有默認選項創建HTTP服務器的最簡單方法如下所示:
HttpServer server = vertx.createHttpServer();
配置HTTP服務器
如果您不需要默認值,則可以通過在HttpServerOptions
創建實例時傳入實例來配置服務器:
HttpServerOptions options = new HttpServerOptions().setMaxWebsocketFrameSize(1000000);
HttpServer server = vertx.createHttpServer(options);
配置HTTP / 2服務器
Vert.x通過TLS h2
和TCP 支持HTTP / 2 h2c
。
要處理h2
請求,必須啓用TLS以及setUseAlpn
:
HttpServerOptions options = new HttpServerOptions()
.setUseAlpn(true)
.setSsl(true)
.setKeyStoreOptions(new JksOptions().setPath("/path/to/my/keystore"));
HttpServer server = vertx.createHttpServer(options);
ALPN是在客戶端和服務器開始交換數據之前協商協議的TLS擴展。
不支持ALPN的客戶端仍然可以進行經典的 SSL握手。
ALPN通常會h2
協議協議,儘管http/1.1
如果服務器或客戶決定可以使用。
要處理h2c
請求,必須禁用TLS,服務器將升級到HTTP / 2任何要升級到HTTP / 2的請求HTTP / 1.1。它也將接受h2c
從PRI * HTTP/2.0\r\nSM\r\n
序言開始的直接連接。
警告 | 大多數瀏覽器都不支持h2c ,所以對於服務的網站你應該使用h2 而不是h2c 。 |
當一個服務器接受一個HTTP / 2連接時,它將它發送給客戶端initial settings
。這些設置定義了客戶端如何使用連接,服務器的默認初始設置爲:
getMaxConcurrentStreams
:100
按照HTTP / 2 RFC的建議其他人的默認HTTP / 2設置值
注意 | Worker Verticles與HTTP / 2不兼容 |
記錄網絡服務器活動
出於調試目的,可以記錄網絡活動。
HttpServerOptions options = new HttpServerOptions().setLogActivity(true);
HttpServer server = vertx.createHttpServer(options);
有關詳細說明,請參閱記錄網絡活動一章。
啓動服務器偵聽
要告訴服務器偵聽傳入請求,請使用其中一種listen
替代方法。
要讓服務器按照以下選項中指定的方式監聽主機和端口:
HttpServer server = vertx.createHttpServer();
server.listen();
或者要指定要監聽的呼叫中的主機和端口,則忽略選項中配置的內容:
HttpServer server = vertx.createHttpServer();
server.listen(8080, "myhost.com");
默認主機是0.0.0.0
指“監聽所有可用地址”,默認端口是80
。
實際的綁定是異步的,所以服務器可能實際上並沒有收聽,直到調用listen的一段時間後才返回。
如果您希望在服務器實際正在偵聽時收到通知,則可以爲listen
呼叫提供處理程序。例如:
HttpServer server = vertx.createHttpServer();
server.listen(8080, "myhost.com", res -> {
if (res.succeeded()) {
System.out.println("Server is now listening!");
} else {
System.out.println("Failed to bind!");
}
});
獲得傳入請求的通知
要在收到請求時收到通知,您需要設置requestHandler
:
HttpServer server = vertx.createHttpServer();
server.requestHandler(request -> {
// Handle the request in here
});
處理請求
當請求到達時,請求處理程序被稱爲在一個實例中傳遞HttpServerRequest
。該對象表示服務器端HTTP請求。
處理程序在請求標題已被完全讀取時調用。
如果請求包含正文,那麼在請求處理程序被調用後的一段時間內,該正文將到達服務器。
每個服務器請求對象都與一個服務器響應對象相關聯。您用於 response
獲取HttpServerResponse
對象的引用。
以下是服務器處理請求並使用“hello world”回覆的一個簡單示例。
vertx.createHttpServer().requestHandler(request -> {
request.response().end("Hello world");
}).listen(8080);
請求版本
請求中指定的HTTP版本可以使用 version
請求方法
使用method
檢索請求的HTTP方法。(即無論是GET,POST,PUT,DELETE,HEAD,OPTIONS等)。
請求查詢
使用query
返回URI的查詢部分
例如,如果請求URI是:
A / B / C / page.html中?參數1 = ABC&param2的= XYZ
然後查詢將是
參數1 = ABC&param2的= XYZ
請求標頭
使用headers
返回HTTP請求頭。
這將返回一個實例MultiMap
- 這與常規的Map或Hash類似,但允許同一個鍵使用多個值 - 這是因爲HTTP允許使用相同鍵的多個頭值。
它也有不區分大小寫的鍵,這意味着您可以執行以下操作:
MultiMap headers = request.headers();
// Get the User-Agent:
System.out.println("User agent is " + headers.get("user-agent"));
// You can also do this and get the same result:
System.out.println("User agent is " + headers.get("User-Agent"));
請求參數
使用params
返回HTTP請求的參數。
請求參數在路徑後面的請求URI上發送。例如,如果URI是:
/page.html?param1=abc¶m2=xyz
然後參數將包含以下內容:
param1:'abc' param2:'xyz
請注意,這些請求參數是從請求的URL中檢索的。如果您的表單屬性是作爲提交multi-part/form-data
請求正文中提交的HTML表單的一部分發送的,那麼它們將不會出現在這裏的參數中。
遠程地址
請求的發件人的地址可以通過檢索remoteAddress
。
絕對URI
在HTTP請求中傳遞的URI通常是相對的。如果你想檢索與請求相對應的絕對URI,你可以使用它absoluteURI
結束處理程序
在endHandler
當整個請求,包括任何體已完全讀出的請求的被調用。
從請求正文讀取數據
HTTP請求通常包含我們想要讀取的主體。如前所述,請求處理程序僅在請求標題已到達時調用,因此請求對象在該點處沒有正文。
這是因爲身體可能非常大(例如文件上傳),並且我們通常不希望在將內容交給您之前在內存中緩衝整個內存,因爲這可能會導致服務器耗盡可用內存。
爲了接收主體,你可以使用handler
請求,每當請求主體到達時就會調用它。這是一個例子:
request.handler(buffer -> {
System.out.println("I have received a chunk of the body of length " + buffer.length());
});
傳遞給處理程序的對象是a Buffer
,並且可以在數據從網絡到達時根據正文的大小多次調用處理函數。
在某些情況下(例如,如果身體很小),您將需要將整個身體聚集在記憶中,因此您可以自己進行聚合,如下所示:
Buffer totalBuffer = Buffer.buffer();
request.handler(buffer -> {
System.out.println("I have received a chunk of the body of length " + buffer.length());
totalBuffer.appendBuffer(buffer);
});
request.endHandler(v -> {
System.out.println("Full body received, length = " + totalBuffer.length());
});
這是一個很常見的情況,Vert.x提供了一個bodyHandler
爲你做這個事情。身體處理程序在接收到全部身體時調用一次:
request.bodyHandler(totalBuffer -> {
System.out.println("Full body received, length = " + totalBuffer.length());
});
處理HTML表單
HTML表單可以使用內容類型application/x-www-form-urlencoded
或multipart/form-data
。
對於URL編碼的表單,表單屬性在URL中編碼,就像正常的查詢參數一樣。
對於多部分表單,它們被編碼在請求主體中,並且直到從線路讀取整個主體纔可用。
多部分表單還可以包含文件上傳。
如果您想要檢索多部分表單的屬性,您應該告訴Vert.x您希望在通過調用 true 讀取任何主體之前接收這樣的表單setExpectMultipart
,然後您應該使用formAttributes
一次整個機構已被閱讀:
server.requestHandler(request -> {
request.setExpectMultipart(true);
request.endHandler(v -> {
// The body has now been fully read, so retrieve the form attributes
MultiMap formAttributes = request.formAttributes();
});
});
處理表單文件上傳
Vert.x也可以處理在多部分請求體中編碼的文件上傳。
要接收文件上傳,您可以告訴Vert.x期待多部分表單並uploadHandler
根據請求進行設置 。
對於到達服務器的每個上傳,該處理程序將被調用一次。
傳遞給處理程序的對象是一個HttpServerFileUpload
實例。
server.requestHandler(request -> {
request.setExpectMultipart(true);
request.uploadHandler(upload -> {
System.out.println("Got a file upload " + upload.name());
});
});
文件上傳可能很大,我們不提供整個上傳到單個緩衝區,因爲這可能會導致內存耗盡,相反,上傳數據是以塊接收的:
request.uploadHandler(upload -> {
upload.handler(chunk -> {
System.out.println("Received a chunk of the upload of length " + chunk.length());
});
});
上傳對象是一個,ReadStream
因此您可以將請求主體抽到任何 WriteStream
實例。有關流和泵的章節,請參閱詳細說明。
如果您只是想將文件上傳到磁盤,您可以使用streamToFileSystem
:
request.uploadHandler(upload -> {
upload.streamToFileSystem("myuploads_directory/" + upload.filename());
});
警告 | 確保在生產系統中檢查文件名以避免惡意客戶端將文件上傳到文件系統的任意位置。請參閱安全說明以獲取更多信息 |
處理壓縮的主體
Vert.x可以處理由客戶端使用deflate或gzip 算法編碼的壓縮主體有效載荷。
setDecompressionSupported
在創建服務器時在選項上啓用解壓縮設置。
默認情況下解壓縮被禁用。
接收自定義的HTTP / 2幀
HTTP / 2是一個爲HTTP請求/響應模型提供各種幀的成幀協議。該協議允許發送和接收其他類型的幀。
要接收自定義幀,您可以使用該customFrameHandler
請求,每次自定義幀到達時都會調用該幀。這是一個例子:
request.customFrameHandler(frame -> {
System.out.println("Received a frame type=" + frame.type() +
" payload" + frame.payload().toString());
});
HTTP / 2幀不受流控制的限制 - 當接收到自定義幀時,幀處理程序將立即被調用,無論請求是暫停還是不
發回回覆
服務器響應對象是一個實例,HttpServerResponse
並從請求中獲得response
。
您使用響應對象將響應寫回到HTTP客戶端。
設置狀態碼和消息
響應的默認HTTP狀態碼是200
,表示OK
。
使用setStatusCode
設置不同的密碼。
您也可以使用指定自定義狀態消息setStatusMessage
。
如果您未指定狀態消息,則將使用與狀態代碼對應的默認值。
注意 | 對於HTTP / 2,由於協議不會將消息傳輸到客戶端,因此狀態將不會出現在響應中 |
編寫HTTP響應
要將數據寫入HTTP響應,請使用一個write
操作。
這些可以在響應結束前多次調用。它們可以通過幾種方式調用:
使用單個緩衝區:
HttpServerResponse response = request.response();
response.write(buffer);
用一個字符串。在這種情況下,字符串將使用UTF-8進行編碼,並將結果寫入導線。
HttpServerResponse response = request.response();
response.write("hello world!");
用一個字符串和一個編碼。在這種情況下,字符串將使用指定的編碼進行編碼,並將結果寫入導線。
HttpServerResponse response = request.response();
response.write("hello world!", "UTF-16");
寫入響應是異步的,並且在寫入隊列後總是立即返回。
如果您只是將單個字符串或緩衝區寫入HTTP響應,則可以編寫它並在一次調用中結束響應 end
第一個寫入結果的響應正在寫入響應中。因此,如果你沒有使用HTTP分塊,那麼你必須Content-Length
在寫入響應之前設置頭部,否則將會太遲。如果你使用HTTP分塊,你不必擔心。
結束HTTP響應
一旦你完成了HTTP響應,你應該end
這樣做。
這可以通過幾種方式完成:
沒有任何論據,答案只是結束。
HttpServerResponse response = request.response();
response.write("hello world!");
response.end();
它也可以用調用字符串或緩衝區的方式write
調用。在這種情況下,它就像使用字符串或緩衝區調用write一樣,然後調用沒有參數的end。例如:
HttpServerResponse response = request.response();
response.end("hello world!");
關閉底層連接
您可以使用關閉底層TCP連接close
。
響應結束時,Vert.x會自動關閉非保持連接。
保持活動連接默認情況下不會由Vert.x自動關閉。如果您希望在閒置時間後關閉保持連接的連接,則可以進行配置setIdleTimeout
。
HTTP / 2連接GOAWAY
在關閉響應之前發送一個幀。
設置響應標題
HTTP響應頭可以通過直接添加到響應中添加到響應中 headers
:
HttpServerResponse response = request.response();
MultiMap headers = response.headers();
headers.set("content-type", "text/html");
headers.set("other-header", "wibble");
或者你可以使用 putHeader
HttpServerResponse response = request.response();
response.putHeader("content-type", "text/html").putHeader("other-header", "wibble");
必須在響應主體的任何部分寫入之前添加標題。
分塊的HTTP響應和預告片
Vert.x支持HTTP分塊傳輸編碼。
這允許HTTP響應主體以塊形式寫入,並且通常在將大型響應主體流式傳輸到客戶端時使用,並且總大小事先未知。
您將HTTP響應置於分塊模式,如下所示:
HttpServerResponse response = request.response();
response.setChunked(true);
默認是不分塊的。處於分塊模式時,每次調用其中一個write
方法都會導致寫入新的HTTP塊。
在分塊模式下,您還可以將HTTP響應預告片寫入響應。這些實際上寫在響應的最後一部分。
注意 | 分塊響應對HTTP / 2流沒有影響 |
要將預告片添加到回覆中,請將其直接添加到回覆中trailers
。
HttpServerResponse response = request.response();
response.setChunked(true);
MultiMap trailers = response.trailers();
trailers.set("X-wibble", "woobble").set("X-quux", "flooble");
或者使用putTrailer
。
HttpServerResponse response = request.response();
response.setChunked(true);
response.putTrailer("X-wibble", "woobble").putTrailer("X-quux", "flooble");
直接從磁盤或類路徑提供文件
如果您正在編寫Web服務器,則從磁盤提供文件的一種方式是將其作爲一個文件打開並將其AsyncFile
泵入HTTP響應。
或者你可以加載它一次使用readFile
,並直接寫入響應。
或者,Vert.x提供了一種方法,允許您在一次操作中將文件從磁盤或文件系統提供給HTTP響應。在底層操作系統支持的情況下,這可能會導致操作系統直接將文件中的字節傳輸到套接字,而不會通過用戶空間進行復制。
這是通過使用完成的sendFile
,並且通常對於大文件更高效,但對於小文件可能會更慢。
這是一個非常簡單的Web服務器,它使用sendFile從文件系統提供文件:
vertx.createHttpServer().requestHandler(request -> {
String file = "";
if (request.path().equals("/")) {
file = "index.html";
} else if (!request.path().contains("..")) {
file = request.path();
}
request.response().sendFile("web/" + file);
}).listen(8080);
發送文件是異步的,並且可能在呼叫返回後的一段時間才能完成。如果您希望在寫入文件時收到通知,您可以使用sendFile
注意 | 如果您sendFile 在使用HTTPS時使用它,它將通過用戶空間複製,因爲如果內核直接將數據從磁盤複製到套接字,它不會給我們應用任何加密的機會。 |
警告 | 如果要使用Vert.x直接編寫Web服務器,請注意用戶無法利用該路徑訪問要從其提供服務的目錄或類路徑之外的文件。使用Vert.x Web可能更安全。 |
當只需要提供文件的一部分時,比如從一個給定的字節開始,可以通過以下操作來實現:
vertx.createHttpServer().requestHandler(request -> {
long offset = 0;
try {
offset = Long.parseLong(request.getParam("start"));
} catch (NumberFormatException e) {
// error handling...
}
long end = Long.MAX_VALUE;
try {
end = Long.parseLong(request.getParam("end"));
} catch (NumberFormatException e) {
// error handling...
}
request.response().sendFile("web/mybigfile.txt", offset, end);
}).listen(8080);
如果要從偏移量開始直到結束時發送文件,則不需要提供該長度,在這種情況下,您可以執行以下操作:
vertx.createHttpServer().requestHandler(request -> {
long offset = 0;
try {
offset = Long.parseLong(request.getParam("start"));
} catch (NumberFormatException e) {
// error handling...
}
request.response().sendFile("web/mybigfile.txt", offset);
}).listen(8080);
反饋意見
服務器響應是一個WriteStream
實例,所以你可以從任何抽到它 ReadStream
,例如AsyncFile
,NetSocket
, WebSocket
或HttpServerRequest
。
下面是一個響應任何PUT方法的請求主體的回聲示例。它爲身體使用一個泵,所以即使HTTP請求體在任何時候都比內存大得多,也可以工作:
vertx.createHttpServer().requestHandler(request -> {
HttpServerResponse response = request.response();
if (request.method() == HttpMethod.PUT) {
response.setChunked(true);
Pump.pump(request, response).start();
request.endHandler(v -> response.end());
} else {
response.setStatusCode(400).end();
}
}).listen(8080);
編寫HTTP / 2幀
HTTP / 2是一個爲HTTP請求/響應模型提供各種幀的成幀協議。該協議允許發送和接收其他類型的幀。
要發送這樣的幀,你可以使用writeCustomFrame
響應。這是一個例子:
int frameType = 40;
int frameStatus = 10;
Buffer payload = Buffer.buffer("some data");
// Sending a frame to the client
response.writeCustomFrame(frameType, frameStatus, payload);
這些幀是立即發送的,並且不受流量控制的限制 - 當發送這種幀時,可以在其他DATA
幀之前完成。
流重置
HTTP / 1.x不允許乾淨重置請求或響應流,例如,當客戶端上載服務器上已存在的資源時,服務器需要接受整個響應。
HTTP / 2在請求/響應過程中隨時支持流重置:
request.response().reset();
默認情況下,NO_ERROR
發送(0)錯誤代碼,可以發送另一個代碼:
request.response().reset(8);
HTTP / 2規範定義了可以使用的錯誤代碼列表。
使用request handler
and 來通知請求處理程序的流重置事件response handler
:
request.response().exceptionHandler(err -> {
if (err instanceof StreamResetException) {
StreamResetException reset = (StreamResetException) err;
System.out.println("Stream reset " + reset.getCode());
}
});
服務器推送
服務器推送是HTTP / 2的一項新功能,可以爲單個客戶端請求並行發送多個響應。
當服務器處理請求時,它可以將請求/響應推送給客戶端:
HttpServerResponse response = request.response();
// Push main.js to the client
response.push(HttpMethod.GET, "/main.js", ar -> {
if (ar.succeeded()) {
// The server is ready to push the response
HttpServerResponse pushedResponse = ar.result();
// Send main.js response
pushedResponse.
putHeader("content-type", "application/json").
end("alert(\"Push response hello\")");
} else {
System.out.println("Could not push client resource " + ar.cause());
}
});
// Send the requested resource
response.sendFile("<html><head><script src=\"/main.js\"></script></head><body></body></html>");
當服務器準備推送響應時,將調用推送響應處理程序並且處理程序可以發送響應。
推送響應處理程序可能會收到失敗,例如,客戶端可能會取消推送,因爲它已經存在main.js
於其緩存中,並且不再需要它。
該push
方法必須在啓動響應結束之前調用,但可以在之後寫入推送的響應。
處理異常
您可以設置exceptionHandler
爲接收在連接傳遞到requestHandler
或之前發生的任何異常websocketHandler
,例如在TLS握手期間。
HTTP壓縮
Vert.x支持開箱即用的HTTP壓縮。
這意味着您可以在回覆給客戶端之前自動壓縮響應的主體。
如果客戶端不支持HTTP壓縮,則將響應發回而不壓縮主體。
這允許處理支持HTTP壓縮的客戶端以及那些不支持HTTP壓縮的客戶端。
要啓用壓縮功能,可以使用它進行配置setCompressionSupported
。
默認情況下壓縮未啓用。
當啓用HTTP壓縮時,服務器將檢查客戶端是否包含Accept-Encoding
包含支持的壓縮的標題。常用的有deflate和gzip。兩者都受Vert.x支持。
如果找到這樣的頭部,服務器將自動壓縮其中一個受支持的壓縮的響應主體並將其發送回客戶端。
只要響應需要在不壓縮的情況下發送,您可以將標題設置content-encoding
爲identity
:
request.response()
.putHeader(HttpHeaders.CONTENT_ENCODING, HttpHeaders.IDENTITY)
.sendFile("/path/to/image.jpg");
請注意,壓縮可能能夠減少網絡流量,但CPU密集度更高。
爲了解決後一個問題,Vert.x允許您調整原生gzip / deflate壓縮算法的“壓縮級別”參數。
壓縮級別允許根據結果數據的壓縮比和壓縮/解壓縮操作的計算成本來配置gizp / deflate算法。
壓縮級別是從'1'到'9'的整數值,其中'1'表示較低的壓縮比但是最快的算法,而'9'意味着最大的壓縮比可用,但是較慢的算法。
使用高於1-2的壓縮級別通常可以節省大小上的一些字節 - 增益不是線性的,並且取決於要壓縮的特定數據 - 但是,對於所需的CPU週期而言,它的成本是非可逆的服務器,同時生成壓縮的響應數據(請注意,在Vert.x不支持壓縮響應數據的任何形式緩存(即使對於靜態文件,因此壓縮在每個請求身體生成時即時完成)以及與解碼(膨脹)接收到的響應時影響客戶端的方式相同,隨着級別的增加,變得更加CPU密集型的操作。
默認情況下 - 如果通過啓用壓縮setCompressionSupported
- Vert.x將使用'6'作爲壓縮級別,但可以將參數配置爲解決任何情況setCompressionLevel
。
創建一個HTTP客戶端
您HttpClient
使用默認選項創建一個實例,如下所示:
HttpClient client = vertx.createHttpClient();
如果要爲客戶端配置選項,請按如下所示創建它:
HttpClientOptions options = new HttpClientOptions().setKeepAlive(false);
HttpClient client = vertx.createHttpClient(options);
Vert.x通過TLS h2
和TCP 支持HTTP / 2 h2c
。
默認情況下,http客戶端執行HTTP / 1.1請求,執行setProtocolVersion
必須設置的HTTP / 2請求HTTP_2
。
對於h2
請求,TLS必須啓用應用層協議協商:
HttpClientOptions options = new HttpClientOptions().
setProtocolVersion(HttpVersion.HTTP_2).
setSsl(true).
setUseAlpn(true).
setTrustAll(true);
HttpClient client = vertx.createHttpClient(options);
對於h2c
請求,必須禁用TLS,客戶端將執行HTTP / 1.1請求並嘗試升級到HTTP / 2:
HttpClientOptions options = new HttpClientOptions().setProtocolVersion(HttpVersion.HTTP_2);
HttpClient client = vertx.createHttpClient(options);
h2c
連接也可以直接建立,即連接從事先知道開始,當 setHttp2ClearTextUpgrade
選項設置爲false時:連接建立後,客戶端將發送HTTP / 2連接前言,並期望從服務器接收相同的前言。
http服務器可能不支持HTTP / 2,可以version
在響應到達時檢查實際版本。
當客戶端連接到HTTP / 2服務器時,它會將其發送到服務器initial settings
。這些設置定義了服務器如何使用連接,客戶端的默認初始設置是HTTP / 2 RFC定義的默認值。
記錄網絡客戶端活動
出於調試目的,可以記錄網絡活動。
HttpClientOptions options = new HttpClientOptions().setLogActivity(true);
HttpClient client = vertx.createHttpClient(options);
有關詳細說明,請參閱記錄網絡活動一章。
發出請求
http客戶端非常靈活,您可以通過多種方式提出請求。
通常你想用http客戶端向同一個主機/端口發出很多請求。爲了避免每次發出請求時重複主機/端口,您可以使用默認主機/端口配置客戶端:
HttpClientOptions options = new HttpClientOptions().setDefaultHost("wibble.com");
// Can also set default port if you want...
HttpClient client = vertx.createHttpClient(options);
client.getNow("/some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
});
或者,如果您發現自己使用同一客戶端向不同主機/端口發出大量請求,則可以在執行請求時簡單指定主機/端口。
HttpClient client = vertx.createHttpClient();
// Specify both port and host name
client.getNow(8080, "myserver.mycompany.com", "/some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
});
// This time use the default port 80 but specify the host name
client.getNow("foo.othercompany.com", "/other-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
});
指定主機/端口的兩種方法都支持與客戶端進行請求的所有不同方式。
沒有請求主體的簡單請求
通常情況下,您會希望在沒有請求主體的情況下發出HTTP請求。HTTP GET,OPTIONS和HEAD請求通常是這種情況。
使用Vert.x http客戶端執行此操作的最簡單方法是使用前綴爲的方法Now
。例如 getNow
。
這些方法創建http請求並通過單個方法調用發送它,並允許您提供一個處理程序,它在返回時將使用http響應調用。
HttpClient client = vertx.createHttpClient();
// Send a GET request
client.getNow("/some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
});
// Send a GET request
client.headNow("/other-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
});
編寫一般請求
在其他時候,您不知道要在運行時發送的請求方法。對於這種用例,我們提供了通用的請求方法,例如request
,允許您在運行時指定HTTP方法:
HttpClient client = vertx.createHttpClient();
client.request(HttpMethod.GET, "some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
}).end();
client.request(HttpMethod.POST, "foo-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
}).end("some-data");
編寫請求組織
有時候你會想要寫有請求的主體,或者你想在發送請求之前先寫請求頭。
這些方法不會立即發送請求,而是返回HttpClientRequest
可用於寫入請求正文或寫入標頭的實例。
以下是一些使用body編寫POST請求的示例:
HttpClient client = vertx.createHttpClient();
HttpClientRequest request = client.post("some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
});
// Now do stuff with the request
request.putHeader("content-length", "1000");
request.putHeader("content-type", "text/plain");
request.write(body);
// Make sure the request is ended when you're done with it
request.end();
// Or fluently:
client.post("some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
}).putHeader("content-length", "1000").putHeader("content-type", "text/plain").write(body).end();
// Or event more simply:
client.post("some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
}).putHeader("content-type", "text/plain").end(body);
存在以UTF-8編碼和任何特定編碼編寫字符串並寫入緩衝區的方法:
request.write("some data");
// Write string encoded in specific encoding
request.write("some other data", "UTF-16");
// Write a buffer
Buffer buffer = Buffer.buffer();
buffer.appendInt(123).appendLong(245l);
request.write(buffer);
如果您只是將單個字符串或緩衝區寫入HTTP請求,則可以將其寫入並通過一次調用結束請求end
。
request.end("some simple data");
// Write buffer and end the request (send it) in a single call
Buffer buffer = Buffer.buffer().appendDouble(12.34d).appendLong(432l);
request.end(buffer);
在寫入請求時,第一次調用write
將導致請求頭被寫出到接線中。
實際寫入是異步的,並且可能在調用返回後的某段時間纔會發生。
帶有請求主體的非分塊HTTP請求需要提供一個Content-Length
頭。
因此,如果您沒有使用分塊的HTTP,那麼Content-Length
在寫入請求之前必須先設置標題,否則將會太遲。
如果您正在調用其中一個end
接受字符串或緩衝區的方法,Vert.x將Content-Length
在寫入請求主體之前自動計算並設置標題。
如果您使用HTTP分塊,Content-Length
則不需要標頭,因此您不必預先計算大小。
編寫請求標頭
您可以使用headers
多圖將報頭寫入請求,如下所示:
MultiMap headers = request.headers();
headers.set("content-type", "application/json").set("other-header", "foo");
標題是一個實例,MultiMap
它提供了添加,設置和刪除條目的操作。Http標頭允許特定鍵的多個值。
您也可以使用書寫標題 putHeader
request.putHeader("content-type", "application/json").putHeader("other-header", "foo");
如果您希望將頭部寫入請求,則必須在寫入請求主體的任何部分之前完成此操作。
非標準的HTTP方法
的OTHER
HTTP方法用於非標準方法中,當使用該方法時,setRawMethod
必須使用設置原始方法發送到服務器。
結束HTTP請求
完成HTTP請求後,您必須使用其中一個end
操作結束該請求。
結束請求會導致寫入任何頭信息(如果它們尚未寫入並且請求被標記爲完成)。
請求可以以幾種方式結束。沒有參數,請求就結束了:
request.end();
或者可以在呼叫中提供字符串或緩衝區end
。這就像write
在調用end
沒有參數之前調用字符串或緩衝區
request.end("some-data");
// End it with a buffer
Buffer buffer = Buffer.buffer().appendFloat(12.3f).appendInt(321);
request.end(buffer);
分塊的HTTP請求
Vert.x支持請求的HTTP分塊傳輸編碼。
這允許HTTP請求主體以塊的形式寫入,並且通常在大型請求主體流式傳輸到服務器時使用,而服務器的大小事先不知道。
您可以使用HTTP請求進入分塊模式setChunked
。
在分塊模式下,每次寫入調用都會導致一個新的塊被寫入線路。在分塊模式下,不需要預先設置Content-Length
請求。
request.setChunked(true);
// Write some chunks
for (int i = 0; i < 10; i++) {
request.write("this-is-chunk-" + i);
}
request.end();
處理異常
您可以通過在HttpClientRequest
實例上設置一個異常處理程序來處理與請求相對應的異常 :
HttpClientRequest request = client.post("some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
});
request.exceptionHandler(e -> {
System.out.println("Received exception: " + e.getMessage());
e.printStackTrace();
});
這不處理需要在代碼中處理的非2xx響應 HttpClientResponse
:
HttpClientRequest request = client.post("some-uri", response -> {
if (response.statusCode() == 200) {
System.out.println("Everything fine");
return;
}
if (response.statusCode() == 500) {
System.out.println("Unexpected behavior on the server side");
return;
}
});
request.end();
重要 | XXXNow 方法不能接收異常處理程序。 |
在客戶端請求上指定一個處理程序
而不是在創建客戶端請求對象的調用中提供響應處理程序,或者,您不能在創建請求時提供處理程序,並稍後使用請求對象本身設置它 handler
,例如:
HttpClientRequest request = client.post("some-uri");
request.handler(response -> {
System.out.println("Received response with status code " + response.statusCode());
});
將請求用作流
這個HttpClientRequest
實例也是一個WriteStream
意思,你可以從任何ReadStream
實例中抽取它。
例如,您可以將磁盤上的文件泵送到http請求主體,如下所示:
request.setChunked(true);
Pump pump = Pump.pump(file, request);
file.endHandler(v -> request.end());
pump.start();
編寫HTTP / 2幀
HTTP / 2是一個爲HTTP請求/響應模型提供各種幀的成幀協議。該協議允許發送和接收其他類型的幀。
要發送這樣的幀,你可以使用write
請求。這是一個例子:
int frameType = 40;
int frameStatus = 10;
Buffer payload = Buffer.buffer("some data");
// Sending a frame to the server
request.writeCustomFrame(frameType, frameStatus, payload);
流重置
HTTP / 1.x不允許乾淨重置請求或響應流,例如,當客戶端上載服務器上已存在的資源時,服務器需要接受整個響應。
HTTP / 2在請求/響應過程中隨時支持流重置:
request.reset();
默認情況下,發送NO_ERROR(0)錯誤代碼,但可以發送另一個代碼:
request.reset(8);
HTTP / 2規範定義了可以使用的錯誤代碼列表。
使用request handler
and 來通知請求處理程序的流重置事件response handler
:
request.exceptionHandler(err -> {
if (err instanceof StreamResetException) {
StreamResetException reset = (StreamResetException) err;
System.out.println("Stream reset " + reset.getCode());
}
});
處理HTTP響應
您接收HttpClientResponse
到您在請求方法中指定的處理程序的實例,或者直接在該HttpClientRequest
對象上設置處理程序。
您可以查詢的狀態代碼,並與響應的狀態消息statusCode
和statusMessage
。
client.getNow("some-uri", response -> {
// the status code - e.g. 200 or 404
System.out.println("Status code is " + response.statusCode());
// the status message e.g. "OK" or "Not Found".
System.out.println("Status message is " + response.statusMessage());
});
將響應用作流
這個HttpClientResponse
實例也是一個ReadStream
這意味着你可以將它泵入任何WriteStream
實例。
響應標題和預告片
Http響應可以包含標題。使用headers
得到的頭。
返回的對象是一個MultiMap
HTTP標頭可以包含單個鍵的多個值。
String contentType = response.headers().get("content-type");
String contentLength = response.headers().get("content-lengh");
分塊的HTTP響應也可以包含預告片 - 這些都是在響應主體的最後一個塊中發送的。
閱讀請求正文
響應處理程序在從線路讀取響應頭時調用。
如果響應有一個主體,那麼在標題被讀取一段時間後,這個主體可能會分成幾部分。在調用響應處理程序之前,我們不會等待所有主體到達,因爲響應可能非常大,而且我們可能會等待很長時間,或者內存不足以應對大量響應。
client.getNow("some-uri", response -> {
response.handler(buffer -> {
System.out.println("Received a part of the response body: " + buffer);
});
});
如果您知道響應主體不是很大,並且想在處理它之前將其全部聚合到內存中,則可以自行聚合:
client.getNow("some-uri", response -> {
// Create an empty buffer
Buffer totalBuffer = Buffer.buffer();
response.handler(buffer -> {
System.out.println("Received a part of the response body: " + buffer.length());
totalBuffer.appendBuffer(buffer);
});
response.endHandler(v -> {
// Now all the body has been read
System.out.println("Total response body length is " + totalBuffer.length());
});
});
或者,您可以使用bodyHandler
完整閱讀回覆時爲整個機構調用的便利:
client.getNow("some-uri", response -> {
response.bodyHandler(totalBuffer -> {
// Now all the body has been read
System.out.println("Total response body length is " + totalBuffer.length());
});
});
響應結束句柄
響應endHandler
在整個響應體被讀取後立即調用,或者在讀取頭之後立即調用,如果沒有主體,則調用響應處理程序。
30x重定向處理
客戶端可以被配置爲遵循HTTP重定向:當客戶端收到一 301
,302
,303
或307
狀態代碼,它遵循由提供重定向Location
響應報頭和響應處理器被傳遞重定向響應,而不是原來的響應。
這是一個例子:
client.get("some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
}).setFollowRedirects(true).end();
重定向策略如下
上
301
,302
或303
狀態代碼,請與重定向GET
方法在
307
狀態碼上,使用相同的HTTP方法和緩存體重定向
警告 | 以下重定向緩存請求主體 |
最大重定向是16
默認的,可以隨着改變setMaxRedirects
。
HttpClient client = vertx.createHttpClient(
new HttpClientOptions()
.setMaxRedirects(32));
client.get("some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
}).setFollowRedirects(true).end();
一種尺寸不適合所有情況,默認的重定向策略可能不適合您的需求。
默認重定向策略可以通過自定義實現進行更改:
client.redirectHandler(response -> {
// Only follow 301 code
if (response.statusCode() == 301 && response.getHeader("Location") != null) {
// Compute the redirect URI
String absoluteURI = resolveURI(response.request().absoluteURI(), response.getHeader("Location"));
// Create a new ready to use request that the client will use
return Future.succeededFuture(client.getAbs(absoluteURI));
}
// We don't redirect
return null;
});
該政策處理原始HttpClientResponse
收到並返回null
或者一個Future<HttpClientRequest>
。
當
null
返回時,原始響應被處理當未來返回時,請求將在成功完成後發送
當將來返回時,請求中設置的異常處理程序在其失敗時被調用
返回的請求必須是未發送的,以便可以發送原始請求處理程序並且客戶端可以發送它。
大多數原始請求設置將被傳播到新請求:
請求標題,除非你設置了一些標題(包括
setHost
)請求主體,除非返回的請求使用
GET
方法響應處理器
請求異常處理器
請求超時
100 - 繼續處理
根據HTTP 1.1規範,客戶端可以Expect: 100-Continue
在發送請求主體的其餘部分之前設置標頭併發送請求標頭。
然後,服務器可以以臨時響應狀態Status: 100 (Continue)
進行響應,以向客戶端表明發送身體的其餘部分是可以的。
這裏的想法是它允許服務器在發送大量數據之前授權並接受/拒絕請求。如果請求可能不被接受,則發送大量數據會浪費帶寬,並且會在服務器讀取將丟棄的數據時將其綁定。
Vert.x允許您continueHandler
在客戶端請求對象上設置一個
如果服務器發送Status: 100 (Continue)
迴應以表示可以發送請求的其餘部分,則會調用此命令。
這與` sendHead`一起使用來發送請求的頭部。
這是一個例子:
HttpClientRequest request = client.put("some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
});
request.putHeader("Expect", "100-Continue");
request.continueHandler(v -> {
// OK to send rest of body
request.write("Some data");
request.write("Some more data");
request.end();
});
在服務器端,Vert.x http服務器可以配置爲在收到Expect: 100-Continue
標題時自動發回100個“繼續”臨時響應。
這是通過設置選項完成的setHandle100ContinueAutomatically
。
如果您想要決定是否手動發回繼續響應,那麼應該將此屬性設置爲 false
(默認值),然後您可以檢查標題並調用writeContinue
以讓客戶端繼續發送主體:
httpServer.requestHandler(request -> {
if (request.getHeader("Expect").equalsIgnoreCase("100-Continue")) {
// Send a 100 continue response
request.response().writeContinue();
// The client should send the body when it receives the 100 response
request.bodyHandler(body -> {
// Do something with body
});
request.endHandler(v -> {
request.response().end();
});
}
});
您也可以通過直接發送失敗狀態代碼來拒絕請求:在這種情況下,主體應該被忽略或應該關閉連接(100-繼續是性能提示並且不能是邏輯協議約束):
httpServer.requestHandler(request -> {
if (request.getHeader("Expect").equalsIgnoreCase("100-Continue")) {
//
boolean rejectAndClose = true;
if (rejectAndClose) {
// Reject with a failure code and close the connection
// this is probably best with persistent connection
request.response()
.setStatusCode(405)
.putHeader("Connection", "close")
.end();
} else {
// Reject with a failure code and ignore the body
// this may be appropriate if the body is small
request.response()
.setStatusCode(405)
.end();
}
}
});
客戶端推送
服務器推送是HTTP / 2的一項新功能,可以爲單個客戶端請求並行發送多個響應。
推送處理程序可以設置請求以接收服務器推送的請求/響應:
HttpClientRequest request = client.get("/index.html", response -> {
// Process index.html response
});
// Set a push handler to be aware of any resource pushed by the server
request.pushHandler(pushedRequest -> {
// A resource is pushed for this request
System.out.println("Server pushed " + pushedRequest.path());
// Set an handler for the response
pushedRequest.handler(pushedResponse -> {
System.out.println("The response for the pushed request");
});
});
// End the request
request.end();
如果客戶端不想接收推送的請求,則可以重置流:
request.pushHandler(pushedRequest -> {
if (pushedRequest.path().equals("/main.js")) {
pushedRequest.reset();
} else {
// Handle it
}
});
當沒有設置處理程序時,任何推送的流將被流重置(8
錯誤代碼)的客戶端自動取消。
接收自定義的HTTP / 2幀
HTTP / 2是一個爲HTTP請求/響應模型提供各種幀的成幀協議。該協議允許發送和接收其他類型的幀。
要接收自定義幀,您可以在請求上使用customFrameHandler,每次自定義幀到達時都會調用該幀。這是一個例子:
response.customFrameHandler(frame -> {
System.out.println("Received a frame type=" + frame.type() +
" payload" + frame.payload().toString());
});
在客戶端上啓用壓縮
The http client comes with support for HTTP Compression out of the box.
This means the client can let the remote http server know that it supports compression, and will be able to handle compressed response bodies.
An http server is free to either compress with one of the supported compression algorithms or to send the body back without compressing it at all. So this is only a hint for the Http server which it may ignore at will.
To tell the http server which compression is supported by the client it will include an Accept-Encoding
header with the supported compression algorithm as value. Multiple compression algorithms are supported. In case of Vert.x this will result in the following header added:
Accept-Encoding: gzip, deflate
The server will choose then from one of these. You can detect if a server ompressed the body by checking for the Content-Encoding
header in the response sent back from it.
If the body of the response was compressed via gzip it will include for example the following header:
Content-Encoding: gzip
To enable compression set setTryUseCompression
on the options used when creating the client.
By default compression is disabled.
HTTP/1.x pooling and keep alive
Http keep alive allows http connections to be used for more than one request. This can be a more efficient use of connections when you’re making multiple requests to the same server.
For HTTP/1.x versions, the http client supports pooling of connections, allowing you to reuse connections between requests.
For pooling to work, keep alive must be true using setKeepAlive
on the options used when configuring the client. The default value is true.
When keep alive is enabled. Vert.x will add a Connection: Keep-Alive
header to each HTTP/1.0 request sent. When keep alive is disabled. Vert.x will add a Connection: Close
header to each HTTP/1.1 request sent to signal that the connection will be closed after completion of the response.
The maximum number of connections to pool for each server is configured using setMaxPoolSize
When making a request with pooling enabled, Vert.x will create a new connection if there are less than the maximum number of connections already created for that server, otherwise it will add the request to a queue.
Keep alive connections will not be closed by the client automatically. To close them you can close the client instance.
Alternatively you can set idle timeout using setIdleTimeout
- any connections not used within this timeout will be closed. Please note the idle timeout value is in seconds not milliseconds.
HTTP/1.1 pipe-lining
The client also supports pipe-lining of requests on a connection.
Pipe-lining means another request is sent on the same connection before the response from the preceding one has returned. Pipe-lining is not appropriate for all requests.
To enable pipe-lining, it must be enabled using setPipelining
. By default pipe-lining is disabled.
When pipe-lining is enabled requests will be written to connections without waiting for previous responses to return.
通過單個連接的管道請求數量受限於setPipeliningLimit
。該選項定義發送到等待響應的服務器的最大HTTP請求數。此限制可確保通過連接到同一服務器的客戶端請求分配的公平性。
HTTP / 2複用
HTTP / 2提倡使用單個連接到服務器,默認情況下,http客戶端爲每個服務器使用一個連接,所有到同一服務器的流在同一個連接上覆用。
當客戶需要使用多於一個連接並使用池時,setHttp2MaxPoolSize
應該使用。
當需要限制每個連接的多路複用流的數量並使用連接池而不是單個連接時,setHttp2MultiplexingLimit
可以使用。
HttpClientOptions clientOptions = new HttpClientOptions().
setHttp2MultiplexingLimit(10).
setHttp2MaxPoolSize(3);
// Uses up to 3 connections and up to 10 streams per connection
HttpClient client = vertx.createHttpClient(clientOptions);
連接的複用限制是在客戶端上設置的一個設置,用於限制單個連接的流數量。如果服務器設置下限,則有效值可能會更低SETTINGS_MAX_CONCURRENT_STREAMS
。
HTTP / 2連接不會被客戶端自動關閉。要關閉它們,您可以調用close
或關閉客戶端實例。
或者,您可以使用以下方式設置空閒超時:setIdleTimeout
- 在此超時內未使用的任何連接將被關閉。請注意,空閒超時值以秒爲單位而不是毫秒。
HTTP連接
該HttpConnection
用於處理HTTP連接事件,生命週期和設置提供了API。
HTTP / 2完全實現了HttpConnection
API。
HTTP / 1.x部分實現了HttpConnection
API:只實現了close操作,close處理程序和異常處理程序。該協議不提供其他操作的語義。
服務器連接
該connection
方法返回服務器上的請求連接:
HttpConnection connection = request.connection();
可以在服務器上設置連接處理程序以通知任何傳入連接:
HttpServer server = vertx.createHttpServer(http2Options);
server.connectionHandler(connection -> {
System.out.println("A client connected");
});
客戶端連接
該connection
方法返回客戶端上的請求連接:
HttpConnection connection = request.connection();
連接處理程序可以在連接發生時在請求中設置通知:
request.connectionHandler(connection -> {
System.out.println("Connected to the server");
});
連接設置
Http2Settings
數據對象配置HTTP / 2的配置。
每個端點必須遵守連接另一端發送的設置。
建立連接時,客戶端和服務器交換初始設置。初始設置由setInitialSettings
客戶端和setInitialSettings
服務器進行配置。
連接建立後,可以隨時更改設置:
connection.updateSettings(new Http2Settings().setMaxConcurrentStreams(100));
由於遠程方應在接收設置更新時進行確認,因此可以給予回覆以通知確認:
connection.updateSettings(new Http2Settings().setMaxConcurrentStreams(100), ar -> {
if (ar.succeeded()) {
System.out.println("The settings update has been acknowledged ");
}
});
相反,remoteSettingsHandler
收到新的遠程設置時會通知您:
connection.remoteSettingsHandler(settings -> {
System.out.println("Received new settings");
});
注意 | 這僅適用於HTTP / 2協議 |
連接ping
HTTP / 2連接ping可用於確定連接往返時間或檢查連接有效性:ping
向PING
遠程端點發送幀:
Buffer data = Buffer.buffer();
for (byte i = 0;i < 8;i++) {
data.appendByte(i);
}
connection.ping(data, pong -> {
System.out.println("Remote side replied");
});
Vert.x will send automatically an acknowledgement when a PING
frame is received, an handler can be set to be notified for each ping received:
connection.pingHandler(ping -> {
System.out.println("Got pinged by remote side");
});
The handler is just notified, the acknowledgement is sent whatsoever. Such feature is aimed for implementing protocols on top of HTTP/2.
NOTE | this only applies to the HTTP/2 protocol |
Connection shutdown and go away
Calling shutdown
will send a GOAWAY
frame to the remote side of the connection, asking it to stop creating streams: a client will stop doing new requests and a server will stop pushing responses. After the GOAWAY
frame is sent, the connection waits some time (30 seconds by default) until all current streams closed and close the connection:
connection.shutdown();
The shutdownHandler
notifies when all streams have been closed, the connection is not yet closed.
可以發送一個GOAWAY
幀,與關閉的主要區別在於,它只會告訴連接的遠程端停止創建新的流而無需調度連接關閉:
connection.goAway(0);
相反,收到時也可以通知GOAWAY
:
connection.goAwayHandler(goAway -> {
System.out.println("Received a go away frame");
});
shutdownHandler
當所有當前流都關閉並且連接可以關閉時,將被調用:
connection.goAway(0);
connection.shutdownHandler(v -> {
// All streams are closed, close the connection
connection.close();
});
這也適用於GOAWAY
收到。
注意 | 這僅適用於HTTP / 2協議 |
HttpClient用法
HttpClient可以用於Verticle或嵌入。
在Verticle中使用時,Verticle 應該使用自己的客戶端實例。
更一般地說,客戶端不應該在不同的Vert.x上下文之間共享,因爲它可能導致意外的行爲。
例如,保持連接將在打開連接的請求的上下文中調用客戶端處理程序,隨後的請求將使用相同的上下文。
發生這種情況時,Vert.x檢測到並記錄警告:
重用與不同上下文的連接:HttpClient可能在不同的Verticles之間共享
HttpClient可以像單元測試或普通java一樣嵌入到非Vert.x線程中main
:客戶端處理程序將由不同的Vert.x線程和上下文調用,此類上下文根據需要創建。對於生產這種用法不建議。
服務器共享
當多個HTTP服務器偵聽同一端口時,vert.x使用循環策略編排請求處理。
我們來創建一個HTTP服務器,例如:
vertx.createHttpServer().requestHandler(request -> {
request.response().end("Hello from server " + this);
}).listen(8080);
This service is listening on the port 8080. So, when this verticle is instantiated multiple times as with:vertx run io.vertx.examples.http.sharing.HttpServerVerticle -instances 2
, what’s happening ? If both verticles would bind to the same port, you would receive a socket exception. Fortunately, vert.x is handling this case for you. When you deploy another server on the same host and port as an existing server it doesn’t actually try and create a new server listening on the same host/port. It binds only once to the socket. When receiving a request it calls the server handlers following a round robin strategy.
Let’s now imagine a client such as:
vertx.setPeriodic(100, (l) -> {
vertx.createHttpClient().getNow(8080, "localhost", "/", resp -> {
resp.bodyHandler(body -> {
System.out.println(body.toString("ISO-8859-1"));
});
});
});
Vert.x delegates the requests to one of the server sequentially:
Hello from i.v.e.h.s.HttpServerVerticle@1
Hello from i.v.e.h.s.HttpServerVerticle@2
Hello from i.v.e.h.s.HttpServerVerticle@1
Hello from i.v.e.h.s.HttpServerVerticle@2
...
Consequently the servers can scale over available cores while each Vert.x verticle instance remains strictly single threaded, and you don’t have to do any special tricks like writing load-balancers in order to scale your server on your multi-core machine.
Using HTTPS with Vert.x
Vert.x http servers and clients can be configured to use HTTPS in exactly the same way as net servers.
Please see configuring net servers to use SSL for more information.
SSL can also be enabled/disabled per request with RequestOptions
or when specifying a scheme with requestAbs
method.
client.getNow(new RequestOptions()
.setHost("localhost")
.setPort(8080)
.setURI("/")
.setSsl(true), response -> {
System.out.println("Received response with status code " + response.statusCode());
});
The setSsl
setting acts as the default client setting.
The setSsl
overrides the default client setting
setting the value to
false
will disable SSL/TLS even if the client is configured to use SSL/TLSsetting the value to
true
will enable SSL/TLS even if the client is configured to not use SSL/TLS, the actual client SSL/TLS (such as trust, key/certificate, ciphers, ALPN, …) will be reused
Likewise requestAbs
scheme also overrides the default client setting.
Server Name Indication (SNI)
Vert.x http servers can be configured to use SNI in exactly the same way as net.adoc.
Vert.x http client will present the actual hostname as server name during the TLS handshake.
WebSockets
WebSockets are a web technology that allows a full duplex socket-like connection between HTTP servers and HTTP clients (typically browsers).
Vert.x supports WebSockets on both the client and server-side.
WebSockets on the server
There are two ways of handling WebSockets on the server side.
WebSocket handler
The first way involves providing a websocketHandler
on the server instance.
When a WebSocket connection is made to the server, the handler will be called, passing in an instance ofServerWebSocket
.
server.websocketHandler(websocket -> {
System.out.println("Connected!");
});
You can choose to reject the WebSocket by calling reject
.
server.websocketHandler(websocket -> {
if (websocket.path().equals("/myapi")) {
websocket.reject();
} else {
// Do something
}
});
Upgrading to WebSocket
The second way of handling WebSockets is to handle the HTTP Upgrade request that was sent from the client, and call upgrade
on the server request.
server.requestHandler(request -> {
if (request.path().equals("/myapi")) {
ServerWebSocket websocket = request.upgrade();
// Do something
} else {
// Reject
request.response().setStatusCode(400).end();
}
});
The server WebSocket
The ServerWebSocket
instance enables you to retrieve the headers
, path
, query
and URI
of the HTTP request of the WebSocket handshake.
WebSockets on the client
The Vert.x HttpClient
supports WebSockets.
You can connect a WebSocket to a server using one of the websocket
operations and providing a handler.
The handler will be called with an instance of WebSocket
when the connection has been made:
client.websocket("/some-uri", websocket -> {
System.out.println("Connected!");
});
Writing messages to WebSockets
If you wish to write a single WebSocket message to the WebSocket you can do this withwriteBinaryMessage
or writeTextMessage
:
Buffer buffer = Buffer.buffer().appendInt(123).appendFloat(1.23f);
websocket.writeBinaryMessage(buffer);
// Write a simple text message
String message = "hello";
websocket.writeTextMessage(message);
If the WebSocket message is larger than the maximum websocket frame size as configured withsetMaxWebsocketFrameSize
then Vert.x will split it into multiple WebSocket frames before sending it on the wire.
Writing frames to WebSockets
A WebSocket message can be composed of multiple frames. In this case the first frame is either a binaryor text frame followed by zero or more continuation frames.
The last frame in the message is marked as final.
To send a message consisting of multiple frames you create frames using WebSocketFrame.binaryFrame
, WebSocketFrame.textFrame
or WebSocketFrame.continuationFrame
and write them to the WebSocket using writeFrame
.
Here’s an example for binary frames:
WebSocketFrame frame1 = WebSocketFrame.binaryFrame(buffer1, false);
websocket.writeFrame(frame1);
WebSocketFrame frame2 = WebSocketFrame.continuationFrame(buffer2, false);
websocket.writeFrame(frame2);
// Write the final frame
WebSocketFrame frame3 = WebSocketFrame.continuationFrame(buffer2, true);
websocket.writeFrame(frame3);
In many cases you just want to send a websocket message that consists of a single final frame, so we provide a couple of shortcut methods to do that with writeFinalBinaryFrame
and writeFinalTextFrame
.
Here’s an example:
websocket.writeFinalTextFrame("Geronimo!");
// Send a websocket messages consisting of a single final binary frame:
Buffer buff = Buffer.buffer().appendInt(12).appendString("foo");
websocket.writeFinalBinaryFrame(buff);
Reading frames from WebSockets
To read frames from a WebSocket you use the frameHandler
.
The frame handler will be called with instances of WebSocketFrame
when a frame arrives, for example:
websocket.frameHandler(frame -> {
System.out.println("Received a frame of size!");
});
Closing WebSockets
Use close
to close the WebSocket connection when you have finished with it.
Streaming WebSockets
The WebSocket
instance is also a ReadStream
and a WriteStream
so it can be used with pumps.
When using a WebSocket as a write stream or a read stream it can only be used with WebSockets connections that are used with binary frames that are no split over multiple frames.
Using a proxy for HTTP/HTTPS connections
The http client supports accessing http/https URLs via a HTTP proxy (e.g. Squid) or SOCKS4a or SOCKS5proxy. The CONNECT protocol uses HTTP/1.x but can connect to HTTP/1.x and HTTP/2 servers.
Connecting to h2c (unencrypted HTTP/2 servers) is likely not supported by http proxies since they will support HTTP/1.1 only.
The proxy can be configured in the HttpClientOptions
by setting a ProxyOptions
object containing proxy type, hostname, port and optionally username and password.
Here’s an example of using an HTTP proxy:
HttpClientOptions options = new HttpClientOptions()
.setProxyOptions(new ProxyOptions().setType(ProxyType.HTTP)
.setHost("localhost").setPort(3128)
.setUsername("username").setPassword("secret"));
HttpClient client = vertx.createHttpClient(options);
When the client connects to an http URL, it connects to the proxy server and provides the full URL in the HTTP request ("GET http://www.somehost.com/path/file.html HTTP/1.1").
When the client connects to an https URL, it asks the proxy to create a tunnel to the remote host with the CONNECT method.
For a SOCKS5 proxy:
HttpClientOptions options = new HttpClientOptions()
.setProxyOptions(new ProxyOptions().setType(ProxyType.SOCKS5)
.setHost("localhost").setPort(1080)
.setUsername("username").setPassword("secret"));
HttpClient client = vertx.createHttpClient(options);
The DNS resolution is always done on the proxy server, to achieve the functionality of a SOCKS4 client, it is necessary to resolve the DNS address locally.
Handling of other protocols
The HTTP proxy implementation supports getting ftp:// urls if the proxy supports that, which isn’t available in non-proxy getAbs requests.
HttpClientOptions options = new HttpClientOptions()
.setProxyOptions(new ProxyOptions().setType(ProxyType.HTTP));
HttpClient client = vertx.createHttpClient(options);
client.getAbs("ftp://ftp.gnu.org/gnu/", response -> {
System.out.println("Received response with status code " + response.statusCode());
});
Support for other protocols is not available since java.net.URL does not support them (gopher:// for example).
Automatic clean-up in verticles
If you’re creating http servers and clients from inside verticles, those servers and clients will be automatically closed when the verticle is undeployed.
Using Shared Data with Vert.x
Shared data contains functionality that allows you to safely share data between different parts of your application, or different applications in the same Vert.x instance or across a cluster of Vert.x instances.
Shared data provides:
synchronous shared maps (local)
asynchronous maps (local or cluster-wide)
asynchronous locks (local or cluster-wide)
asynchronous counters (local or cluster-wide)
IMPORTANT | The behavior of the distributed data structure depends on the cluster manager you use. Backup (replication) and behavior when a network partition is faced are defined by the cluster manager and its configuration. Refer to the cluster manager documentation as well as to the underlying framework manual. |
Local shared maps
Local shared maps
allow you to share data safely between different event loops (e.g. different verticles) in the same Vert.x instance.
Local shared maps only allow certain data types to be used as keys and values. Those types must either be immutable, or certain other types that can be copied like Buffer
. In the latter case the key/value will be copied before putting it in the map.
This way we can ensure there is no shared access to mutable state between different threads in your Vert.x application so you don’t have to worry about protecting that state by synchronising access to it.
Here’s an example of using a shared local map:
SharedData sd = vertx.sharedData();
LocalMap<String, String> map1 = sd.getLocalMap("mymap1");
map1.put("foo", "bar"); // Strings are immutable so no need to copy
LocalMap<String, Buffer> map2 = sd.getLocalMap("mymap2");
map2.put("eek", Buffer.buffer().appendInt(123)); // This buffer will be copied before adding to map
// Then... in another part of your application:
map1 = sd.getLocalMap("mymap1");
String val = map1.get("foo");
map2 = sd.getLocalMap("mymap2");
Buffer buff = map2.get("eek");
Asynchronous shared maps
Asynchronous shared maps allow data to be put in the map and retrieved locally when Vert.x is not clustered. When clustered, data can be put from any node and retrieved from the same node or any other node.
IMPORTANT | In clustered mode, asynchronous shared maps rely on distributed data structures provided by the cluster manager. Beware that the latency relative to asynchronous shared map operations can be much higher in clustered than in local mode. |
This makes them really useful for things like storing session state in a farm of servers hosting a Vert.x web application.
You get an instance of AsyncMap
with getAsyncMap
.
Getting the map is asynchronous and the result is returned to you in the handler that you specify. Here’s an example:
SharedData sd = vertx.sharedData();
sd.<String, String>getAsyncMap("mymap", res -> {
if (res.succeeded()) {
AsyncMap<String, String> map = res.result();
} else {
// Something went wrong!
}
});
Putting data in a map
You put data in a map with put
.
The actual put is asynchronous and the handler is notified once it is complete:
map.put("foo", "bar", resPut -> {
if (resPut.succeeded()) {
// Successfully put the value
} else {
// Something went wrong!
}
});
Getting data from a map
You get data from a map with get
.
The actual get is asynchronous and the handler is notified with the result some time later
map.get("foo", resGet -> {
if (resGet.succeeded()) {
// Successfully got the value
Object val = resGet.result();
} else {
// Something went wrong!
}
});
Other map operations
You can also remove entries from an asynchronous map, clear them and get the size.
See the API docs
for more information.
Asynchronous locks
Asynchronous locks
allow you to obtain exclusive locks locally or across the cluster - this is useful when you want to do something or access a resource on only one node of a cluster at any one time.
Asynchronous locks have an asynchronous API unlike most lock APIs which block the calling thread until the lock is obtained.
To obtain a lock use getLock
.
This won’t block, but when the lock is available, the handler will be called with an instance of Lock
, signifying that you now own the lock.
While you own the lock no other caller, anywhere on the cluster will be able to obtain the lock.
When you’ve finished with the lock, you call release
to release it, so another caller can obtain it.
sd.getLock("mylock", res -> {
if (res.succeeded()) {
// Got the lock!
Lock lock = res.result();
// 5 seconds later we release the lock so someone else can get it
vertx.setTimer(5000, tid -> lock.release());
} else {
// Something went wrong
}
});
You can also get a lock with a timeout. If it fails to obtain the lock within the timeout the handler will be called with a failure:
sd.getLockWithTimeout("mylock", 10000, res -> {
if (res.succeeded()) {
// Got the lock!
Lock lock = res.result();
} else {
// Failed to get lock
}
});
異步計數器
在本地或跨應用程序的不同節點維護原子計數器通常很有用。
你可以這樣做Counter
。
您通過以下方式獲得實例getCounter
:
sd.getCounter("mycounter", res -> {
if (res.succeeded()) {
Counter counter = res.result();
} else {
// Something went wrong!
}
});
一旦你有一個實例,你可以檢索當前計數,以原子方式遞增它,使用各種方法遞減和增加一個值。
查看API docs
更多信息。
在Vert.x中使用文件系統
Vert.x FileSystem
對象提供了許多操作文件系統的操作。
每個Vert.x實例有一個文件系統對象,您可以通過它獲取它 fileSystem
。
提供了每個操作的阻塞和非阻塞版本。非阻塞版本帶有一個在操作完成或發生錯誤時調用的處理程序。
以下是一個文件異步拷貝的例子:
FileSystem fs = vertx.fileSystem();
// Copy file from foo.txt to bar.txt
fs.copy("foo.txt", "bar.txt", res -> {
if (res.succeeded()) {
// Copied ok!
} else {
// Something went wrong
}
});
阻塞版本被命名xxxBlocking
並返回結果或直接拋出異常。在很多情況下,根據操作系統和文件系統,某些潛在的阻止操作可能會很快返回,這就是我們提供這些操作的原因,但強烈建議您在使用它們之前測試它們在特定應用程序中返回的時間長度從事件循環,以免違反黃金法則。
以下是使用阻止API的副本:
FileSystem fs = vertx.fileSystem();
// Copy file from foo.txt to bar.txt synchronously
fs.copyBlocking("foo.txt", "bar.txt");
許多操作存在複製,移動,截斷,chmod和許多其他文件操作。我們不會在這裏API docs
全部列出,請查閱完整列表。
我們來看幾個使用異步方法的例子:
Vertx vertx = Vertx.vertx();
// Read a file
vertx.fileSystem().readFile("target/classes/readme.txt", result -> {
if (result.succeeded()) {
System.out.println(result.result());
} else {
System.err.println("Oh oh ..." + result.cause());
}
});
// Copy a file
vertx.fileSystem().copy("target/classes/readme.txt", "target/classes/readme2.txt", result -> {
if (result.succeeded()) {
System.out.println("File copied");
} else {
System.err.println("Oh oh ..." + result.cause());
}
});
// Write a file
vertx.fileSystem().writeFile("target/classes/hello.txt", Buffer.buffer("Hello"), result -> {
if (result.succeeded()) {
System.out.println("File written");
} else {
System.err.println("Oh oh ..." + result.cause());
}
});
// Check existence and delete
vertx.fileSystem().exists("target/classes/junk.txt", result -> {
if (result.succeeded() && result.result()) {
vertx.fileSystem().delete("target/classes/junk.txt", r -> {
System.out.println("File deleted");
});
} else {
System.err.println("Oh oh ... - cannot delete the file: " + result.cause());
}
});
異步文件
Vert.x提供了一個異步文件抽象,允許您操作文件系統上的文件。
You open an AsyncFile
as follows:
OpenOptions options = new OpenOptions();
fileSystem.open("myfile.txt", options, res -> {
if (res.succeeded()) {
AsyncFile file = res.result();
} else {
// Something went wrong!
}
});
AsyncFile
implements ReadStream
and WriteStream
so you can pump files to and from other stream objects such as net sockets, http requests and responses, and WebSockets.
They also allow you to read and write directly to them.
Random access writes
To use an AsyncFile
for random access writing you use the write
method.
The parameters to the method are:
buffer
: the buffer to write.position
: an integer position in the file where to write the buffer. If the position is greater or equal to the size of the file, the file will be enlarged to accommodate the offset.handler
: the result handler
Here is an example of random access writes:
Vertx vertx = Vertx.vertx();
vertx.fileSystem().open("target/classes/hello.txt", new OpenOptions(), result -> {
if (result.succeeded()) {
AsyncFile file = result.result();
Buffer buff = Buffer.buffer("foo");
for (int i = 0; i < 5; i++) {
file.write(buff, buff.length() * i, ar -> {
if (ar.succeeded()) {
System.out.println("Written ok!");
// etc
} else {
System.err.println("Failed to write: " + ar.cause());
}
});
}
} else {
System.err.println("Cannot open file " + result.cause());
}
});
Random access reads
To use an AsyncFile
for random access reads you use the read
method.
The parameters to the method are:
buffer
: the buffer into which the data will be read.offset
: an integer offset into the buffer where the read data will be placed.position
: the position in the file where to read data from.length
: the number of bytes of data to readhandler
: the result handler
Here’s an example of random access reads:
Vertx vertx = Vertx.vertx();
vertx.fileSystem().open("target/classes/les_miserables.txt", new OpenOptions(), result -> {
if (result.succeeded()) {
AsyncFile file = result.result();
Buffer buff = Buffer.buffer(1000);
for (int i = 0; i < 10; i++) {
file.read(buff, i * 100, i * 100, 100, ar -> {
if (ar.succeeded()) {
System.out.println("Read ok!");
} else {
System.err.println("Failed to write: " + ar.cause());
}
});
}
} else {
System.err.println("Cannot open file " + result.cause());
}
});
Opening Options
When opening an AsyncFile
, you pass an OpenOptions
instance. These options describe the behavior of the file access. For instance, you can configure the file permissions with the setRead
, setWrite
and setPerms
methods.
You can also configure the behavior if the open file already exists with setCreateNew
andsetTruncateExisting
.
You can also mark the file to be deleted on close or when the JVM is shutdown with setDeleteOnClose
.
Flushing data to underlying storage.
In the OpenOptions
, you can enable/disable the automatic synchronisation of the content on every write using setDsync
. In that case, you can manually flush any writes from the OS cache by calling the flush
method.
This method can also be called with an handler which will be called when the flush is complete.
Using AsyncFile as ReadStream and WriteStream
AsyncFile
implements ReadStream
and WriteStream
. You can then use them with a pump to pump data to and from other read and write streams. For example, this would copy the content to another AsyncFile
:
Vertx vertx = Vertx.vertx();
final AsyncFile output = vertx.fileSystem().openBlocking("target/classes/plagiary.txt", new OpenOptions());
vertx.fileSystem().open("target/classes/les_miserables.txt", new OpenOptions(), result -> {
if (result.succeeded()) {
AsyncFile file = result.result();
Pump.pump(file, output).start();
file.endHandler((r) -> {
System.out.println("Copy done");
});
} else {
System.err.println("Cannot open file " + result.cause());
}
});
You can also use the pump to write file content into HTTP responses, or more generally in anyWriteStream
.
Accessing files from the classpath
When vert.x cannot find the file on the filesystem it tries to resolve the file from the class path. Note that classpath resource paths never start with a /
.
Due to the fact that Java does not offer async access to classpath resources, the file is copied to the filesystem in a worker thread when the classpath resource is accessed the very first time and served from there asynchrously. When the same resource is accessed a second time, the file from the filesystem is served directly from the filesystem. The original content is served even if the classpath resource changes (e.g. in a development system).
This caching behaviour can be set on the setFileResolverCachingEnabled
option. The default value of this option is true
unless the system property vertx.disableFileCaching
is defined.
The path where the files are cached is .vertx
by default and can be customized by setting the system property vertx.cacheDirBase
.
The whole classpath resolving feature can be disabled by setting the system property vertx.disableFileCPResolving
to true
.
NOTE | these system properties are evaluated once when the the io.vertx.core.impl.FileResolver class is loaded, so these properties should be set before loading this class or as a JVM system property when launching it. |
Closing an AsyncFile
To close an AsyncFile
call the close
method. Closing is asynchronous and if you want to be notified when the close has been completed you can specify a handler function as an argument.
Datagram sockets (UDP)
Using User Datagram Protocol (UDP) with Vert.x is a piece of cake.
UDP是無連接傳輸,基本上意味着您沒有與遠程對等方的持久連接。
相反,您可以發送和接收包,並且每個包中都包含遠程地址。
除此之外,UDP不像使用TCP那麼安全,這意味着不能保證發送數據報數據包完全可以接收到它的端點。
唯一的保證是它要麼完全接受要麼完全不接受。
另外,您通常無法發送大於網絡接口MTU大小的數據,這是因爲每個數據包都將作爲一個數據包發送。
但請注意,即使數據包大小小於MTU,它仍可能會失敗。
在哪個尺寸下它會失敗取決於操作系統等。所以經驗法則是嘗試發送小包。
由於UDP的性質,它最適合允許您丟棄數據包的應用程序(例如監視應用程序)。
它的好處是與TCP相比,它有更少的開銷,可以由NetServer和NetClient處理(見上文)。
創建一個DatagramSocket
要使用UDP,您首先需要創建一個DatagramSocket
。如果您只想發送數據或發送和接收,則無關緊要。
DatagramSocket socket = vertx.createDatagramSocket(new DatagramSocketOptions());
返回的DatagramSocket
將不會綁定到特定的端口。如果您只想發送數據(如客戶端),這不是問題,但在下一節中更多地介紹這一點。
發送數據報包
如前所述,用戶數據報協議(UDP)以分組的形式向遠程對等點發送數據,但沒有以持久方式連接到它們。
這意味着每個數據包可以發送到不同的遠程對等體。
發送數據包非常簡單,如下所示:
DatagramSocket socket = vertx.createDatagramSocket(new DatagramSocketOptions());
Buffer buffer = Buffer.buffer("content");
// Send a Buffer
socket.send(buffer, 1234, "10.0.0.1", asyncResult -> {
System.out.println("Send succeeded? " + asyncResult.succeeded());
});
// Send a String
socket.send("A string used as content", 1234, "10.0.0.1", asyncResult -> {
System.out.println("Send succeeded? " + asyncResult.succeeded());
});
接收數據報包
如果你想接收數據包,你需要DatagramSocket
通過調用 listen(…)}
它來綁定數據包。
這樣你將能夠收聽收聽。DatagramPacket`s that were sent to the address and port on which the `DatagramSocket
除此之外,你還想設置一個Handler
將被調用的每個收到DatagramPacket
。
將DatagramPacket
有以下幾種方法:
因此,聽一個特定的地址和端口,你會做這樣的事情:
DatagramSocket socket = vertx.createDatagramSocket(new DatagramSocketOptions());
socket.listen(1234, "0.0.0.0", asyncResult -> {
if (asyncResult.succeeded()) {
socket.handler(packet -> {
// Do something with the packet
});
} else {
System.out.println("Listen failed" + asyncResult.cause());
}
});
請注意,即使{代碼AsyncResult}成功,它也只意味着它可能寫在網絡堆棧中,但不能保證它到達或根本不會到達遠程對等體。
如果你需要這樣的保證,那麼你想使用TCP協議,並在頂部建立一些握手邏輯。
組播
發送多播數據包
多播允許多個套接字接收相同的數據包。這可以通過讓套接字加入可以發送數據包的同一個多播組來實現。
我們將在下一節中介紹如何加入多播組並接收數據包。
發送多播數據包與發送普通數據報包沒有區別。不同之處在於您將多播組地址傳遞給send方法。
這裏展示:
DatagramSocket socket = vertx.createDatagramSocket(new DatagramSocketOptions());
Buffer buffer = Buffer.buffer("content");
// Send a Buffer to a multicast address
socket.send(buffer, 1234, "230.0.0.1", asyncResult -> {
System.out.println("Send succeeded? " + asyncResult.succeeded());
});
所有已加入多播組230.0.0.1的套接字都將收到該數據包。
接收組播數據包
如果您希望接收特定多播組的數據包,則需要DatagramSocket
通過調用listen(…)
它來加入多播組來綁定它。
通過這種方式,您將收到DatagramPackets,這些DatagramPackets已發送到DatagramSocket
偵聽的地址和端口以及發送到多播組的那些端口 。
除此之外,您還需要設置一個處理程序,它將針對每個接收的DatagramPacket進行調用。
將DatagramPacket
有以下幾種方法:
sender()
:代表數據包發送者的InetSocketAddressdata()
:保存接收到的數據的緩衝器。
因此,要監聽特定地址和端口,並且還要接收多播組230.0.0.1的數據包,您可以執行如下所示的操作:
DatagramSocket socket = vertx.createDatagramSocket(new DatagramSocketOptions());
socket.listen(1234, "0.0.0.0", asyncResult -> {
if (asyncResult.succeeded()) {
socket.handler(packet -> {
// Do something with the packet
});
// join the multicast group
socket.listenMulticastGroup("230.0.0.1", asyncResult2 -> {
System.out.println("Listen succeeded? " + asyncResult2.succeeded());
});
} else {
System.out.println("Listen failed" + asyncResult.cause());
}
});
Unlisten /離開組播組
有時您希望在有限的時間內接收多播組的數據包。
在這種情況下,你可以先開始傾聽他們,然後再不聽。
這顯示在這裏:
DatagramSocket socket = vertx.createDatagramSocket(new DatagramSocketOptions());
socket.listen(1234, "0.0.0.0", asyncResult -> {
if (asyncResult.succeeded()) {
socket.handler(packet -> {
// Do something with the packet
});
// join the multicast group
socket.listenMulticastGroup("230.0.0.1", asyncResult2 -> {
if (asyncResult2.succeeded()) {
// will now receive packets for group
// do some work
socket.unlistenMulticastGroup("230.0.0.1", asyncResult3 -> {
System.out.println("Unlisten succeeded? " + asyncResult3.succeeded());
});
} else {
System.out.println("Listen failed" + asyncResult2.cause());
}
});
} else {
System.out.println("Listen failed" + asyncResult.cause());
}
});
阻止多播
除了unlisten多播地址外,還可以針對特定發件人地址阻止多播。
請注意,這僅適用於某些操作系統和內核版本。所以請檢查操作系統文檔是否受支持。
這是一個專家功能。
要阻止來自特定地址的多播,您可以blockMulticastGroup(…)
在DatagramSocket上調用,如下所示:
DatagramSocket socket = vertx.createDatagramSocket(new DatagramSocketOptions());
// Some code
// This would block packets which are send from 10.0.0.2
socket.blockMulticastGroup("230.0.0.1", "10.0.0.2", asyncResult -> {
System.out.println("block succeeded? " + asyncResult.succeeded());
});
DatagramSocket屬性
創建時DatagramSocket
,可以設置多個屬性來改變其與DatagramSocketOptions
對象的行爲。這些在這裏列出:
setSendBufferSize
以字節爲單位設置發送緩衝區大小。setReceiveBufferSize
以字節爲單位設置TCP接收緩衝區大小。setReuseAddress
如果爲true,那麼在TIME_WAIT狀態下的地址可以在關閉後重新使用。setBroadcast
設置或清除SO_BROADCAST套接字選項。當設置此選項時,數據報(UDP)數據包可能會發送到本地接口的廣播地址。setMulticastNetworkInterface
設置或清除IP_MULTICAST_LOOP套接字選項。當該選項設置時,組播數據包也將在本地接口上接收。setMulticastTimeToLive
設置IP_MULTICAST_TTL套接字選項。TTL代表“生存時間”,但在此情況下,它指定允許數據包通過的IP跳數,特別是組播流量。每個轉發數據包的路由器或網關都會減少TTL。如果TTL由路由器遞減到0,它將不會被轉發。
DatagramSocket本地地址
您可以通過調用找到套接字的本地地址(即UDP套接字的這一端的地址) localAddress
。這隻會返回一個InetSocketAddress
,如果你綁定的DatagramSocket
與listen(…)
之前,否則將返回null。
關閉DatagramSocket
您可以通過調用該close
方法來關閉套接字。這將關閉套接字並釋放所有資源
DNS客戶端
通常情況下,您會發現自己處於需要以異步方式獲取DNS信息的情況。
不幸的是,這對於Java虛擬機本身附帶的API來說是不可能的。由於Vert.x提供了它自己的完全異步的DNS解析API。
要獲得DnsClient實例,您將通過Vertx實例創建一個新的實例。
DnsClient client = vertx.createDnsClient(53, "10.0.0.1");
您還可以使用選項創建客戶端並配置查詢超時。
DnsClient client = vertx.createDnsClient(new DnsClientOptions()
.setPort(53)
.setHost("10.0.0.1")
.setQueryTimeout(10000)
);
創建沒有參數或省略服務器地址的客戶端將使用內部使用的服務器的地址作爲非阻塞地址解析。
DnsClient client1 = vertx.createDnsClient();
// Just the same but with a different query timeout
DnsClient client2 = vertx.createDnsClient(new DnsClientOptions().setQueryTimeout(10000));
擡頭
嘗試查找給定名稱的A(ipv4)或AAAA(ipv6)記錄。返回的第一個將被使用,所以它的行爲方式與您在操作系統上使用“nslookup”時可能使用的方式相同。
要查找“vertx.io”的A / AAAA記錄,您通常會使用它:
DnsClient client = vertx.createDnsClient(53, "9.9.9.9");
client.lookup("vertx.io", ar -> {
if (ar.succeeded()) {
System.out.println(ar.result());
} else {
System.out.println("Failed to resolve entry" + ar.cause());
}
});
lookup4
嘗試查找給定名稱的A(ipv4)記錄。返回的第一個將被使用,所以它的行爲方式與您在操作系統上使用“nslookup”時可能使用的方式相同。
要查找“vertx.io”的A記錄,您通常會使用它:
DnsClient client = vertx.createDnsClient(53, "9.9.9.9");
client.lookup4("vertx.io", ar -> {
if (ar.succeeded()) {
System.out.println(ar.result());
} else {
System.out.println("Failed to resolve entry" + ar.cause());
}
});
lookup6
嘗試查找給定名稱的AAAA(ipv6)記錄。返回的第一個將被使用,所以它的行爲方式與您在操作系統上使用“nslookup”時可能使用的方式相同。
要查找“vertx.io”的A記錄,您通常會使用它:
DnsClient client = vertx.createDnsClient(53, "9.9.9.9");
client.lookup6("vertx.io", ar -> {
if (ar.succeeded()) {
System.out.println(ar.result());
} else {
System.out.println("Failed to resolve entry" + ar.cause());
}
});
resolveA
嘗試解析給定名稱的所有A(ipv4)記錄。這與在unix類似的操作系統上使用“挖掘”非常相似。
要查找“vertx.io”的所有A記錄,您通常會這樣做:
DnsClient client = vertx.createDnsClient(53, "9.9.9.9");
client.resolveA("vertx.io", ar -> {
if (ar.succeeded()) {
List<String> records = ar.result();
for (String record : records) {
System.out.println(record);
}
} else {
System.out.println("Failed to resolve entry" + ar.cause());
}
});
resolveAAAA
嘗試解析給定名稱的所有AAAA(ipv6)記錄。這與在unix類似的操作系統上使用“挖掘”非常相似。
要查找“vertx.io”的所有AAAAA記錄,您通常會這樣做:
DnsClient client = vertx.createDnsClient(53, "9.9.9.9");
client.resolveAAAA("vertx.io", ar -> {
if (ar.succeeded()) {
List<String> records = ar.result();
for (String record : records) {
System.out.println(record);
}
} else {
System.out.println("Failed to resolve entry" + ar.cause());
}
});
resolveCNAME
Try to resolve all CNAME records for a given name. This is quite similar to using "dig" on unix like operation systems.
To lookup all the CNAME records for "vertx.io" you would typically do:
DnsClient client = vertx.createDnsClient(53, "9.9.9.9");
client.resolveCNAME("vertx.io", ar -> {
if (ar.succeeded()) {
List<String> records = ar.result();
for (String record : records) {
System.out.println(record);
}
} else {
System.out.println("Failed to resolve entry" + ar.cause());
}
});
resolveMX
Try to resolve all MX records for a given name. The MX records are used to define which Mail-Server accepts emails for a given domain.
To lookup all the MX records for "vertx.io" you would typically do:
DnsClient client = vertx.createDnsClient(53, "9.9.9.9");
client.resolveMX("vertx.io", ar -> {
if (ar.succeeded()) {
List<MxRecord> records = ar.result();
for (MxRecord record: records) {
System.out.println(record);
}
} else {
System.out.println("Failed to resolve entry" + ar.cause());
}
});
Be aware that the List will contain the MxRecord
sorted by the priority of them, which means MX records with smaller priority coming first in the List.
The MxRecord
allows you to access the priority and the name of the MX record by offer methods for it like:
record.priority();
record.name();
resolveTXT
Try to resolve all TXT records for a given name. TXT records are often used to define extra informations for a domain.
To resolve all the TXT records for "vertx.io" you could use something along these lines:
DnsClient client = vertx.createDnsClient(53, "9.9.9.9");
client.resolveTXT("vertx.io", ar -> {
if (ar.succeeded()) {
List<String> records = ar.result();
for (String record: records) {
System.out.println(record);
}
} else {
System.out.println("Failed to resolve entry" + ar.cause());
}
});
resolveNS
Try to resolve all NS records for a given name. The NS records specify which DNS Server hosts the DNS informations for a given domain.
To resolve all the NS records for "vertx.io" you could use something along these lines:
DnsClient client = vertx.createDnsClient(53, "9.9.9.9");
client.resolveNS("vertx.io", ar -> {
if (ar.succeeded()) {
List<String> records = ar.result();
for (String record: records) {
System.out.println(record);
}
} else {
System.out.println("Failed to resolve entry" + ar.cause());
}
});
resolveSRV
Try to resolve all SRV records for a given name. The SRV records are used to define extra informations like port and hostname of services. Some protocols need this extra informations.
To lookup all the SRV records for "vertx.io" you would typically do:
DnsClient client = vertx.createDnsClient(53, "9.9.9.9");
client.resolveSRV("vertx.io", ar -> {
if (ar.succeeded()) {
List<SrvRecord> records = ar.result();
for (SrvRecord record: records) {
System.out.println(record);
}
} else {
System.out.println("Failed to resolve entry" + ar.cause());
}
});
Be aware that the List will contain the SrvRecords sorted by the priority of them, which means SrvRecords with smaller priority coming first in the List.
在SrvRecord
允許您訪問包含在SRV記錄本身所有信息:
record.priority();
record.name();
record.weight();
record.port();
record.protocol();
record.service();
record.target();
有關確切的詳細信息,請參閱API文檔。
resolvePTR
嘗試解決給定名稱的PTR記錄。PTR記錄將ipaddress映射到名稱。
要解析ipaddress 10.0.0.1的PTR記錄,您可以使用PTR概念“1.0.0.10.in-addr.arpa”
DnsClient client = vertx.createDnsClient(53, "9.9.9.9");
client.resolvePTR("1.0.0.10.in-addr.arpa", ar -> {
if (ar.succeeded()) {
String record = ar.result();
System.out.println(record);
} else {
System.out.println("Failed to resolve entry" + ar.cause());
}
});
reverseLookup
嘗試對ipaddress進行反向查找。這與解決PTR記錄基本相同,但允許您只傳入ip地址而不傳遞有效的PTR查詢字符串。
爲ipaddress 10.0.0.1做反向查找可以做類似如下的事情:
DnsClient client = vertx.createDnsClient(53, "9.9.9.9");
client.reverseLookup("10.0.0.1", ar -> {
if (ar.succeeded()) {
String record = ar.result();
System.out.println(record);
} else {
System.out.println("Failed to resolve entry" + ar.cause());
}
});
錯誤處理
正如您在前面的章節中看到的那樣,DnsClient允許您傳入一個處理程序,一旦查詢完成,它將通過AsyncResult通知。
如果發生錯誤,將會收到一個DnsException異常通知,該異常將DnsResponseCode
顯示解析失敗的原因。這個DnsResponseCode可以用來更詳細地檢查原因。
可能的DnsResponseCodes是:
所有這些錯誤都是由DNS服務器本身“生成”的。
您可以從DnsException中獲取DnsResponseCode,如下所示:
DnsClient client = vertx.createDnsClient(53, "10.0.0.1");
client.lookup("nonexisting.vert.xio", ar -> {
if (ar.succeeded()) {
String record = ar.result();
System.out.println(record);
} else {
Throwable cause = ar.cause();
if (cause instanceof DnsException) {
DnsException exception = (DnsException) cause;
DnsResponseCode code = exception.code();
// ...
} else {
System.out.println("Failed to resolve entry" + ar.cause());
}
}
});
流
Vert.x中有幾個對象允許讀取和寫入項目。
In previous versions the streams.adoc package was manipulating Buffer
objects exclusively. From now, streams are not coupled to buffers anymore and they work with any kind of objects.
In Vert.x, write calls return immediately, and writes are queued internally.
It’s not hard to see that if you write to an object faster than it can actually write the data to its underlying resource, then the write queue can grow unbounded - eventually resulting in memory exhaustion.
To solve this problem a simple flow control (back-pressure) capability is provided by some objects in the Vert.x API.
Any flow control aware object that can be written-to implements WriteStream
, while any flow control object that can be read-from is said to implement ReadStream
.
Let’s take an example where we want to read from a ReadStream
then write the data to a WriteStream
.
A very simple example would be reading from a NetSocket
then writing back to the same NetSocket
- since NetSocket
implements both ReadStream
and WriteStream
. Note that this works between any ReadStream
and WriteStream
compliant object, including HTTP requests, HTTP responses, async files I/O, WebSockets, etc.
A naive way to do this would be to directly take the data that has been read and immediately write it to the NetSocket
:
NetServer server = vertx.createNetServer(
new NetServerOptions().setPort(1234).setHost("localhost")
);
server.connectHandler(sock -> {
sock.handler(buffer -> {
// Write the data straight back
sock.write(buffer);
});
}).listen();
上面的例子存在一個問題:如果從套接字中讀取數據的速度比寫回套接字的速度快,它將在寫入隊列中累積NetSocket
,最終耗盡內存。這可能會發生,例如,如果套接字另一端的客戶端讀取速度不夠快,則會對連接施加反作用力。
自NetSocket
實現以來WriteStream
,我們可以WriteStream
在寫入之前檢查它是否已滿:
NetServer server = vertx.createNetServer(
new NetServerOptions().setPort(1234).setHost("localhost")
);
server.connectHandler(sock -> {
sock.handler(buffer -> {
if (!sock.writeQueueFull()) {
sock.write(buffer);
}
});
}).listen();
這個例子不會耗盡RAM,但如果寫入隊列已滿,我們將最終丟失數據。我們真正想要做的是暫停NetSocket
寫隊列滿的時候:
NetServer server = vertx.createNetServer(
new NetServerOptions().setPort(1234).setHost("localhost")
);
server.connectHandler(sock -> {
sock.handler(buffer -> {
sock.write(buffer);
if (sock.writeQueueFull()) {
sock.pause();
}
});
}).listen();
我們快到了,但並不完全。在NetSocket
現在被暫停時,該文件是滿的,但我們還需要取消暫停時,寫入隊列已經處理了積壓:
NetServer server = vertx.createNetServer(
new NetServerOptions().setPort(1234).setHost("localhost")
);
server.connectHandler(sock -> {
sock.handler(buffer -> {
sock.write(buffer);
if (sock.writeQueueFull()) {
sock.pause();
sock.drainHandler(done -> {
sock.resume();
});
}
});
}).listen();
我們終於得到它了。該drainHandler
事件處理程序將被調用時,寫入隊列已準備好接受更多的數據,這種恢復NetSocket
是允許讀取更多的數據。
在編寫Vert.x應用程序時,要做到這一點非常常見,所以我們提供了一個稱爲“輔助類”的輔助類Pump
,爲您完成所有這些難題。你只要餵它ReadStream
加上WriteStream
然後開始它:
NetServer server = vertx.createNetServer(
new NetServerOptions().setPort(1234).setHost("localhost")
);
server.connectHandler(sock -> {
Pump.pump(sock, sock).start();
}).listen();
這與更詳細的例子完全相同。
現在讓我們來看看在方法上ReadStream
和WriteStream
的詳細信息:
ReadStream
ReadStream
通過實施HttpClientResponse
,DatagramSocket
, HttpClientRequest
,HttpServerFileUpload
, HttpServerRequest
,MessageConsumer
, NetSocket
,WebSocket
,TimeoutStream
, AsyncFile
。
功能:
handler
:設置一個將從ReadStream接收項目的處理程序。pause
: pause the handler. When paused no items will be received in the handler.resume
: resume the handler. The handler will be called if any item arrives.exceptionHandler
: Will be called if an exception occurs on the ReadStream.endHandler
: Will be called when end of stream is reached. This might be when EOF is reached if the ReadStream represents a file, or when end of request is reached if it’s an HTTP request, or when the connection is closed if it’s a TCP socket.
WriteStream
WriteStream
is implemented by HttpClientRequest
, HttpServerResponse
WebSocket
, NetSocket
, AsyncFile
, and MessageProducer
Functions:
write
: write an object to the WriteStream. This method will never block. Writes are queued internally and asynchronously written to the underlying resource.setWriteQueueMaxSize
: set the number of object at which the write queue is considered full, and the methodwriteQueueFull
returnstrue
. Note that, when the write queue is considered full, if write is called the data will still be accepted and queued. The actual number depends on the stream implementation, forBuffer
the size represents the actual number of bytes written and not the number of buffers.writeQueueFull
: returnstrue
if the write queue is considered full.exceptionHandler
: Will be called if an exception occurs on theWriteStream
.drainHandler
: The handler will be called if theWriteStream
is considered no longer full.
Pump
Instances of Pump have the following methods:
start
: Start the pump.stop
: Stops the pump. When the pump starts it is in stopped mode.setWriteQueueMaxSize
: This has the same meaning assetWriteQueueMaxSize
on theWriteStream
.
A pump can be started and stopped multiple times.
When a pump is first created it is not started. You need to call the start()
method to start it.
Record Parser
The record parser allows you to easily parse protocols which are delimited by a sequence of bytes, or fixed size records.
It transforms a sequence of input buffer to a sequence of buffer structured as configured (either fixed size or separated records).
For example, if you have a simple ASCII text protocol delimited by '\n' and the input is the following:
buffer1:HELLO\nHOW ARE Y
buffer2:OU?\nI AM
buffer3: DOING OK
buffer4:\n
The record parser would produce
buffer1:HELLO
buffer2:HOW ARE YOU?
buffer3:I AM DOING OK
Let’s see the associated code:
final RecordParser parser = RecordParser.newDelimited("\n", h -> {
System.out.println(h.toString());
});
parser.handle(Buffer.buffer("HELLO\nHOW ARE Y"));
parser.handle(Buffer.buffer("OU?\nI AM"));
parser.handle(Buffer.buffer("DOING OK"));
parser.handle(Buffer.buffer("\n"));
You can also produce fixed sized chunks as follows:
RecordParser.newFixed(4, h -> {
System.out.println(h.toString());
});
For more details, check out the RecordParser
class.
Json Parser
您可以輕鬆解析JSON結構,但需要一次提供JSON內容,但在需要解析非常大的結構時可能並不方便。
非阻塞的JSON解析器是一個事件驅動的解析器,能夠處理非常大的結構。它將一系列輸入緩衝區轉換爲一系列JSON解析事件。
JsonParser parser = JsonParser.newParser();
// Set handlers for various events
parser.handler(event -> {
switch (event.type()) {
case START_OBJECT:
// Start an objet
break;
case END_OBJECT:
// End an objet
break;
case START_ARRAY:
// Start an array
break;
case END_ARRAY:
// End an array
break;
case VALUE:
// Handle a value
String field = event.fieldName();
if (field != null) {
// In an object
} else {
// In an array or top level
if (event.isString()) {
} else {
// ...
}
}
break;
}
});
解析器是非阻塞的,發射事件由輸入緩衝區驅動。
JsonParser parser = JsonParser.newParser();
// start array event
// start object event
// "firstName":"Bob" event
parser.handle(Buffer.buffer("[{\"firstName\":\"Bob\","));
// "lastName":"Morane" event
// end object event
parser.handle(Buffer.buffer("\"lastName\":\"Morane\"},"));
// start object event
// "firstName":"Luke" event
// "lastName":"Lucky" event
// end object event
parser.handle(Buffer.buffer("{\"firstName\":\"Luke\",\"lastName\":\"Lucky\"}"));
// end array event
parser.handle(Buffer.buffer("]"));
// Always call end
parser.end();
事件驅動的解析提供了更多的控制,但是以處理細粒度事件爲代價,這有時會很不方便。JSON解析器允許您在需要時將JSON結構處理爲值:
JsonParser parser = JsonParser.newParser();
parser.objectValueMode();
parser.handler(event -> {
switch (event.type()) {
case START_ARRAY:
// Start the array
break;
case END_ARRAY:
// End the array
break;
case VALUE:
// Handle each object
break;
}
});
parser.handle(Buffer.buffer("[{\"firstName\":\"Bob\"},\"lastName\":\"Morane\"),...]"));
parser.end();
可以在解析期間設置和取消設置值模式,以便在細粒度事件或JSON對象值事件之間切換。
JsonParser parser = JsonParser.newParser();
parser.handler(event -> {
// Start the object
switch (event.type()) {
case START_OBJECT:
// Set object value mode to handle each entry, from now on the parser won't emit start object events
parser.objectValueMode();
break;
case VALUE:
// Handle each object
// Get the field in which this object was parsed
String id = event.fieldName();
System.out.println("User with id " + id + " : " + event.value());
break;
case END_OBJECT:
// Set the object event mode so the parser emits start/end object events again
parser.objectEventMode();
break;
}
});
parser.handle(Buffer.buffer("{\"39877483847\":{\"firstName\":\"Bob\"},\"lastName\":\"Morane\"),...}"));
parser.end();
你也可以對數組做同樣的事情
JsonParser parser = JsonParser.newParser();
parser.handler(event -> {
// Start the object
switch (event.type()) {
case START_OBJECT:
// Set array value mode to handle each entry, from now on the parser won't emit start array events
parser.arrayValueMode();
break;
case VALUE:
// Handle each array
// Get the field in which this object was parsed
System.out.println("Value : " + event.value());
break;
case END_OBJECT:
// Set the array event mode so the parser emits start/end object events again
parser.arrayEventMode();
break;
}
});
parser.handle(Buffer.buffer("[0,1,2,3,4,...]"));
parser.end();
你也可以解碼POJO
parser.handler(event -> {
// Handle each object
// Get the field in which this object was parsed
String id = event.fieldName();
User user = event.mapTo(User.class);
System.out.println("User with id " + id + " : " + user.firstName + " " + user.lastName);
});
每當解析器無法處理緩衝區時,除非設置了異常處理程序,否則將引發異常:
JsonParser parser = JsonParser.newParser();
parser.exceptionHandler(err -> {
// Catch any parsing or decoding error
});
解析器還解析json流:
串聯的json流:
{"temperature":30}{"temperature":50}
線分隔json流:
{"an":"object"}\r\n3\r\n"a string"\r\nnull
欲瞭解更多詳情,請查看JsonParser
課程。
線程安全
指標SPI
默認情況下,Vert.x不記錄任何指標。相反,它爲其他人提供了可以添加到類路徑中的SPI。度量指標SPI是一種高級功能,允許實施者從Vert.x捕獲事件以收集指標。欲瞭解更多信息,請諮詢 API Documentation
。
如果使用嵌入Vert.x,也可以以編程方式指定度量工廠 setFactory
。
OSGi的
Vert.x Core打包爲OSGi包,因此可以用於任何OSGi R4.2 +環境,如Apache Felix或Eclipse Equinox。捆綁出口io.vertx.core*
。
但是,該捆綁對Jackson和Netty有一定的依賴性。要解析vert.x核心包,請部署:
傑克遜註解[2.6.0,3)
傑克遜核心[2.6.2,3)
Jackson Databind [2.6.2,3)
Netty緩衝區[4.0.31,5)
Netty編解碼器[4.0.31,5)
Netty編解碼器/襪子[4.0.31,5)
Netty編解碼器/通用[4.0.31,5)
Netty編解碼器/處理器[4.0.31,5)
Netty編解碼器/傳輸[4.0.31,5)
以下是Apache Felix 5.2.0的工作部署:
14|Active | 1|Jackson-annotations (2.6.0)
15|Active | 1|Jackson-core (2.6.2)
16|Active | 1|jackson-databind (2.6.2)
18|Active | 1|Netty/Buffer (4.0.31.Final)
19|Active | 1|Netty/Codec (4.0.31.Final)
20|Active | 1|Netty/Codec/HTTP (4.0.31.Final)
21|Active | 1|Netty/Codec/Socks (4.0.31.Final)
22|Active | 1|Netty/Common (4.0.31.Final)
23|Active | 1|Netty/Handler (4.0.31.Final)
24|Active | 1|Netty/Transport (4.0.31.Final)
25|Active | 1|Netty/Transport/SCTP (4.0.31.Final)
26|Active | 1|Vert.x Core (3.1.0)
在Equinox上,您可能想要禁用ContextFinder
以下框架屬性: eclipse.bundle.setTCCL=false
'vertx'命令行
該vertx
命令用於從命令行與Vert.x進行交互。主要用於運行Vert.x垂直。爲此,您需要下載並安裝Vert.x發行版,並將bin
安裝目錄添加到您的PATH
環境變量中。還要確保你的Java 8 JDK PATH
。
注意 | JDK需要支持即時編譯Java代碼。 |
運行垂直
您可以使用直接從命令行運行原始Vert.x垂直vertx run
。這裏有幾個run
命令的例子:
vertx run my-verticle.js (1)
vertx run my-verticle.groovy (2)
vertx run my-verticle.rb (3)
vertx run io.vertx.example.MyVerticle (4)
vertx run io.vertx.example.MVerticle -cp my-verticle.jar (5)
vertx run MyVerticle.java (6)
部署JavaScript Verticle
部署Groovy Verticle
部署Ruby垂直
部署已編譯的Java Verticle。Classpath根目錄是當前目錄
部署一個包裝在Jar中的Verticle,該jar需要位於類路徑中
編譯Java源代碼並進行部署
就Java而言,名稱可以是Verticle的完全限定類名稱,也可以直接指定Java源文件,並由Vert.x爲您編譯。
您也可以在Verticle前加上要使用的語言實現的名稱。例如,如果Verticle是一個已編譯的Groovy類,則可以將它作爲前綴,groovy:
以便Vert.x知道它是Groovy類而不是Java類。
vertx run groovy:io.vertx.example.MyGroovyVerticle
The vertx run
command can take a few optional parameters, they are:
-conf <config_file>
- Provides some configuration to the verticle.config_file
is the name of a text file containing a JSON object that represents the configuration for the verticle. This is optional.-cp <path>
- The path on which to search for the verticle and any other resources used by the verticle. This defaults to.
(current directory). If your verticle references other scripts, classes or other resources (e.g. jar files) then make sure these are on this path. The path can contain multiple path entries separated by:
(colon) or;
(semi-colon) depending on the operating system. Each path entry can be an absolute or relative path to a directory containing scripts, or absolute or relative filenames for jar or zip files. An example path might be-cp classes:lib/otherscripts:jars/myjar.jar:jars/otherjar.jar
. Always use the path to reference any resources that your verticle requires. Do not put them on the system classpath as this can cause isolation issues between deployed verticles.-instances <instances>
- 要實例化的Verticle的實例數量。每個Verticle實例都是嚴格單線程的,因此可以跨可用內核擴展應用程序,您可能想要部署多個實例。如果省略,則將部署單個實例。-worker
- 該選項確定垂直是否是工作者垂直。-cluster
- 此選項確定Vert.x實例是否嘗試在網絡上與其他Vert.x實例組成羣集。集羣Vert.x實例允許Vert.x與其他節點形成分佈式事件總線。默認值是false
(不聚類)。-cluster-port
- 如果還指定了羣集選項,那麼這將確定哪個端口將用於與其他Vert.x實例進行羣集通信。默認是0
- 這意味着“ 選擇一個免費的隨機端口 ”。除非確實需要綁定到特定端口,否則通常不需要指定此參數。-cluster-host
- 如果還指定了羣集選項,則這將確定哪個主機地址將用於與其他Vert.x實例進行羣集通信。默認情況下,它會嘗試從可用的界面中選擇一個。如果您有多個接口並且您想使用特定接口,請在此處指定。-ha
- 如果指定,Verticle將部署爲高可用性(HA)部署。請參閱相關部分了解更多詳情-quorum
- 與...一起使用-ha
。它指定集羣中的任何HA部署ID處於活動狀態的最小節點數。默認爲0。-hagroup
- 與...一起使用-ha
。它指定此節點將加入的HA組。羣集中可以有多個HA組。節點只會故障轉移到同一組中的其他節點。默認值是`__DEFAULT__`
您還可以使用:設置系統屬性-Dkey=value
。
這裏有一些例子:
使用默認設置運行JavaScript verticle server.js
vertx run server.js
運行預編譯的Java Verticle指定類路徑的10個實例
vertx run com.acme.MyVerticle -cp "classes:lib/myjar.jar" -instances 10
通過源文件運行Java Verticle的10個實例
vertx run MyVerticle.java -instances 10
運行ruby worker verticle的20個實例
vertx run order_worker.rb -instances 20 -worker
在同一臺計算機上運行兩個JavaScript Verticle,並讓它們彼此以及網絡上的任何其他服務器羣集在一起
vertx run handler.js -cluster
vertx run sender.js -cluster
運行一個Ruby verticle,傳遞它一些配置
vertx run my_verticle.rb -conf my_verticle.conf
其中my_verticle.conf
可能包含以下內容:
{
"name": "foo",
"num_widgets": 46
}
該配置將通過核心API在Verticle內部提供。
使用vert.x的高可用性功能時,您可能需要創建vert.x 的裸露實例。此實例在啓動時不會部署任何Verticle,但如果羣集的另一個節點死亡,則會收到Verticle。要創建裸露的實例,請啓動:
vertx bare
根據您的羣集配置,您可能需要追加cluster-host
和cluster-port
參數。
執行打包成一個胖罐子的Vert.x應用程序
一個胖罐子是一個可執行的JAR嵌入它的依賴。這意味着您不必在執行jar的機器上預先安裝Vert.x。就像任何可執行Java jar一樣,它可以被執行。
java -jar my-application-fat.jar
Vert.x沒有特別的關於這個,你可以用任何Java應用程序來做到這一點
您可以創建自己的主類並在清單中指定該類,但建議您將代碼編寫爲Verticle,並使用Vert.x Launcher
類(io.vertx.core.Launcher
)作爲主類。這是在命令行運行Vert.x時使用的主類,因此可以指定命令行參數,例如-instances
爲了更容易地擴展應用程序。
要將Verticle部署在像這樣的fatjar中,您必須具有以下清單:
Main-Class
設置io.vertx.core.Launcher
Main-Verticle
指定主Verticle(完全限定的類名或腳本文件名)
您還可以提供您將傳遞給的常用命令行參數vertx run
:
java -jar my-verticle-fat.jar -cluster -conf myconf.json
java -jar my-verticle-fat.jar -cluster -conf myconf.json -cp path/to/dir/conf/cluster_xml
注意 | 請查閱示例存儲庫中的Maven / Gradle最簡單和Maven / Gradle Verticle示例,以獲取構建應用程序的示例,作爲fatjars。 |
胖胖的jar run
默認執行這個命令。
顯示Vert.x的版本
要顯示vert.x版本,只需啓動:
vertx version
其他命令
該vertx
命令行和Launcher
也提供其它的命令除了run
和version
:
您可以bare
使用以下方法創建實例:
vertx bare
# or
java -jar my-verticle-fat.jar bare
您還可以使用以下命令在後臺啓動應用程序:
java -jar my-verticle-fat.jar start -Dvertx-id=my-app-name
如果my-app-name
未設置,則會生成一個隨機ID,並將其打印在命令提示符處。您可以將run
選項傳遞給start
命令:
java -jar my-verticle-fat.jar start -Dvertx-id=my-app-name -cluster
一旦在後臺啓動,您可以使用以下stop
命令停止它:
java -jar my-verticle-fat.jar stop my-app-name
您還可以使用以下命令列出在後臺啓動的vert.x應用程序:
java -jar my-verticle-fat.jar list
的start
,stop
和list
命令也可以從vertx
工具。start`命令支持一些選項:
vertx-id
:應用程序ID,如果未設置,則使用隨機UUIDjava-opts
:Java虛擬機選項,JAVA_OPTS
如果未設置則使用環境變量。redirect-output
:將生成的進程輸出和錯誤流重定向到父進程流。如果選項值包含空格,請不要忘記在``“``(雙引號)之間換行。
當`start`命令產生一個新進程時,傳遞給JVM的java選項不會被傳播,所以你必須** 使用`java-opts`來配置JVM(`-X`,`-D` ...)。如果您使用`CLASSPATH`環境變量,請確保它 包含所有必需的罐子(vertx-core,你的罐子和所有的依賴)。
這組命令是可擴展的,請參閱擴展vert.x啓動器部分。
實時重新部署
開發時,在文件更改時自動重新部署應用程序可能很方便。該vertx
命令行工具和更普遍的Launcher
類提供了此功能。這裏有些例子:
vertx run MyVerticle.groovy --redeploy="**/*.groovy" --launcher-class=io.vertx.core.Launcher
vertx run MyVerticle.groovy --redeploy="**/*.groovy,**/*.rb" --launcher-class=io.vertx.core.Launcher
java io.vertx.core.Launcher run org.acme.MyVerticle --redeploy="**/*.class" --launcher-class=io.vertx.core
.Launcher -cp ...
重新部署過程如下執行。首先,您的應用程序作爲後臺應用程序啓動(使用該start
命令)。在匹配文件更改時,進程停止並重新啓動應用程序。這避免了泄漏,因爲該過程重新啓動。
要啓用實時重新部署,請將該--redeploy
選項傳遞給run
命令。該--redeploy
指示設置文件的觀看。這組可以使用Ant風格的圖案(有**
,*
和?
)。您可以使用逗號(,
)分隔它們來指定多個集合。模式與當前工作目錄相關。
傳遞給run
命令的參數將傳遞給應用程序。Java虛擬機選項可以使用配置--java-opts
。例如,要傳遞conf
參數或系統屬性,您需要使用:--java-opts="-conf=my-conf.json -Dkey=value"
該--launcher-class
選項決定用主類應用程序啓動。一般來說 Launcher
,但你已經用你自己的主力。
重新部署功能可以在您的IDE中使用:
Eclipse - 使用類主類創建一個Run配置。在Program arguments區域(在Arguments選項卡中),寫入。您還可以添加其他參數。隨着Eclipse在保存時逐漸編譯文件,重新部署工作將順利進行。
io.vertx.core.Launcher
run your-verticle-fully-qualified-name --redeploy=**/*.java --launcher-class=io.vertx.core.Launcher
IntelliJ - 創建一個運行配置(應用程序),將Main類設置爲
io.vertx.core.Launcher
。在程序參數中寫:run your-verticle-fully-qualified-name --redeploy=**/*.class --launcher-class=io.vertx.core.Launcher
。要觸發重新部署,你需要做的項目或模塊明確(建立菜單→ 製作項目)。
要調試您的應用程序,請將您的運行配置創建爲遠程應用程序並使用配置調試器--java-opts
。但是,不要忘記在每次重新部署後重新插入調試器,因爲每次都會創建一個新進程。
您也可以在重新部署週期中掛鉤構建過程:
java -jar target/my-fat-jar.jar --redeploy="**/*.java" --on-redeploy="mvn package"
java -jar build/libs/my-fat-jar.jar --redeploy="src/**/*.java" --on-redeploy='./gradlew shadowJar'
“on-redeploy”選項指定在應用程序關閉後和重新啓動之前調用的命令。因此,如果更新某些運行時構件,則可以掛鉤構建工具。例如,您可以啓動gulp
或grunt
更新您的資源。不要忘記,將參數傳遞給你的應用程序需要 --java-opts
param:
java -jar target/my-fat-jar.jar --redeploy="**/*.java" --on-redeploy="mvn package" --java-opts="-Dkey=val"
java -jar build/libs/my-fat-jar.jar --redeploy="src/**/*.java" --on-redeploy='./gradlew shadowJar' --java-opts="-Dkey=val"
重新部署功能還支持以下設置:
redeploy-scan-period
:文件系統檢查週期(以毫秒爲單位),默認爲250msredeploy-grace-period
:兩次重新部署之間等待的時間(以毫秒爲單位),缺省情況下爲1000毫秒redeploy-termination-period
:停止應用程序後(在啓動用戶命令之前)等待的時間量。在Windows上,這個過程不會立即被終止。時間以毫秒爲單位。默認情況下爲0毫秒。
集羣管理器
在Vert.xa中,集羣管理器用於各種功能,包括:
Vert.x節點在羣集中的發現和組成員身份
維護集羣廣泛的主題用戶列表(因此我們知道哪些節點對哪些事件總線地址感興趣)
分佈式地圖支持
分佈式鎖
分佈式計數器
集羣管理器不處理事件總線節點間傳輸,這是由Vert.x直接通過TCP連接完成的。
Vert.x發行版中使用的默認集羣管理器是使用Hazelcast的默認集羣管理器,但由於Vert.x集羣管理器是可插拔的,因此可以輕鬆地用不同的實現來替換它。
集羣管理器必須實現該接口ClusterManager
。通過使用Java Service Loader功能ClusterManager
在類路徑上查找實例,Vert.x在運行時定位集羣管理器 。
如果您在命令行使用Vert.x並且想要使用羣集,則應確保lib
Vert.x安裝的目錄包含您的羣集管理器jar。
如果您使用Maven或Gradle項目中的Vert.x,只需將羣集管理器jar添加爲項目的依賴項即可。
如果使用嵌入Vert.x,也可以以編程方式指定集羣管理器 setClusterManager
。
記錄
使用它的內置日誌API的Vert.x日誌。默認實現使用JDK(JUL)日誌記錄,因此不需要額外的日誌記錄依賴關係。
配置JUL日誌記錄
JUL日誌配置文件可以通過提供一個名爲:的系統屬性來以正常的JUL方式指定java.util.logging.config.file
,其值是您的配置文件。有關這方面的更多信息和JUL配置文件的結構,請參閱JUL日誌文檔。
Vert.x還提供了一種稍微更方便的方式來指定配置文件,而無需設置系統屬性。只需vertx-default-jul-logging.properties
在你的類路徑中提供一個帶有名稱的JUL配置文件(例如在你的fatjar中),Vert.x將使用它來配置JUL。
使用另一個日誌框架
如果您不希望Vert.x將JUL用於自己的日誌記錄,則可以將其配置爲使用其他日誌框架,例如Log4J或SLF4J。
要做到這一點,你應該設置一個系統屬性,調用vertx.logger-delegate-factory-class-name
一個實現接口的Java類的名稱LogDelegateFactory
。我們提供預建實現的用於Log4J的(1版),Log4J的2和SLF4J與類名 io.vertx.core.logging.Log4jLogDelegateFactory
,io.vertx.core.logging.Log4j2LogDelegateFactory
並io.vertx.core.logging.SLF4JLogDelegateFactory
分別。如果你想使用這些實現,你還應該確保相關的Log4J或SLF4J jar包在你的類路徑中。
請注意,所提供的Log4J 1委託不支持參數化消息。Log4J 2的代表使用{}
類似SLF4J委託的語法。JUL委託使用{x}
語法。
從您的應用程序登錄
Vert.x本身就是一個庫,您可以使用任何日誌庫來使用該日誌庫的API來從自己的應用程序登錄。
但是,如果您願意,也可以使用上述的Vert.x日誌記錄工具爲您的應用程序提供日誌記錄。
要做到這一點,你可以使用它LoggerFactory
來獲取Logger
你用於日誌記錄的實例,例如
Logger logger = LoggerFactory.getLogger(className);
logger.info("something happened");
logger.error("oops!", exception);
警告 | 記錄後端使用不同的格式來表示參數化消息中的可替換令牌。因此,如果您依賴Vert.x參數化日誌記錄方法,則無需更改代碼即可切換後端。 |
Netty日誌記錄
在配置日誌記錄時,您也應該關心配置Netty日誌記錄。
Netty不依賴外部日誌配置(例如系統屬性),而是基於Netty類中可見的日誌庫實現日誌配置:
SLF4J
如果可見,請使用庫否則使用,
Log4j
如果它是可見的否則後備
java.util.logging
通過直接在Netty的內部記錄器實現上設置記錄器實現可以被強制爲特定的實現io.netty.util.internal.logging.InternalLoggerFactory
:
// Force logging to Log4j
InternalLoggerFactory.setDefaultFactory(Log4JLoggerFactory.INSTANCE);
故障排除
啓動時發出SLF4J警告
如果在啓動應用程序時看到以下消息:
SLF4J:無法加載類“org.slf4j.impl.StaticLoggerBinder”。 SLF4J:默認爲無操作(NOP)記錄器實施 SLF4J:有關更多詳細信息,請參閱http://www.slf4j.org/codes.html#StaticLoggerBinder。
這意味着你的類路徑中有SLF4J-API,但沒有實際的綁定。使用SLF4J記錄的消息將被丟棄。你應該爲你的類路徑添加一個綁定。檢查https://www.slf4j.org/manual.html#swapping選擇一個綁定並對其進行配置。
請注意,Netty默認查找SLF4-API jar並使用它。
連接重置由對等
如果您的日誌顯示一堆:
io.vertx.core.net.impl.ConnectionBase 嚴重:java.io.IOException:由對等方重置連接
這意味着客戶端正在重置HTTP連接而不是關閉它。此消息還表示您可能沒有使用完整的有效負載(連接在您未能完成之前就被切斷)。
主機名稱解析
Vert.x使用地址解析器將主機名解析爲IP地址,而不是使用JVM內置的阻止解析器。
主機名使用以下內容解析爲IP地址:
操作系統的主機文件
否則DNS查詢針對服務器列表
默認情況下,它將使用來自環境的系統DNS服務器地址列表,如果該列表不能被檢索,它將使用Google的公共DNS服務器"8.8.8.8"
和"8.8.4.4"
。
創建Vertx
實例時還可以配置DNS服務器:
Vertx vertx = Vertx.vertx(new VertxOptions().
setAddressResolverOptions(
new AddressResolverOptions().
addServer("192.168.0.1").
addServer("192.168.0.2:40000"))
);
DNS服務器的默認端口是53
,當服務器使用不同的端口時,可以使用冒號分隔符來設置此端口:192.168.0.2:40000
。
注意 | 有時可能需要使用JVM內置解析器,JVM系統屬性 -Dvertx.disableDnsResolver = true會激活此行爲 |
故障轉移
當服務器沒有及時回覆時,解析器將嘗試從列表中選擇下一個,搜索受到限制setMaxQueries
(默認值爲4
查詢)。
當解析器在getQueryTimeout
毫秒內未收到正確答案(默認值爲5
秒)時,DNS查詢被視爲失敗 。
服務器列表旋轉
默認情況下,dns服務器選擇使用第一個,其餘服務器用於故障轉移。
您可以配置setRotateServers
爲true
讓解析器執行循環法選擇。它將查詢負載分散到服務器中,並避免所有查找命中列表中的第一個服務器。
故障轉移仍然適用,並將使用列表中的下一臺服務器。
主機映射
操作系統的主機文件用於執行ipaddress的主機名查找。
替代主機文件可以用來代替:
Vertx vertx = Vertx.vertx(new VertxOptions().
setAddressResolverOptions(
new AddressResolverOptions().
setHostsPath("/path/to/hosts"))
);
搜索域
默認情況下,解析器將使用環境中的系統DNS搜索域。或者,可以提供明確的搜索域列表:
Vertx vertx = Vertx.vertx(new VertxOptions().
setAddressResolverOptions(
new AddressResolverOptions().addSearchDomain("foo.com").addSearchDomain("bar.com"))
);
使用搜索域列表時,點的數量閾值是1
從/etc/resolv.conf
Linux 加載的,但可以使用它將其配置爲特定值setNdots
。
高可用性和故障切換
Vert.x允許您運行具有高可用性(HA)支持的Verticle。在這種情況下,當運行Verticle的vert.x實例突然死亡時,Verticle會遷移到另一個vertx實例。vert.x實例必須位於同一個羣集中。
自動故障轉移
當vert.x在啓用HA的情況下運行時,如果vert.x運行失敗或死亡的vert.x實例,Verticle會自動在羣集的另一個vert.x實例上重新部署。我們稱之爲垂直故障切換。
要在啓用HA的情況下運行vert.x ,只需將該-ha
標誌添加到命令行:
vertx run my-verticle.js -ha
現在讓HA工作,您需要集羣中有多個Vert.x實例,因此假設您有另一個已經啓動的Vert.x實例,例如:
vertx run my-other-verticle.js -ha
如果my-verticle.js
現在正在運行的Vert.x實例已經死掉(可以通過殺死進程來測試它kill -9
),那麼運行的Vert.x實例my-other-verticle.js
會自動部署my-verticle .js
,因爲Vert.x實例正在運行兩個Verticle。
注意 | 只有當第二個Vert.x實例有權訪問Verticle文件時(這裏my-verticle.js )才能進行遷移。 |
重要 | 請注意,乾淨關閉Vert.x實例不會導致發生故障轉移,例如CTRL-C 或kill -SIGINT |
您也可以啓動裸 Vert.x實例 - 即最初不運行任何Verticle的實例,它們也將故障切換集羣中的節點。要開始一個裸露的實例,你只需要:
vertx run -ha
在使用-ha
交換機時,您不需要提供-cluster
交換機,因爲如果您需要HA,則會假定爲集羣。
注意 | 根據您的羣集配置,您可能需要自定義羣集管理器配置(默認爲Hazelcast),和/或添加cluster-host 和cluster-port 參數。 |
HA組
當使用HA運行Vert.x實例時,您還可以選擇指定HA組。HA組表示集羣中的邏輯節點組。只有具有相同HA組的節點才能互相故障轉移。如果您未指定HA組,__DEFAULT__
則使用默認組。
要指定一個HA組,-hagroup
在運行Verticle時使用該開關,例如
vertx run my-verticle.js -ha -hagroup my-group
我們來看一個例子:
在第一個終端中:
vertx run my-verticle.js -ha -hagroup g1
在第二個終端中,讓我們使用相同的組運行另一個Verticle:
vertx run my-other-verticle.js -ha -hagroup g1
最後,在第三個終端中,使用不同的組來啓動另一個Verticle:
vertx run yet-another-verticle.js -ha -hagroup g2
如果我們終止終端1中的實例,它將故障轉移到終端2中的實例,而不是終端3中的實例,因爲它具有不同的組。
如果我們殺死終端3中的實例,它將不會失敗,因爲該組中沒有其他vert.x實例。
處理網絡分區 - Quora
HA實施也支持quora。法定人數是分佈式事務必須獲得才能被允許在分佈式系統中執行操作的最低票數。
啓動Vert.x實例時,可以指示它quorum
在部署任何HA部署之前需要一個實例。在此情況下,仲裁是羣集中特定組的最小節點數。通常,您將法定大小選擇到組中節點數量的Q = 1 + N/2
哪個位置N
。如果Q
羣集中的節點少於這些節點,HA部署將取消部署。如果/當重新達到法定人數時,他們將重新部署。通過這樣做,您可以防止網絡分區,即分裂大腦。
這裏有更多關於quora的信息。
使用-quorum
在命令行中指定的法定數來運行vert.x實例,例如
在第一個終端中:
vertx run my-verticle.js -ha -quorum 3
此時,Vert.x實例將啓動,但不會部署該模塊(尚),因爲羣集中只有一個節點,而不是3個。
在第二個終端中:
vertx run my-other-verticle.js -ha -quorum 3
此時,Vert.x實例將啓動,但不會部署模塊(尚),因爲羣集中只有兩個節點,而不是3個。
在第三個控制檯中,您可以啓動另一個vert.x實例:
vertx run yet-another-verticle.js -ha -quorum 3
好極了! - 我們有三個節點,這是法定人數。此時模塊將自動部署在所有實例上。
如果我們現在關閉或關閉其中一個節點,模塊將自動在其他節點上取消部署,因爲不再有法定人數。
Quora也可以與ha組一起使用。在這種情況下,每個特定組都會解決quora問題。
本地傳輸
Vert.x可以在BSD(OSX)和Linux上使用本地傳輸(如果可用)運行:
Vertx vertx = Vertx.vertx(new VertxOptions().
setPreferNativeTransport(true)
);
// True when native is available
boolean usingNative = vertx.isNativeTransportEnabled();
System.out.println("Running with native: " + usingNative);
注意 | 如果您的應用程序需要本地傳輸,則更喜歡本地傳輸不會阻止應用程序執行,您需要檢查isNativeTransportEnabled 。 |
本地Linux傳輸
您需要在您的類路徑中添加以下依賴項:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>4.1.15.Final</version>
<classifier>linux-x86_64</classifier>
</dependency>
Linux上的Native可爲您提供額外的網絡選項:
SO_REUSEPORT
TCP_QUICKACK
TCP_CORK
TCP_FASTOPEN
vertx.createHttpServer(new HttpServerOptions()
.setTcpFastOpen(fastOpen)
.setTcpCork(cork)
.setTcpQuickAck(quickAck)
.setReusePort(reusePort)
);
原生BSD傳輸
您需要在您的類路徑中添加以下依賴項:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>4.1.15.Final</version>
<classifier>osx-x86_64</classifier>
</dependency>
MacOS Sierra及以上版本均受支持。
BSD本地化爲您提供額外的網絡選項:
SO_REUSEPORT
vertx.createHttpServer(new HttpServerOptions().setReusePort(reusePort));
域套接字
Natives爲以下項目提供支持域套接字NetServer
:
vertx.createNetServer().connectHandler(so -> {
// Handle application
}).listen(SocketAddress.domainSocketAddress("/var/tmp/myservice.sock"));
以及NetClient
:
NetClient netClient = vertx.createNetClient();
// Only available on BSD and Linux
netClient.connect(SocketAddress.domainSocketAddress("/var/tmp/myservice.sock"), ar -> {
if (ar.succeeded()) {
// Connected
} else {
ar.cause().printStackTrace();
}
});
注意 | 支持HttpServer 並且HttpClient 可以在Vert.x的更高版本中預期 |
安全說明
Vert.x是一個工具包,而不是一個自發的框架,我們強迫你以某種方式做事。這給了你作爲一個開發者的巨大力量,但是這對你來說是一個很大的責任。
與任何工具包一樣,可以編寫不安全的應用程序,因此在開發應用程序時應特別小心,尤其是在向公衆公開的情況下(例如通過互聯網)。
Web應用程序
如果編寫Web應用程序,強烈建議您直接使用Vert.x-Web而不是Vert.x核心來提供資源並處理文件上載。
Vert.x-Web在請求中標準化路徑,以防止惡意客戶製作URL訪問Web根目錄之外的資源。
同樣,對於文件上傳,Vert.x-Web提供了上傳到磁盤上已知位置的功能,並且不依賴客戶端在上載時提供的文件名,可以將其上傳到磁盤上的不同位置。
Vert.x核心本身不提供這種檢查,因此您將作爲開發人員自行實施它們。
聚集的事件公交車輛
當在網絡上的不同Vert.x節點之間對事件總線進行聚類時,流量將在線路上未加密發送,因此如果您有機密數據要發送且您的Vert.x節點不在可信網絡上。
標準安全最佳實踐
無論是使用Vert.x還是任何其他工具包編寫,任何服務都可能存在漏洞,因此始終遵循安全最佳實踐,尤其是在您的服務面向公衆時。
例如,您應該始終在DMZ中運行它們並使用權限有限的用戶帳戶,以便在服務受到威脅時限制損害程度。
Vert.x命令行界面API
Vert.x Core提供了一個用於解析傳遞給程序的命令行參數的API。
它還能夠打印幫助消息,詳細說明可用於命令行工具的選項。即使這些功能遠離Vert.x核心主題,該API也可用於Launcher
您可以在fat-jar 和vertx
命令行工具中使用的類。另外,它是polyglot(可以從任何支持的語言中使用)並在Vert.x Shell中使用。
Vert.x CLI提供了一個模型來描述您的命令行界面,但也是一個解析器。這個解析器支持不同類型的語法:
類似POSIX選項(即。
tar -zxvf foo.tar.gz
)GNU像長期選項(即。
du --human-readable --max-depth=1
)Java像屬性(即
java -Djava.awt.headless=true -Djava.net.useSystemProxies=true Foo
)帶有附加價值的短期期權(即
gcc -O2 foo.c
)單個連字符(即
ant -projecthelp
)的長選項
使用CLI API是一個3個步驟的過程:
命令行界面的定義
用戶命令行的解析
查詢/詢問
定義階段
CLI cli = CLI.create("copy")
.setSummary("A command line interface to copy files.")
.addOption(new Option()
.setLongName("directory")
.setShortName("R")
.setDescription("enables directory support")
.setFlag(true))
.addArgument(new Argument()
.setIndex(0)
.setDescription("The source")
.setArgName("source"))
.addArgument(new Argument()
.setIndex(1)
.setDescription("The destination")
.setArgName("target"));
正如你所看到的,你可以創建一個新的CLI
使用 CLI.create
。傳遞的字符串是CLI的名稱。一旦創建,您可以設置摘要和說明。摘要旨在簡短(一行),而描述可以包含更多詳細信息。每個選項和參數也CLI
使用addArgument
和 addOption
方法添加到對象上 。
選項
An Option
is a command line parameter identified by a key present in the user command line. Options must have at least a long name or a short name. Long name are generally used using a --
prefix, while short names are used with a single -
. Options can get a description displayed in the usage (see below). Options can receive 0, 1 or several values. An option receiving 0 values is a flag
, and must be declared using setFlag
. By default, options receive a single value, however, you can configure the option to receive several values using setMultiValued
:
CLI cli = CLI.create("some-name")
.setSummary("A command line interface illustrating the options valuation.")
.addOption(new Option()
.setLongName("flag").setShortName("f").setFlag(true).setDescription("a flag"))
.addOption(new Option()
.setLongName("single").setShortName("s").setDescription("a single-valued option"))
.addOption(new Option()
.setLongName("multiple").setShortName("m").setMultiValued(true)
.setDescription("a multi-valued option"));
Options can be marked as mandatory. A mandatory option not set in the user command line throws an exception during the parsing:
CLI cli = CLI.create("some-name")
.addOption(new Option()
.setLongName("mandatory")
.setRequired(true)
.setDescription("a mandatory option"));
Non-mandatory options can have a default value. This value would be used if the user does not set the option in the command line:
CLI cli = CLI.create("some-name")
.addOption(new Option()
.setLongName("optional")
.setDefaultValue("hello")
.setDescription("an optional option with a default value"));
An option can be hidden using the setHidden
method. Hidden option are not listed in the usage, but can still be used in the user command line (for power-users).
If the option value is contrained to a fixed set, you can set the different acceptable choices:
CLI cli = CLI.create("some-name")
.addOption(new Option()
.setLongName("color")
.setDefaultValue("green")
.addChoice("blue").addChoice("red").addChoice("green")
.setDescription("a color"));
Options can also be instantiated from their JSON form.
Arguments
Unlike options, arguments do not have a key and are identified by their index. For example, in java com.acme.Foo
, com.acme.Foo
is an argument.
參數沒有名稱,使用基於0的索引進行標識。第一個參數有索引0
:
CLI cli = CLI.create("some-name")
.addArgument(new Argument()
.setIndex(0)
.setDescription("the first argument")
.setArgName("arg1"))
.addArgument(new Argument()
.setIndex(1)
.setDescription("the second argument")
.setArgName("arg2"));
如果您沒有設置參數索引,它會使用聲明順序自動計算它。
CLI cli = CLI.create("some-name")
// will have the index 0
.addArgument(new Argument()
.setDescription("the first argument")
.setArgName("arg1"))
// will have the index 1
.addArgument(new Argument()
.setDescription("the second argument")
.setArgName("arg2"));
這argName
是可選的,用於使用消息。
作爲選項,Argument
可以:
被隱藏使用
setHidden
被強制使用
setRequired
有一個默認值使用
setDefaultValue
接收幾個值
setMultiValued
- 只有最後一個參數可以是多值的。
參數也可以從他們的JSON形式實例化。
用法生成
一旦你的CLI
實例配置,可以生成用法消息:
CLI cli = CLI.create("copy")
.setSummary("A command line interface to copy files.")
.addOption(new Option()
.setLongName("directory")
.setShortName("R")
.setDescription("enables directory support")
.setFlag(true))
.addArgument(new Argument()
.setIndex(0)
.setDescription("The source")
.setArgName("source"))
.addArgument(new Argument()
.setIndex(0)
.setDescription("The destination")
.setArgName("target"));
StringBuilder builder = new StringBuilder();
cli.usage(builder);
它會生成如下所示的使用消息:
Usage: copy [-R] source target
A command line interface to copy files.
-R,--directory enables directory support
如果您需要調整使用信息,請檢查UsageMessageFormatter
課程。
解析階段
一旦你的CLI
實例配置好了,你可以解析用戶命令行來評估每個選項和參數:
CommandLine commandLine = cli.parse(userCommandLineArguments);
該parse
方法返回一個CommandLine
包含值的對象。默認情況下,它會驗證用戶命令行並檢查是否設置了每個必需的選項和參數以及每個選項所接收的值的數量。您可以通過false
作爲第二個參數傳遞來禁用驗證parse
。如果要檢查參數或選項,即使分析的命令行無效,這也很有用。
您可以檢查是否 CommandLine
有效使用isValid
。
查詢/詢問階段
一旦解析完成,您可以從 方法CommandLine
返回的對象中檢索選項和參數的值 parse
:
CommandLine commandLine = cli.parse(userCommandLineArguments);
String opt = commandLine.getOptionValue("my-option");
boolean flag = commandLine.isFlagEnabled("my-flag");
String arg0 = commandLine.getArgumentValue(0);
你的一個選項可以被標記爲“幫助”。如果用戶命令行啓用了“幫助”選項,則驗證不會失敗,但可以讓您檢查用戶是否請求幫助:
CLI cli = CLI.create("test")
.addOption(
new Option().setLongName("help").setShortName("h").setFlag(true).setHelp(true))
.addOption(
new Option().setLongName("mandatory").setRequired(true));
CommandLine line = cli.parse(Collections.singletonList("-h"));
// The parsing does not fail and let you do:
if (!line.isValid() && line.isAskingForHelp()) {
StringBuilder builder = new StringBuilder();
cli.usage(builder);
stream.print(builder.toString());
}
鍵入的選項和參數
TypedOption
並TypedArgument
讓您指定一個類型,以便(String)原始值轉換爲指定的類型。
取而代之的 Option
和Argument
,使用TypedOption
和TypedArgument
在CLI
:定義
CLI cli = CLI.create("copy")
.setSummary("A command line interface to copy files.")
.addOption(new TypedOption<Boolean>()
.setType(Boolean.class)
.setLongName("directory")
.setShortName("R")
.setDescription("enables directory support")
.setFlag(true))
.addArgument(new TypedArgument<File>()
.setType(File.class)
.setIndex(0)
.setDescription("The source")
.setArgName("source"))
.addArgument(new TypedArgument<File>()
.setType(File.class)
.setIndex(0)
.setDescription("The destination")
.setArgName("target"));
然後,您可以按如下方式檢索轉換後的值:
CommandLine commandLine = cli.parse(userCommandLineArguments);
boolean flag = commandLine.getOptionValue("R");
File source = commandLine.getArgumentValue("source");
File target = commandLine.getArgumentValue("target");
vert.x CLI能夠轉換爲類:
具有帶單個
String
參數的構造 函數,如File
orJsonObject
用靜態
from
或fromString
方法使用靜態
valueOf
方法,如基元類型和枚舉
另外,您可以實現自己的功能Converter
並指示CLI使用此轉換器:
CLI cli = CLI.create("some-name")
.addOption(new TypedOption<Person>()
.setType(Person.class)
.setConverter(new PersonConverter())
.setLongName("person"));
對於布爾值,該布爾值進行評估,以true
:on
,yes
,1
,true
。
如果您的某個選項具有某種enum
類型,則會自動計算該組選項。
使用註釋
您還可以使用註釋來定義您的CLI。定義是在類和setter 方法上使用註釋完成的:
@Name("some-name")
@Summary("some short summary.")
@Description("some long description")
public class AnnotatedCli {
private boolean flag;
private String name;
private String arg;
@Option(shortName = "f", flag = true)
public void setFlag(boolean flag) {
this.flag = flag;
}
@Option(longName = "name")
public void setName(String name) {
this.name = name;
}
@Argument(index = 0)
public void setArg(String arg) {
this.arg = arg;
}
}
一旦註釋,您可以CLI
使用以下方法定義和注入值:
CLI cli = CLI.create(AnnotatedCli.class);
CommandLine commandLine = cli.parse(userCommandLineArguments);
AnnotatedCli instance = new AnnotatedCli();
CLIConfigurator.inject(commandLine, instance);
Vert.x啓動器
vert.x Launcher
在fat jar中被用作主類,並被vertx
命令行工具使用。它執行一組命令,如run,bare,start ...
擴展vert.x啓動器
您可以通過實施您自己的Command
(僅限Java)來擴展命令集:
@Name("my-command")
@Summary("A simple hello command.")
public class MyCommand extends DefaultCommand {
private String name;
@Option(longName = "name", required = true)
public void setName(String n) {
this.name = n;
}
@Override
public void run() throws CLIException {
System.out.println("Hello " + name);
}
}
您還需要實現CommandFactory
:
public class HelloCommandFactory extends DefaultCommandFactory<HelloCommand> {
public HelloCommandFactory() {
super(HelloCommand.class);
}
}
然後,創建src/main/resources/META-INF/services/io.vertx.core.spi.launcher.CommandFactory
並添加一行代表工廠的完全限定名稱:
io.vertx.core.launcher.example.HelloCommandFactory
構建包含命令的jar。一定要包含SPI文件(META-INF/services/io.vertx.core.spi.launcher.CommandFactory
)。
然後,將包含該命令的jar放入fat-jar的類路徑中(或者將其包含在裏面)或者放在lib
vert.x發行版的目錄中,然後你就可以執行:
vertx hello vert.x
java -jar my-fat-jar.jar hello vert.x
在胖罐中使用啓動器
要使用Launcher
類的脂肪罐子只設置Main-Class
了的清單來 io.vertx.core.Launcher
。另外,將Main-Verticle
MANIFEST條目設置爲主Verticle的名稱。
默認情況下,它執行該run
命令。但是,您可以通過設置Main-Command
MANIFEST條目來配置默認命令 。如果fat jar在沒有命令的情況下啓動,則使用默認命令。
對啓動器進行子分類
您也可以創建一個子類Launcher
來啓動您的應用程序。該類被設計爲易於擴展。
一個Launcher
子類可以:
在中定製vert.x配置
beforeStartingVertx
通過覆蓋檢索由“run”或“bare”命令創建的vert.x實例
afterStartingVertx
用
getMainVerticle
and來 配置默認垂直和命令getDefaultCommand
使用
register
和添加/刪除命令unregister
啓動器和退出代碼
當您將該Launcher
類用作主類時,它使用以下退出碼:
0
如果進程順利結束,或者未拋出的錯誤被拋出1
用於一般目的的錯誤11
如果Vert.x無法初始化12
如果產卵過程無法啓動,找到或停止。該錯誤代碼由start
andstop
命令使用14
如果系統配置不符合系統要求(如未找到java,則爲shc)15
如果主垂直不能部署
配置Vert.x緩存
當Vert.x需要從類路徑中讀取一個文件(嵌入在一個fat jar中,jar中形成類路徑或類路徑上的文件)時,它會將其複製到緩存目錄中。這背後的原因很簡單:從jar或從輸入流中讀取文件被阻止。因此,爲了避免每次都付出代價,Vert.x會將文件複製到其緩存目錄,並在每次後續讀取時從該目錄中讀取該文件。這種行爲可以配置。
首先,默認情況下,Vert.x $CWD/.vertx
用作緩存目錄。它在這個內部創建一個獨特的目錄以避免衝突。該位置可以使用vertx.cacheDirBase
系統屬性進行配置。例如,如果當前工作目錄不可寫(例如在不可變容器上下文中),請使用以下命令啓動應用程序:
vertx run my.Verticle -Dvertx.cacheDirBase=/tmp/vertx-cache
# or
java -jar my-fat.jar vertx.cacheDirBase=/tmp/vertx-cache
重要 | 該目錄必須是可寫的。 |
在編輯HTML,CSS或JavaScript等資源時,此緩存機制可能很煩人,因爲它只提供文件的第一個版本(因此,如果重新加載頁面,則不會看到您的編輯)。爲了避免這種行爲,請使用啓動應用程序-Dvertx.disableFileCaching=true
。使用此設置,Vert.x仍然使用緩存,但始終使用原始來源刷新存儲在緩存中的版本。因此,如果您編輯從類路徑提供的文件並刷新瀏覽器,則Vert.x會從類路徑中讀取該文件,並將其複製到緩存目錄並從中提供。不要在生產中使用這個設置,它可以殺死你的表演。
最後,您可以通過使用完全禁用緩存-Dvertx.disableFileCPResolving=true
。這種設置不是沒有後果的。Vert.x將無法從類路徑中讀取任何文件(僅來自文件系統)。使用此設置時要非常小心。