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.x
,Verticle
实例的本意就是为了让所有的东西能够平行扩展,并且组件足够小到可以实现“单一职责”。从上边的右图可以知道,一个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
看完了上边的TCPSSLOptions
和NetworkOptions
,接下来我们一起来看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
的属性已经在新版中标记成了「@Deprecated」,它的get方法的源代码如下:
@Deprecated
public boolean isClientAuthRequired() {
return clientAuth == ClientAuth.REQUIRED;
}
至于和本文相关的网络服务器的知识,大家可以参考下边的扩展阅读:
3.4. HttpServerOptions
无疑我们现在才接触到主角:HttpServerOptions
——按照上边的格式看看这个类和它的父辈有什么不同,之后再结合实际应用来理解这部分内容,和NetServerOptions
一样,HttpServerOptions
同样是一个可实例化的类。