vert.x core

前言

最近翻譯了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));

順序組成

allany正在執行的併發組合物,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個操作是鏈接的:

  1. 一個文件被創建(fut1

  2. fut2文件中寫入了一些東西

  3. 該文件被移動(startFuture

當這3個步驟成功時,最後的未來(startFuture)成功了。但是,如果其中一個步驟失敗,那麼最終的未來將失敗。

這個例子使用:

  • compose:當前未來完成時,運行給定的函數,返回未來。當這個返回的未來完成時,它完成組合。

  • compose:當前未來完成時,運行給定的處理程序完成給定的future(下一個)。

在第二種情況下,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。

你可以把一個Verticle想象成一個有點像Actor模型中的actor

應用程序通常由同一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);

此配置可通過Context對象或直接使用該 config方法獲得。

該配置作爲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");

取消定時器

要取消定期計時器,請調用cancelTimer指定計時器ID。例如:

vertx.cancelTimer(timerID);

自動清理Verticle

如果您從垂直內部創建定時器,那麼在解除垂直部署時,這些定時器將自動關閉。

Verticle工作者池

Verticle使用Vert.x工作池來執行阻止操作,即executeBlocking工作者Verticle。

可以在部署選項中指定不同的工作池:

vertx.deployVerticle("the-verticle", new DeploymentOptions().setWorkerPoolName("the-specific-pool"));

事件總線

event busVert.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最好傳遞消息,並且不會有意識地將它們丟棄。這被稱爲盡力而爲的交付。

但是,如果全部或部分事件總線出現故障,則可能會丟失信息。

如果您的應用程序關心丟失的消息,則應該將您的處理程序編碼爲冪等,並且發送者要在恢復後重試。

消息的類型

開箱即用Vert.x允許任何原始/簡單類型,字符串或buffers作爲消息發送。

然而,在Vert.x中以JSON形式發送消息是一種慣例和慣例

JSON非常容易創建,讀取和解析Vert.x支持的所有語言,因此它已成爲Vert.x的一種 通用語言

但是,如果你不想使用JSON,你不會被迫使用JSON。

事件總線非常靈活,並且還支持通過事件總線發送任意對象。您通過codec爲要發送的對象定義一個做到這一點

事件總線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將按照它們從任何特定發件人發送的相同順序將郵件傳遞給任何特定處理程序。

消息對象

您在消息處理程序中收到的對象是a Message

body消息的對應於發送或發佈的對象。

消息的標題可用headers

確認消息/發送回覆

當使用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何時成功處理,因此可以從數據庫中刪除訂單

超時發送

當用回覆處理程序發送消息時,您可以在中指定超時DeliveryOptions

如果在這段時間內沒有收到答覆,答覆處理程序將被調用失敗。

默認的超時時間是30秒。

發送失敗

消息發送可能由於其他原因失敗,包括:

  • 沒有處理程序可用於發送消息

  • 收件人已明確使用該郵件失敗 fail

在所有情況下,答覆處理程序都將被調用,具體失敗。

消息編解碼器

如果您爲事件總線定義並註冊一個對象,則可以在事件總線上發送任何您喜歡的對象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還允許您指定的事件總線是否被聚集,端口和主機,因爲你將與做setClusteredgetClusterHostgetClusterPort

在容器中使用時,您還可以配置公共主機和端口:

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文檔。

然而,在簡單的情況下,無論是mapFrommapTo應該會成功,如果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 appendUnsignedXXXsetUnsignedXXX方法。當爲實現最小化帶寬消耗而優化的網絡協議實現編解碼器時,這非常有用。

在以下示例中,值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

本地和遠程地址

a的本地地址NetSocket可以使用檢索localAddress

a的遠程地址(即連接的另一端的地址)NetSocket 可以使用檢索remoteAddress

從類路徑發送文件或資源

文件和類路徑資源可以直接使用寫入套接字sendFile這可以是發送文件的一種非常有效的方式,因爲它可以由操作系統支持的操作系統內核直接處理。

請參閱關於從類路徑中提供文件的章節,以瞭解路徑解析的限制或禁用它。

socket.sendFile("myfile.dat");

流媒體套接字

的實例NetSocketReadStream WriteStream實例,因此它們可用於數據泵或其它讀取和寫入數據流。

有關更多信息,請參見關於流和泵的章節

升級到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);

建立聯繫

要連接到您使用的服務器connect,請指定服務器的端口和主機,以及將使用包含NetSocketwhen連接成功的結果調用的處理程序, 如果連接失敗,則會失敗。

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是否被使用,並且它是通過配置的啓用NetClientOptionsNetServerOptions用於創建服務器或客戶端的情況。

