编写 HTTP 服务器

创建一个 HTTP 服务器

最简单的方法来创建一个 HTTP 服务器,所有选项使用默认的。如下所示:

  1. HttpServer server = vertx.createHttpServer();

配置 HTTP 服务器

如果你不想使用默认值,创建服务器时可以通过传入一个HttpServerOptions实例配置:

  1. HttpServerOptions options = new HttpServerOptions().setMaxWebsocketFrameSize(1000000);
  2. HttpServer server = vertx.createHttpServer(options);

启动服务器监听

使用listen告诉服务器以监听传入的请求。

在选项中指定的主机和端口:

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

或在调用listen中指定的主机和端口,忽略配置选项:

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

默认主机是0.0.0.0, ‘监听所有可用的地址’ ,默认端口是80.

实际的绑定是异步的,所以服务器可能不会实际被监听,直到一段时间后,调用返回。

如果想要listen实际监听后通知你,可以提供listen调用处理程序。例如:

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

收到传入请求通知

若要请求到达时通知,需要设置requestHandler:

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

处理请求

当请求到达时,则该请求调用处理程序传递HttpServerRequest的一个实例。此对象所表示的服务器端的 HTTP 请求。

当请求headers已完全读取时,将调用该处理程序。

如果该请求包含一个body,该body将到达服务器,一段时间后请求处理程序被调用。

服务器请求对象可以获取uripathparamsheaders等。

每个服务器请求对象是与一台服务器的响应对象相关联。使用response来获取对HttpServerResponse对象。

这里是一个简单的例子,服务器处理请求和回复”hello world”。

  1. vertx.createHttpServer().requestHandler(request -> {
  2. request.response().end("Hello world");
  3. }).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?param1=abc&param2=xyz

那么,路径会

/a/b/c/page.html

请求查询

使用query返回的 URI 的查询部分

例如,如果请求 URI 只是:

a/b/c/page.html?param1=abc&param2=xyz

那么,该查询会

param1=abc&param2=xyz

请求headers

使用headers方法来返回的 HTTP 请求headers。

这将返回一个实例MultiMap-就像一个普通的map或Hash,但允许多个值的同一键-这是因为 HTTP 允许多个header值用相同的密钥。

key 不区分大小写,这就意味着您可以执行以下操作:

  1. MultiMap headers = request.headers();
  2. // Get the User-Agent:
  3. System.out.println("User agent is " + headers.get("user-agent"));
  4. // You can also do this and get the same result:
  5. System.out.println("User agent is " + headers.get("User-Agent"));
请求参数

使用params返回的 HTTP 请求参数。

就像headers这返回的MultiMap实例,可以有多个参数具有相同的名称。

路径后的请求 URI是请求参数。例如,如果 URI:

  1. /page.html?param1=abc&param2=xyz

然后参数将包含以下内容:

  1. param1: 'abc'
  2. param2: 'xyz

请注意这些请求参数从请求的 URL。如果您有已作为体内的multi-part/form-data请求提交 HTML 表单提交的一部分发送的窗体属性然后他们将不出现在这里的 params。

请注意,这些请求参数从请求的URL中获取。如果你的form属性为multi-part/form-data请求的话,参数不会出现在这里,会包含在body中提交发送。

远程地址

请求的发送者的地址可以通过remoteaddress获取。

绝对 URI

传入的 HTTP 请求的 URI 是通常相对。如果想要检索对应于该请求的绝对 URI,可以用absoluteURI

结束处理程序

当整个,包括任何body已完全读取请求时,将调用endHandler请求。

从请求正body读取数据

通常一个 HTTP 请求包含我们想要读取的body。前面所提到的请求处理程序仅仅有headers,这里并没有body。

这是因为body可能会非常大 (例如一个文件上传),我们通常不不会吧缓冲的整个body放在内存中交给你,因为那将导致服务器内存用尽。

若要接收body,您可以使用handler请求,调用后,会有请求body到达。下面是一个示例:

  1. request.handler(buffer -> {
  2. System.out.println("I have received a chunk of the body of length " + buffer.length());
  3. });

传递到handler的对象是一个Buffer,该handler可以调用多次,当数据到达时从网络,根据body的大小。

在某些情况下 (例如如果body很小) 想要在内存中缓存整个body如下所示:

  1. Buffer totalBuffer = Buffer.buffer();
  2. request.handler(buffer -> {
  3. System.out.println("I have received a chunk of the body of length " + buffer.length());
  4. totalBuffer.appendBuffer(buffer);
  5. });
  6. request.endHandler(v -> {
  7. System.out.println("Full body received, length = " + totalBuffer.length());
  8. });

这是这种常见的情况,Vert.x 提供bodyHandler来为你做这个。bodyHandler收到所有的body:

  1. request.bodyHandler(totalBuffer -> {
  2. System.out.println("Full body received, length = " + totalBuffer.length());
  3. });
Pumping requests

请求对象是ReadStream ,所以你可以pump请求body到任何WriteStream实例。

