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中


  io.vertx
  vertx-core
  3.5.1

如果你用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 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 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版本独立性。

要使用的引擎选项是

  • getSslEngineOptions设置时选项

  • 除此以外 JdkSSLEngineOptions

NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setKeyStoreOptions(keyStoreOptions);

// Use JDK SSL engine explicitly
options = new NetServerOptions().
  setSsl(true).
  setKeyStoreOptions(keyStoreOptions).
  setJdkSslEngineOptions(new JdkSSLEngineOptions());

// Use OpenSSL engine
options = new NetServerOptions().
  setSsl(true).
  setKeyStoreOptions(keyStoreOptions).
  setOpenSslEngineOptions(new OpenSSLEngineOptions());

服务器名称指示(SNI)

服务器名称指示(SNI)是一种TLS扩展,客户端通过该扩展指定尝试连接的主机名:在TLS握手过程中,客户端提供服务器名称,服务器可以使用该名称响应此服务器名称的特定证书,而不是默认部署证书。如果服务器需要客户端身份验证,则服务器可以根据指定的服务器名称使用特定的可信CA证书。

当SNI处于活动状态时,服务器使用

  • 证书CN或SAN DNS(使用DNS的主题备用名称)进行完全匹配,例如 www.example.com

  • 证书CN或SAN DNS证书匹配通配符名称,例如 *.example.com

  • 否则当客户端不提供服务器名称或提供的服务器名称时,第一个证书不能匹配

当服务器另外需要客户端认证时:

  • 如果JksOptions用于设置信任选项(options),则完成与信任存储区别名的完全匹配

  • 否则可用的CA证书以与没有SNI的情况相同的方式使用

您可以通过设置服务器上启用SNI 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支持

要使用的引擎选项是

  • getSslEngineOptions设置时选项

  • JdkSSLEngineOptions 当ALPN可用于JDK时

  • OpenSSLEngineOptions 当ALPN可用于OpenSSL时

  • 否则失败

OpenSSL ALPN支持

OpenSSL提供本地ALPN支持。

OpenSSL需要在类路径上配置setOpenSslEngineOptions 和使用netty-ticative jar。使用tcnative可能需要将OpenSSL安装在您的操作系统上,具体取决于具体的实现。

Jetty-ALPN支持

Jetty-ALPN是一个小的jar,它覆盖了几个Java 8发行版以支持ALPN。

在JVM必须与启动alpn启动- $ {}版本的.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这些设置定义了客户端如何使用连接,服务器的默认初始设置为:

  • getMaxConcurrentStreams100按照HTTP / 2 RFC的建议

  • 其他人的默认HTTP / 2设置值

注意
Worker Verticles与HTTP / 2不兼容

记录网络服务器活动

出于调试目的,可以记录网络活动。

HttpServerOptions options = new HttpServerOptions().setLogActivity(true);

HttpServer server = vertx.createHttpServer(options);

有关详细说明,请参阅记录网络活动

启动服务器侦听

要告诉服务器侦听传入请求,请使用其中一种listen 替代方法。

要让服务器按照以下选项中指定的方式监听主机和端口:

HttpServer server = vertx.createHttpServer();
server.listen();

或者要指定要监听的呼叫中的主机和端口,则忽略选项中配置的内容:

HttpServer server = vertx.createHttpServer();
server.listen(8080, "myhost.com");

默认主机是0.0.0.0指“监听所有可用地址”,默认端口是80

实际的绑定是异步的,所以服务器可能实际上并没有收听,直到调用listen的一段时间后才返回。

如果您希望在服务器实际正在侦听时收到通知,则可以为listen呼叫提供处理程序例如:

HttpServer server = vertx.createHttpServer();
server.listen(8080, "myhost.com", res -> {
  if (res.succeeded()) {
    System.out.println("Server is now listening!");
  } else {
    System.out.println("Failed to bind!");
  }
});

获得传入请求的通知

要在收到请求时收到通知,您需要设置requestHandler

HttpServer server = vertx.createHttpServer();
server.requestHandler(request -> {
  // Handle the request in here
});

处理请求

当请求到达时,请求处理程序被称为在一个实例中传递HttpServerRequest该对象表示服务器端HTTP请求。

处理程序在请求标题已被完全读取时调用。

如果请求包含正文,那么在请求处理程序被调用后的一段时间内,该正文将到达服务器。

服务器请求对象允许您检索的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。

URI的定义见HTTP规范的第5.1.2节 - 请求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("");

当服务器准备推送响应时,将调用推送响应处理程序并且处理程序可以发送响应。

推送响应处理程序可能会收到失败,例如,客户端可能会取消推送,因为它已经存在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

  • 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.

你可能感兴趣的:(vertx)