在服務器上啓用SSL / TLS

SSL / TLS已啓用 ssl

默認情況下它被禁用。

指定服務器的密鑰/證書

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證書助手並給予KeyCertOptionsTrustOptions配置:

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);

密碼套件可以在指定NetServerOptionsNetClientOptions配置。

配置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);

協議版本可以在NetServerOptionsNetClientOptions配置中指定

SSL引擎

引擎實現可以配置爲使用OpenSSL而不是JDK實現。OpenSSL提供比JDK引擎更好的性能和CPU使用率,以及JDK版本獨立性。

要使用的引擎選項是

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 setSnitrue並配置了多個密鑰/證書對服務器。

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支持

要使用的引擎選項是

OpenSSL ALPN支持

OpenSSL提供本地ALPN支持。

OpenSSL需要在類路徑上配置setOpenSslEngineOptions 和使用netty-ticative jar。使用tcnative可能需要將OpenSSL安裝在您的操作系統上,具體取決於具體的實現。

Jetty-ALPN支持

Jetty-ALPN是一個小的jar,它覆蓋了幾個Java 8發行版以支持ALPN。

在JVM必須與啓動alpn啓動- $ {}版本的.jarbootclasspath

-Xbootclasspath / p:/路徑/到/ alpn啓動$ {}版本的.jar

其中$ {}版本依賴於JVM版本,如8.1.7.v20160121OpenJDK的1.8.0u74完整列表在Jetty-ALPN頁面上提供

主要缺點是版本取決於JVM。

爲了解決這個問題,可以使用Jetty ALPN代理代理是一個JVM代理,它將爲運行它的JVM選擇正確的ALPN版本:

-javaagent:/路徑/到/ alpn /劑

使用代理進行客戶端連接

所述NetClient支持A HTTP / 1.x的CONNECTSOCKS4ASOCKS5代理。

可以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在通過應用層協議協商(ALPN)協商的 TLS上使用時識別HTTP / 2協議

  • h2c 通過TCP以明文形式識別HTTP / 2協議時,可以使用HTTP / 1.1升級請求或直接建立此類連接

要處理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。它也將接受h2cPRI * HTTP/2.0\r\nSM\r\n序言開始的直接連接

警告
大多數瀏覽器都不支持h2c,所以對於服務的網站你應該使用h2而不是h2c

當一個服務器接受一個HTTP / 2連接時,它將它發送給客戶端initial settings這些設置定義了客戶端如何使用連接,服務器的默認初始設置爲:

注意
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請求。

處理程序在請求標題已被完全讀取時調用。

如果請求包含正文,那麼在請求處理程序被調用後的一段時間內,該正文將到達服務器。

服務器請求對象允許您檢索的uri pathparams headers,除其他事項。

每個服務器請求對象都與一個服務器響應對象相關聯。您用於 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等)。

請求URI

使用uri檢索請求的URI。

請注意,這是HTTP請求中傳遞的實際URI,它幾乎總是一個相對URI。

請求路徑

使用path返回URI的路徑部分

例如,如果請求URI是:

A / B / C / page.html中?參數1 = ABC&param2的= XYZ

那麼路徑就是

/a/b/c/page.html

請求查詢

使用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"));

請求主機

使用host返回HTTP請求的主機。

對於HTTP / 1.x請求,host將返回標頭,對於HTTP / 1請求:authority返回僞標頭。

請求參數

使用params返回HTTP請求的參數。

就像headers這樣返回一個實例,MultiMap 因爲可以有多個參數具有相同的名稱。

請求參數在路徑後面的請求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());
});

提出請求

請求對象是一個,ReadStream所以你可以將請求主體抽到任何 WriteStream實例。

有關流和泵的章節,請參閱詳細說明。

處理HTML表單

HTML表單可以使用內容類型application/x-www-form-urlencodedmultipart/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可以處理由客戶端使用deflategzip 算法編碼的壓縮主體有效載荷

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幀不受流控制的限制 - 當接收到自定義幀時,幀處理程序將立即被調用,無論請求是暫停還是不

非標準的HTTP方法

OTHERHTTP方法用於非標準方法,在這種情況下 rawMethod返回由客戶端發送的HTTP方法。

發回回覆

服務器響應對象是一個實例,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,例如AsyncFileNetSocket WebSocketHttpServerRequest

下面是一個響應任何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 handlerand 來通知請求處理程序的流重置事件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-encodingidentity

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");

