HttpServer初赦

Vert.x内置了HTTP服务器,而且封装的是纯异步Java服务器Netty,那么读者也许会有一个疑问:HttpServer的实例究竟是什么时候创建的,如果只是纯粹地编写,在哪里实例化都不影响,而本章就尝试把这些内容讲透,让大家对Vert.x中的HttpServer有一个更加深入的理解;官方的vertx-web项目中,创建该实例的位置是在一个Verticle中,那么我们做个简单的探索,看是不是只有在那里才能创建它。

从进入Web的章节开始,我们将使用Vert.x的Web子项目,这是我们引入的第二个角色:Vertx-Web;它需要您在Maven项目的pom文件中引入下边的配置:

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-web</artifactId>
  <version>3.7.0</version>
</dependency>

若您使用的是Gradle,则需要在您的build.gradle中引入:

dependencies {
  compile 'io.vertx:vertx-web:3.7.0'
}

1. 启动

在您的主函数中写入下边代码:

package io.vertx.up._02.http;

import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServer;

public class DirectServer {

    public static void main(final String[] args) {
        final Vertx vertx = Vertx.vertx();
        final HttpServer server = vertx.createHttpServer();
        server.listen(8099);
    }
}

Exception」运行的时候您会遇到下边的错误,这是本人无意间踩到的一个坑:

Exception in thread "main" java.lang.IllegalStateException: Set request or websocket handler first
    at io.vertx.core.http.impl.HttpServerImpl.listen(HttpServerImpl.java:221)
    at io.vertx.core.http.impl.HttpServerImpl.listen(HttpServerImpl.java:211)
    at io.vertx.up._02.http.DirectServer.main(DirectServer.java:11)

异常的意思很清楚,那么来个完整版:

package io.vertx.up._02.http;

import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServer;

public class DirectServer {

    public static void main(final String[] args) {
        final Vertx vertx = Vertx.vertx();
        final HttpServer server = vertx.createHttpServer();
        server.requestHandler(handler -> {
            System.out.println(Thread.currentThread().getName());
            handler.response()
                    .putHeader("content-type", "text/plain")
                    .end("Hello Direct Server!");
        });
        server.listen(8099);
    }
}

当您在浏览器中输入:http://localhost:8099,您将会看到如下截图:

后台的Console中依旧是两行,为什么打印两次在前文中说过了,这里不重复。

vert.x-eventloop-thread-1
vert.x-eventloop-thread-1

实际上结合Vert.x的官方教程,可以得到一个结论:

在哪里创建HttpServer可随意,只要您可以拿到一个Vert.x实例的引用,您就可以调用它的createHttpServer()来创建对应的HttpServer的实例,而在Vert.x中,创建和启动一个HttpServer的实例往往是在一个Vertcile里面进行,为什么呢?

是的,在我个人看来,实际上是没有影响的,但从Vert.x的本质上看,可以把这个问题当做一个趣味性思考。回到原始的话题,Vert.x是一个工具集,它本质的设计和Restful无关,和Web服务无关,和HttpServer更加没有直接的联系,应该这样描述:“这个工具集具备了作为Http服务器的功能”,从这种意义上说,在Verticle中创建HttpServer的实例就是理所当然了,也可以说使用Verticle是打开Vert.x工具集的正确方式,所以回头看上边的代码,很幼稚,幼稚得连Vert.x的入门代码都谈不上。对比下边两幅图来思考一下Vert.x的作者的原始意图:

上边代码是左边这种方式,而官方教程中创建HttpServer的实例则是右边这种方式,虽然在Verticle实例中创建HttpServer同样要调用createHttpServer()的API,但从某种意义上讲,右边的方式更加优雅,这里列举我个人的思考点做参考,既然是一个趣味性话题,则本质上没有对错之分。