处理 HTML 表单

HTML表单可以提交application/x-www-form-urlencodedmultipart/form-data内容类型。

对于 url 编码形式,像正常的查询参数一样,把表单属性编码在 url 中。

multi-part表单在请求body中编码,因此不可用直到从wire读取了整个body。

Multi-part 表单还可以包含文件上传。

如果您想要获取的属性是multi-part的形式,得到这种表单之前,通过调用setExpectMultipart 设置为true,告诉 Vert.x 你期望的body是read,,然后你应该使用formAttributes获取 ,读取所有body的属性:

  1. server.requestHandler(request -> {
  2. request.setExpectMultipart(true);
  3. request.endHandler(v -> {
  4. // The body has now been fully read, so retrieve the form attributes
  5. MultiMap formAttributes = request.formAttributes();
  6. });
  7. });
处理表单文件上传

Vert.x 还可以处理在multi-part请求body中编码文件上传。

接收文件上传,告诉 Vert.x 期望multi-part表单形式并要求设置uploadHandler

每个上传到服务器上,此handler将调用一次。

传递到handler的对象是一个HttpServerFileUpload实例。

  1. server.requestHandler(request -> {
  2. request.setExpectMultipart(true);
  3. request.uploadHandler(upload -> {
  4. System.out.println("Got a file upload " + upload.name());
  5. });
  6. });

上传的文件可能会很大,我们不提供单个缓冲区上传整个数据,因为这可能导致内存消耗殆尽,取而代之的是,上传的数据在块中收到:

  1. request.uploadHandler(upload -> {
  2. upload.handler(chunk -> {
  3. System.out.println("Received a chunk of the upload of length " + chunk.length());
  4. });
  5. });

上传对象是ReadStream ,所以你可以pump请求body到任何WriteStream实例。

如果你只是想要上传文件到磁盘,你可以使用streamToFileSystem:

  1. request.uploadHandler(upload -> {
  2. upload.streamToFileSystem("myuploads_directory/" + upload.filename());
  3. });

警告
确保您在一个生产系统中检查文件,以避免恶意客户端将文件上传到您的文件系统。查看更多信息的安全说明

发送返回响应

服务器的响应对象是HttpServerResponse的实例和所得的请求response.

响应对象用于写回 HTTP 客户端的响应。

设置状态代码和消息

response 的默认 HTTP 状态码是200,表示OK.

使用setStatusCode来设置不同的代码。

您还可以使用setStatusMessage指定一个自定义的状态消息.

如果您不指定一条状态消息,将使用默认的状态代码。

写入 HTTP responses(响应)

使用一个write操作,将数据写入 HTTP responses。

这些可以在响应结束之前多次调用。可以以下面几种方法调用:

用一个缓冲区:

  1. HttpServerResponse response = request.response();
  2. response.write(buffer);

用一个字符串。在这种情况下将默认使用UTF-8编码。

  1. HttpServerResponse response = request.response();
  2. response.write("hello world!");

使用一个字符串和编码。在本例的字符串将使用指定的编码。

  1. HttpServerResponse response = request.response();
  2. response.write("hello world!", "UTF-16");

写入responses是异步的,在写入队列后立即返回。

如果你只是写一个字符串或缓存HTTP response,你可以写它和调用end结束response

在响应头第一个调用正在被写入结果的响应
第一次调用将写入结果正在被写入到响应响应头。因此,如果您不使用 HTTP 分块然后之前,必须设置Content-Length标头写的反应,因为它否则就太晚了。如果您使用的 HTTP 分块你不必担心。

第一次调用写结果的响应头被写入响应。因此,如果你不使用HTTP分块,那么header,你必须设置响应的Content-Length。如果您使用HTTP分块你不必担心。

结束 HTTP responses

一旦你完成了 HTTP responses你应该end它。

这可以有以下几种方式:

不带任何参数,response 是只是结束。

  1. HttpServerResponse response = request.response();
  2. response.write("hello world!");
  3. response.end();

它也可以用字符串调用或和缓冲区相同的方式 write。在这种情况下,首先用字符串写入缓冲区,然后和不带参数的一样。例如:

  1. HttpServerResponse response = request.response();
  2. response.end("hello world!");
关闭底层连接

使用 close 关闭底层的 TCP 连接.

短连接在 response 结束时, Vert.x 将自动关闭 。

长连接的 Vert.x 默认情况下是不会自动的关闭。如果你希望连接保持到空闲时间后关闭,使用 setIdleTimeout 方法配置。

设置 response headers

可以将 HTTP response headers 通过 response 直接添加到 headers:

  1. HttpServerResponse response = request.response();
  2. MultiMap headers = response.headers();
  3. headers.set("content-type", "text/html");
  4. headers.set("other-header", "wibble");

或者你可以使用putHeader

  1. HttpServerResponse response = request.response();
  2. response.putHeader("content-type", "text/html").putHeader("other-header", "wibble");

Headers 必须添加在所有写入response body之前。