編寫請求組織

有時候你會想要寫有請求的主體,或者你想在發送請求之前先寫請求頭。

要做到這一點,你可以調用其中一種特定的請求方法,例如post或其中一種通用請求方法request

這些方法不會立即發送請求,而是返回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方法

OTHERHTTP方法用於非標準方法中,當使用該方法時,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();

請求超時

您可以使用特定的http請求設置超時setTimeout

如果請求在超時期限內沒有返回任何數據,則會將異常傳遞給異常處理程序(如果提供)並且請求將被關閉。

處理異常

您可以通過在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 handlerand 來通知請求處理程序的流重置事件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得到的頭。

返回的對象是一個MultiMapHTTP標頭可以包含單個鍵的多個值。

String contentType = response.headers().get("content-type");
String contentLength = response.headers().get("content-lengh");

分塊的HTTP響應也可以包含預告片 - 這些都是在響應主體的最後一個塊中發送的。

trailers用來獲得預告片。拖車也是MultiMap

閱讀請求正文

響應處理程序在從線路讀取響應頭時調用。

如果響應有一個主體,那麼在標題被讀取一段時間後,這個主體可能會分成幾部分。在調用響應處理程序之前,我們不會等待所有主體到達,因爲響應可能非常大,而且我們可能會等待很長時間,或者內存不足以應對大量響應。

當響應體的一部分到達時,handler用一個Buffer代表身體的部分來調用

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在整個響應體被讀取後立即調用,或者在讀取頭之後立即調用,如果沒有主體,則調用響應處理程序。

從響應中讀取cookie

您可以使用一個響應來檢索cookie的列表cookies

或者,您可以Set-Cookie在響應中自行解析報頭。

30x重定向處理

客戶端可以被配置爲遵循HTTP重定向:當客戶端收到一 301302303307狀態代碼,它遵循由提供重定向Location響應報頭和響應處理器被傳遞重定向響應,而不是原來的響應。

這是一個例子:

client.get("some-uri", response -> {
  System.out.println("Received response with status code " + response.statusCode());
}).setFollowRedirects(true).end();

重定向策略如下

  • 301302303狀態代碼,請與重定向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-Encodingheader 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完全實現了HttpConnectionAPI。

HTTP / 1.x部分實現了HttpConnectionAPI:只實現了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可用於確定連接往返時間或檢查連接有效性:pingPING遠程端點發送幀:

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協議

連接關閉

連接close關閉連接:

  • 它關閉了HTTP / 1.x的套接字

  • 在HTTP / 2沒有延遲的情況下關閉,GOAWAY在連接關閉之前幀仍將被髮送。*

closeHandler通知時,連接被關閉。

HttpClient用法

HttpClient可以用於Verticle或嵌入。

在Verticle中使用時,Verticle 應該使用自己的客戶端實例

更一般地說,客戶端不應該在不同的Vert.x上下文之間共享,因爲它可能導致意外的行爲。

例如,保持連接將在打開連接的請求的上下文中調用客戶端處理程序,隨後的請求將使用相同的上下文。

發生這種情況時,Vert.x檢測到並記錄警告:

重用與不同上下文的連接:HttpClient可能在不同的Verticles之間共享

HttpClient可以像單元測試或普通java一樣嵌入到非Vert.x線程中main:客戶端處理程序將由不同的Vert.x線程和上下文調用,此類上下文根據需要創建。對於生產這種用法不建議。

服務器共享

當多個HTTP服務器偵聽同一端口時,vert.x使用循環策略編排請求處理。

我們來創建一個HTTP服務器,例如:

io.vertx.examples.http.sharing.HttpServerVerticle
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/TLS

  • setting 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 read

  • handler: 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 flushmethod.

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有以下幾種方法:

  • sender:代表數據包發送者的InetSocketAddress

  • data:保存接收到的數據的緩衝器。

因此,聽一個特定的地址和端口,你會做這樣的事情:

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():代表數據包發送者的InetSocketAddress

  • data():保存接收到的數據的緩衝器。

因此,要監聽特定地址和端口,並且還要接收多播組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狀態下的地址可以在關閉後重新使用。

  • setTrafficClass

  • setBroadcast設置或清除SO_BROADCAST套接字選項。當設置此選項時,數據報(UDP)數據包可能會發送到本地接口的廣播地址。

  • setMulticastNetworkInterface設置或清除IP_MULTICAST_LOOP套接字選項。當該選項設置時,組播數據包也將在本地接口上接收。

  • setMulticastTimeToLive設置IP_MULTICAST_TTL套接字選項。TTL代表“生存時間”,但在此情況下,它指定允許數據包通過的IP跳數,特別是組播流量。每個轉發數據包的路由器或網關都會減少TTL。如果TTL由路由器遞減到0,它將不會被轉發。