如果直接在main函数中通过Vert.x创建HttpServer实例,本质上跳过了Verticle实例的创建,也就是说你在用一种没有Verticle的方式使用Vert.xVerticle实例的本意就是为了让所有的东西能够平行扩展,并且组件足够小到可以实现“单一职责”。从上边的右图可以知道,一个Verticle实例本质上只做了一件事,要么创建HttpServer,要么创建Rpc Server或者其他,而Vert.x提供了对Vertcile实例的很好的管理方式,比如可部署、也可撤销、同样支持热插拔,既然有了这样的一种管理模式,直接在main函数中创建似乎就显得多余了,不仅仅如此,自己管理一些配置以及相关数据时不得不说是噩梦(我试过,主函数很重,代码很多)。所以,注意Vert.x中的HttpServer的正确打开方式:老老实实按照官方的教程在一个Vertcle中去创建,而不是直接在主函数中创建。

2. 配置初探

很多使用过Vert.x中的HttpServer的小伙伴都知道官方有一段很简化的创建HttpServer的代码:

HttpServer server = vertx.createHttpServer();

server.requestHandler(request -> {

  // 设置针对每个请求的Handler
  HttpServerResponse response = request.response();
  response.putHeader("content-type", "text/plain");

  // 生成请求数据
  response.end("Hello World!");
});

server.listen(8080);

实际上,这段代码中隐藏了一个开发人员可能比较关心的东西:io.vertx.core.http.HttpServerOptions,本章节主要针对这个类解析说明(不过我只告诉读者我所理解的部分)。

2.1. createHttpServer

Vert.x的源代码中,我们可以看到createHttpServer的方法签名如下:

    io.vertx.core.http.HttpServer createHttpServer(io.vertx.core.http.HttpServerOptions httpServerOptions);

    io.vertx.core.http.HttpServer createHttpServer();

从上述方法签名可以知道创建HttpServer的实例有两个核心API,一个是带参数(将是本章解析的内容),一个不带参数(官方例子),细心的读者会发现带此参数的API就会传入本章节需要解析的类io.vertx.core.http.HttpServerOptions

2.2. listen

直接查看Vert.x中的listen方法源代码,该方法的方法签名如下:

    // 无参同异步方法    
    @io.vertx.codegen.annotations.Fluent
    io.vertx.core.http.HttpServer listen();

    @io.vertx.codegen.annotations.Fluent
    io.vertx.core.http.HttpServer listen(
        io.vertx.core.Handler<io.vertx.core.AsyncResult<io.vertx.core.http.HttpServer>> handler);

    // 双参同异步方法 i - port(端口号)、s - host(Host地址)
    @io.vertx.codegen.annotations.Fluent
    io.vertx.core.http.HttpServer listen(int i, java.lang.String s);

    @io.vertx.codegen.annotations.Fluent
    io.vertx.core.http.HttpServer listen(int i, java.lang.String s, 
        io.vertx.core.Handler<io.vertx.core.AsyncResult<io.vertx.core.http.HttpServer>> handler);

    // 单参同异步方法 i - port(端口号)
    @io.vertx.codegen.annotations.Fluent
    io.vertx.core.http.HttpServer listen(int i);

    @io.vertx.codegen.annotations.Fluent
    io.vertx.core.http.HttpServer listen(int i, 
        io.vertx.core.Handler<io.vertx.core.AsyncResult<io.vertx.core.http.HttpServer>> handler);

关于AsyncResult的用法,后边会有相关说明。上述方法签名列表中有几点需要指出:

  • HttpServerOptions有自己的默认实现,这里主要针对listen的两个参数说明:
    • DEFAULT_PORT:在不设置端口号的时候,HttpServer使用的默认端口号是80:Default port the server will listen on = 80
    • DEFAULT_HOST:在不设置Host地址时,默认值是0.0.0.0,但是该配置的值不是在HttpServerOptions中定义,而是在它的父类NetServerOptions中定义的:The default host to listen on = "0.0.0.0" ( meaning listen on all available interfaces )
  • 「不设置HttpServerOptions」:如果创建HttpServer时不传入HttpServerOptions,则直接使用系统默认的HttpServerOptions中的定义,但是方法优先,在调用listen时传入了port或host则以listen传入参数中的值为主。
  • 「设置HttpServerOptions」:如果创建HttpServer时传入HttpServerOptions,则直接使用传入的HttpServerOptions中的配置,同样是方法优先,调用listen时传入了port或host则还是以传入参数值为主。

整体的配置优先级可归纳为:

HttpServerOptions(默认) <  HttpServerOptions(createHttpServer传入) <  listen ( port / host Only )