分块 HTTP 响应和传输

Vert.x 支持HTTP 分块传输编码.

表示输出的内容长度不能确定。

把 HTTP response 设置为分块模式,如下所示:

  1. HttpServerResponse response = request.response();
  2. response.setChunked(true);

默认值为非分块。在分块模式下,每次调用 write 调用时,就会写出一个新的 HTTP 块。

当在分块模式中你也可以写入 HTTP response 传输到response。实际上,这些实际上写入响应的最后块。

若要添加trailers到response,直接添加到trailers.

  1. HttpServerResponse response = request.response();
  2. response.setChunked(true);
  3. MultiMap trailers = response.trailers();
  4. trailers.set("X-wibble", "woobble").set("X-quux", "flooble");

或使用putTrailer.

  1. HttpServerResponse response = request.response();
  2. response.setChunked(true);
  3. response.putTrailer("X-wibble", "woobble").putTrailer("X-quux", "flooble");
直接从磁盘或类路径(classpath)提供文件服务

如果你正在编写一个Web服务器,使用AsyncFile打开磁盘上的文件并注入到HTTP response。

或者你可以一气呵成,使用readFile加载并直接写入响应。

另外,Vert.x 提供了一种方法,可以从磁盘或者文件系统操作HTTP response。由底层操作系统支持,这可能在OS中直接从文件到套接字传送字节,而无需通过用户空间(user-space)复制。

对于大文件使用sendFile,通常更有效,但小文件可能较慢。

下面是使用sendFile,提供了一个非常简单的Web服务器:

  1. vertx.createHttpServer().requestHandler(request -> {
  2. String file = "";
  3. if (request.path().equals("/")) {
  4. file = "index.html";
  5. } else if (!request.path().contains("..")) {
  6. file = request.path();
  7. }
  8. request.response().sendFile("web/" + file);
  9. }).listen(8080);

发送的文件是异步的,可能无法马上返回。如果你想要该文件写入完成通知,可以使用sendFile

注意

如果你在使用的 HTTPS 使用sendFile会通过用户空间 ,因为如果内核将数据直接从磁盘对套接字,没有机会给应用任何加密。

警告

如果你要直接使用 Vert.x 写 web 服务器,小心用户访问其他文件路径,使用 Vert.x Web可能更安全,。

当只需要一个文件片段,可以给定从某字节开发,可以通过下面达到:

  1. vertx.createHttpServer().requestHandler(request -> {
  2. long offset = 0;
  3. try {
  4. offset = Long.parseLong(request.getParam("start"));
  5. } catch (NumberFormatException e) {
  6. // error handling...
  7. }
  8. long end = Long.MAX_VALUE;
  9. try {
  10. end = Long.parseLong(request.getParam("end"));
  11. } catch (NumberFormatException e) {
  12. // error handling...
  13. }
  14. request.response().sendFile("web/mybigfile.txt", offset, end);
  15. }).listen(8080);

如果想要发送的文件从偏移量开始到结束,您不需要提供长度,在这种情况下你可以这么做:

  1. vertx.createHttpServer().requestHandler(request -> {
  2. long offset = 0;
  3. try {
  4. offset = Long.parseLong(request.getParam("start"));
  5. } catch (NumberFormatException e) {
  6. // error handling...
  7. }
  8. request.response().sendFile("web/mybigfile.txt", offset);
  9. }).listen(8080);
注入responses

服务器response是一个WriteStream实例,可以从任何ReadStream注入,例如AsyncFileNetSocketWebSocketHttpServerRequest

这是一个PUT请求body返回响应的例子,使用pump,即使HTTP请求body比内存大的多,也能正常工作:

  1. vertx.createHttpServer().requestHandler(request -> {
  2. HttpServerResponse response = request.response();
  3. if (request.method() == HttpMethod.PUT) {
  4. response.setChunked(true);
  5. Pump.pump(request, response).start();
  6. request.endHandler(v -> response.end());
  7. } else {
  8. response.setStatusCode(400).end();
  9. }
  10. }).listen(8080);

HTTP 压缩

Vert.x 带有 HTTP 压缩开箱即用支持。

这意味着在发送回客户端之前能自动压缩响应的主体。

如果客户端不支持 HTTP 压缩,将没有压缩 body 响应发送回。

这可以同时处理支持 HTTP 压缩的客户端和那些不支持HTTP 压缩的客户端。

若要启用压缩,使用setCompressionSupported配置。

默认情况下不启用压缩。

当启用 HTTP 压缩,服务器将检查客户端是否包括Accept-Encoding的报头,它包含支持的压缩方式。常用的有deflate和 gzip(Web服务器处理HTTP压缩之gzip、deflate压缩)。 Vert.x 两者都支持。

如果服务器将自动压缩与一个支持压缩响应正文中的并将其发送回客户端,就找到这种头。
如果找到这样的报头,服务器将自动压缩发送回客户端。

要知道压缩可能能够减少网络流量,但是是需要消耗更多的 CPU