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

WriteStream is implemented by HttpClientRequest, HttpServerResponse WebSocket, NetSocket, AsyncFile, and MessageProducer

Functions:

  • write: write an object to the WriteStream. This method will never block. Writes are queued internally and asynchronously written to the underlying resource.

  • setWriteQueueMaxSize: set the number of object at which the write queue is considered full, and the 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将无法从类路径中读取任何文件(仅来自文件系统)。使用此设置时要非常小心。






猜你喜欢

转载自blog.csdn.net/zyydecsdn/article/details/80110449