实际上listen方法只能设置host和port两个配置信息,HttpServerOptions其他配置信息还是要通过createHttpServer的方法来传入,而方法createHttpServer(HttpServerOptions)是开发人员在“编程方式“中唯一进行自定义配置的位置(使用命令行方式启动Vertx实例除外)。

Exception」在不提供port和host直接启动Vert.x的过程中,您也许会遇到下边错误:

Sep 11, 2018 7:44:41 AM io.vertx.core.http.impl.HttpServerImpl
SEVERE: java.net.SocketException: Permission denied

出现上述信息过后,证明HttpServer实际上是没有启动成功的,在Unix体系的操作系统中,使用非root账户设置的Web Server的端口号不可低于1024(不包含1024),而Vertx创建的HttpServer实例的默认端口号为80,所以这种情况下回看到上述错误信息,解决上述异常的办法很多,这里介绍几种解决办法:

  • 将您的账号权限提升,使用root账号启动该程序。
  • 调用listen方法时传入大于或等于1024的端口号。
  • 为应用程序单独设置用户的ID使它具有root权限,这个方法可以使得程序像root用户一样执行,不过有可能会带来安全上的风险(使用chown/chmod命令)。
chown root.root <您的应用程序路径>
chmod u+s <您的应用程序路径>
  • 关闭selinux,不推荐,并且该方法我没有验证过,不知道是不是对所有linux系统都适用。
  • 设置服务器的端口转发规则,而服务本身依然运行在高于1024的端口中。
  • 有些Linux系统支持能力的概念,即:普通用户也能够做只有超级用户才能做的任务——包括使用端口,这种情况下可以直接打开该用户的端口绑定能力。
# 设置CAP_NET_BIND_SERVICE
setcap cap_net_bind_service =+ep <您的应用程序路径>
# 1.注意该方法不是所有的Linux系统都使用,内核在2.1之前的系统中并没有提供“能力”的概念,所以需要检查该系统是否支持。
# 2.另外需要注意的是如果运行的程序是一个脚本,那么该方法是没有办法正常工作的。

3. HttpServerOptions

接下来让我们一起看看HttpServerOptions的相关细节——作为createHttpServer的主参数,它在设置Vert.x中的HttpServer相关配置时起到了重要作用;那么缘木求鱼,如果要理解HttpServerOptions中的很多细节,最好的办法就是去追溯到它的源头,从Java语言的顶层开始。

倘若只是堆代码或者API文档,我想读者是没有心情去阅读这些枯燥而且索然无味的文字的,所以我尽可能将我所知道的东西意义消化,然后重新造一道菜肴,让读者消化起来更有味道。

本章节牵涉的类的继承树如下

NetworkOptions -> TCPSSLOptions -> NetServerOptions -> HttpServerOptions

3.1. NetworkOptions

第一个出场的叫io.vertx.core.net.NetworkOptions,对的,它似乎和我们要讲到的HttpServer都没有什么直接的关系,但是它可以算得上是HttpServerOptions的“曾祖”(在代码中extends了四次才找到它),并且它是一个抽象类:

public abstract class NetworkOptions

既然它是抽象类,那么它抽象了什么?——从名字可以知道,这个类抽象了和底层网络相关的一些配置项,这个类主要定义了下边几个配置:

  • logActivity:是否开启网络日志,由于Vert.x底层使用了Netty服务器,它的日志是基于Netty的Pipeline机制实现的。
  • receiveBufferSize/sendBufferSize:设置网络发送/接收字节流缓冲区的字节数。
  • reuseAddress/reusePort:是否启用地址重用或端口重用。
  • trafficClass:设置“通信量类”。
Json配置节点 set方法名 默认值
logActivity setLogActivity DEFAULT_LOG_ENABLED = false
receiveBufferSize setReceiveBufferSize DEFAULT_RECEIVE_BUFFER_SIZE = -1
reuseAddress setReuseAddress DEFAULT_REUSE_ADDRESS = true
reusePort setReusePort DEFAULT_REUSE_PORT = false
sendBufferSize setSendBufferSize DEFAULT_SEND_BUFFER_SIZE = -1
trafficClass setTrafficClass DEFAULT_TRAFFIC_CLASS = -1