DatagramSocket本地地址

您可以通過調用找到套接字的本地地址(即UDP套接字的這一端的地址) localAddress這隻會返回一個InetSocketAddress,如果你綁定的DatagramSocketlisten(…​)之前,否則將返回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();

這與更詳細的例子完全相同。

現在讓我們來看看在方法上ReadStreamWriteStream的詳細信息:

ReadStream

功能:

  • 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

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 method writeQueueFull returns true. 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, for Buffer the size represents the actual number of bytes written and not the number of buffers.

  • writeQueueFull: returns true if the write queue is considered full.

  • exceptionHandler: Will be called if an exception occurs on the WriteStream.

  • drainHandler: The handler will be called if the WriteStream is considered no longer full.

Pump

Instances of Pump have the following methods:

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課程。

線程安全

大多數Vert.x對象可以安全地從不同的線程訪問。但是,如果從創建它們的相同上下文訪問它們,性能會得到優化。

例如,如果您已經部署了創建在其處理程序中NetServer提供 NetSocket實例的Verticle,那麼最好始終從Verticle的事件循環訪問該套接字實例。

如果您堅持使用標準的Vert.x垂直部署模型,並避免在垂直間共享對象,那麼您就不必考慮它了。

指標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)
  1. 部署JavaScript Verticle

  2. 部署Groovy Verticle

  3. 部署Ruby垂直

  4. 部署已編譯的Java Verticle。Classpath根目錄是當前目錄

  5. 部署一個包裝在Jar中的Verticle,該jar需要位於類路徑中

  6. 編譯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-hostcluster-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也提供其它的命令除了runversion

您可以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

startstoplist命令也可以從vertx工具。start`命令支持一些選項:

  • vertx-id :應用程序ID,如果未設置,則使用隨機UUID

  • java-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.Launcherrun 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-optsparam:

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 :文件系統檢查週期(以毫秒爲單位),默認爲250ms

  • redeploy-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並且想要使用羣集,則應確保libVert.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.Log4jLogDelegateFactoryio.vertx.core.logging.Log4j2LogDelegateFactoryio.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服務器選擇使用第一個,其餘服務器用於故障轉移。

您可以配置setRotateServerstrue讓解析器執行循環法選擇。它將查詢負載分散到服務器中,並避免所有查找命中列表中的第一個服務器。

故障轉移仍然適用,並將使用列表中的下一臺服務器。

主機映射

操作系統主機文件用於執行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-hostcluster-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個步驟的過程:

  1. 命令行界面的定義

  2. 用戶命令行的解析

  3. 查詢/詢問

定義階段

每個命令行界面都必須定義將要使用的一組選項和參數。它也需要一個名字。CLI API使用OptionArgument類來描述選項和參數:

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可以:

參數也可以從他們的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());
}

鍵入的選項和參數

描述的OptionArgument類是無類型的,這意味着只有String值。

TypedOptionTypedArgument讓您指定一個類型,以便(String)原始值轉換爲指定的類型。

取而代之的 OptionArgument,使用TypedOption TypedArgumentCLI:定義

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參數的構造 函數,如FileorJsonObject

  • 用靜態fromfromString方法

  • 使用靜態valueOf方法,如基元類型和枚舉

另外,您可以實現自己的功能Converter並指示CLI使用此轉換器:

CLI cli = CLI.create("some-name")
    .addOption(new TypedOption<Person>()
        .setType(Person.class)
        .setConverter(new PersonConverter())
        .setLongName("person"));

對於布爾值,該布爾值進行評估,以trueonyes1true

如果您的某個選項具有某種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 Launcherfat jar中被用作主類,並被vertx命令行工具使用。它執行一組命令,如runbarestart ...

擴展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子類可以:

啓動器和退出代碼

當您將該Launcher類用作主類時,它使用以下退出碼:

  • 0 如果進程順利結束,或者未拋出的錯誤被拋出

  • 1 用於一般目的的錯誤

  • 11 如果Vert.x無法初始化

  • 12如果產卵過程無法啓動,找到或停止。該錯誤代碼由startand stop命令使用

  • 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將無法從類路徑中讀取任何文件(僅來自文件系統)。使用此設置時要非常小心。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章