从上边的定义可以知道,NetworkOptions负责了和网络底层相关的所有配置,而关于这几种配置的几个话题可参考后记延展阅读,由于正章主要是讲Vert.x,所以点到为止。

3.2. TCPSSLOptions

第二个出场的叫io.vertx.core.net.TCPSSLOptions,它是我们本章主角HttpServerOptions祖父辈的,同样是一个抽象类:

public abstract class TCPSSLOptions extends NetworkOptions

这个“祖父”相比之前的NetworkOptions,选项相对复杂一点,主要抽象了和TCP协议、SSL协议相关的配置项。它主要定义了以下配置项:

  • tcpNoDelay:设置TCP-no-delay的值「TCP中的NO_DELAY」,默认为true,该值为true时禁用了TCP中的Nagle算法。
  • tcpKeepAlive:设置TCP连接的keep alive属性,启用KeepAlive机制,默认为false(是否为一个长连接)——需要说明的是KeepAlive并不是TCP协议规范中的一部分,只是几乎所有的TCP/IP协议栈都支持这种机制。
  • soLinger:设置「TCP中的SO_LINGER」,它用于设置延迟关闭的时间,等待套接字发送缓冲区中的数据发送完成。
  • usePooledBuffers:设置Netty服务器是否使用缓冲池,默认值为false。
  • ssl:在连接中是否启用SSL,默认未启用。
  • idleTimeout:默认的空闲超时时间值,默认为0,这种模式下,设置这部分内容需要和KeepAlive机制进行区分。
  • idleTimeoutUnit:默认的空闲超时时间值使用的单位。
  • useAlpn:是否在TLS协议中使用ALPN协议,ALPN全称为Application Layer Protocol Negotiation,它是TLS的一种扩展,允许在安全连接基础上进行应用层协议的协商,它支持任意应用层协议的协商,目前目前最多的是支持HTTP2的协商
  • tcpFastOpen:设置「TCP中的TCP_FASTOPEN」选项,默认为false。
  • tcpCork:设置「TCP中的TCP_CORK」选项,默认为false。
  • tcpQuickAck:设置「TCP中的TCP_QUICKACK」选项,默认为false。

上述属性为简单属性,不牵涉到内置对象的创建,实际上TCPSSLOptions中还包含了SSL相关的一些专用配置类,这些类将在后续的教程中逐渐讲述。

Json配置节点 set方法名 默认值
tcpNoDelay setTcpNoDelay DEFAULT_TCP_NO_DELAY = true
tcpKeepAlive setTcpKeepAlive DEFAULT_TCP_KEEP_ALIVE = false
soLinger setSoLinger DEFAULT_SO_LINGER = -1
usePooledBuffers setUsePooledBuffers DEFAULT_USE_POOLED_BUFFERS = false
ssl setSsl DEFAULT_SSL = false
idleTimeout setIdleTimeout DEFAULT_IDLE_TIMEOUT = 0
idleTimeoutUnit setIdleTimeoutUnit DEFAULT_IDEL_TIMEOUT_TIME_UNIT = TimeUnit.SECONDS
useAlpn setUseAlpn DEFAULT_USE_ALPN = false
tcpFastOpen setTcpFastOpen DEFAULT_TCP_FAST_OPEN = false
tcpCork setTcpCork DEFAULT_TCP_CORK = false
tcpQuickAck setTcpQuickAck DEFAULT_TCP_QUICK_ACK = false

从上边的所有定义可以知道,TCPSSLOptions类负责了大量和TCP协议相关的配置,关于其中部分配置的扩展阅读,读者可以参考下边的内容:

TCPSSLOptions中除了上述的基本属性以外,还包含了和SSL相关的复杂属性,它都是通过Java类来定义的,先看看SSL相关的复杂属性相关类信息:

  • io.vertx.core.net.JdkSSSLEngineOptions
  • io.vertx.core.net.JksOptions
  • io.vertx.core.net.OpenSSLEngineOptions
  • io.vertx.core.net.PemKeyCertOptions
  • io.vertx.core.net.PemTrustOptions
  • io.vertx.core.net.PfxOptions
Json配置节点 set方法名 实例化的类,方法参数(上边列表)
jdkSslEngineOptions setJdkSslEngineOptions JdkSSLEngineOptions
keyStoreOptions setKeyStoreOptions JksOptions
openSslEngineOptions setOpenSslEngineOptions OpenSSLEngineOptions
pemKeyCertOptions setPemKeyCertOptions PemKeyCertOptions
pemTrustOptions setPemTrustOptions PemTrustOptions
pfxKeyCertOptions setPfxKeyCertOptions PfxOptions
pfxTrustOptions setPfxTrustOptions PfxOptions
trustStoreOptions setTrustStroeOptions JksOptions
crlPaths addCrlPath 包含String元素的JsonArray
crlValues addCrlValue 包含String元素的JsonArray,添加时转成Buffer处理
enabledCipherSuites addEnabledCipherSuite 包含String元素的JsonArray
enabledSecureTransportProtocols setEnabledSecureTransportProtocols 包含String元素的list

也许读者会对上述的一些选项比较陌生,但是没有关系,本小节只是需要让读者明白,TCPSSLOptions中包含了很多和SSL协议相关的配置项信息,并且了解TCPSSLOptions类中所包含的API来设置相关配置,关于一些基础知识可参考下边的文章:

3.3. NetServerOptions

看完了上边的TCPSSLOptionsNetworkOptions,接下来我们一起来看HttpServerOptions的直接父类——NetServerOptions,该类定义了很多和网络服务器相关的配置项,包括前文提到的端口、主机IP地址等,和前边两个类不同的一点是,这个类本身不是一个抽象类,而是一个可实例化的类,它的定义如下:

public class NetServerOptions extends TCPSSLOptions

这个类所包含的属性如下:

  • acceptBacklog:默认值为1024,用于设置默认的连接缓存相关的参数,它一般表示在拒绝额外的请求之前,能接受的连接数。
  • port:网络服务器的端口号,在NetServerOptions类中设置的默认参数为0,它表示随机选择一个可用的端口号。
  • host:主机IP地址,默认值为0.0.0.0
  • sni:设置服务器是否支持SNI——SNI全称为Server Name Indication,它用于改善服务器和客户端SSL/TLS的一个扩展。
  • clientAuthRequired:「已过期」在客户端和服务器建立连接时,是否启用SSL/TLS,默认值为false。
  • clientAuth:设置SSL/TLS的模式,在Vert.x中,该属性的类型为一个枚举类型:io.vertx.core.http.ClientAuth,它用于描述客户端和服务器之间对SSL/TLS支持的种类——包含了三个值。
    • ClientAuth.NONE:不需要任何客户端认证。
    • ClientAuth.REQUEST:如果客户端提供了身份验证则接受该认证结果。设置了这种模式后,若客户端没有提供任何身份认证,那么服务端和客户端继续协商处理该请求。
    • ClientAuth.REQUIRED:「严格」要求客户端必须提供身份认证,若客户端没有提供,则服务端拒绝该请求。

NetServerOptions类主要设置了和网络服务器相关的配置项,它的详情:

Json配置节点 set方法名 默认值
acceptBacklog setAcceptBacklog DEFAULT_ACCEPT_BACKLOG = -1
port setPort DEFAULT_PORT = 0
host setHost DEFAULT_HOST = "0.0.0.0"
sni setSni DEFAULT_SNI = false
clientAuth setClientAuth DEFAULT_CLIENT_AUTH = ClientAuth.NONE
clientAuthRequired setClientAuthRequired

有一点值的注意的是,上述的所有属性中,clientAuthRequired的属性已经在新版中标记成了「@Deprecated」,它的get方法的源代码如下:

  @Deprecated
  public boolean isClientAuthRequired() {
    return clientAuth == ClientAuth.REQUIRED;
  }

至于和本文相关的网络服务器的知识,大家可以参考下边的扩展阅读:

3.4. HttpServerOptions

无疑我们现在才接触到主角:HttpServerOptions——按照上边的格式看看这个类和它的父辈有什么不同,之后再结合实际应用来理解这部分内容,和NetServerOptions一样,HttpServerOptions同样是一个可实例化的类。

results matching ""

    No results matching ""