目录
Netty HTTP服务器(HttpServer)
Reactor Netty提供易于使用和易于配置的HttpServer类。它隐藏Netty了创建HTTP服务器所需的大部分功能并增加了Reactive Streams背压(Reactive Streams是具有无阻塞背压的异步流处理的标准)
启动和停止
要启动HTTP服务器,必须创建和配置HttpServe实例。默认情况下,host为任何本地地址配置,并且系统在bind调用操作时选择一个临时端口。
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create() // 创建一个准备配置的HttpServer实例
.bindNow(); // 以阻塞方式启动服务器并等待它完成初始化
server.onDispose()
.block();
}
}
返回的DisposableServer提供了一个简单的服务器API,包括disposeNow(),它以阻塞方式关闭服务器
主机和端口
要在特定的host和上提供服务port,可以将以下配置应用到HTTP服务器:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.host("localhost") // 配置HTTP服务器主机
.port(8080) // 配置HTTP服务器端口
.bindNow();
server.onDispose()
.block();
}
}
要在多个地址上提供服务,在配置HttpServer之后,可以多次绑定它以获得单独的可处置服务器。所有创建的服务器都将共享诸如“LoopResources”之类的资源,因为它们在引擎盖下使用相同的配置实例。
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class MultiAddressApplication {
public static void main(String[] args) {
HttpServer httpServer = HttpServer.create();
DisposableServer server1 = httpServer
.host("localhost") // 配置第一台HTTP服务器主机
.port(8080) // 配置第一个HTTP服务器端口
.bindNow();
DisposableServer server2 = httpServer
.host("0.0.0.0") // 配置第二台HTTP服务器主机
.port(8081) // 配置第二个HTTP服务器端口
.bindNow();
Mono.when(server1.onDispose(), server2.onDispose())
.block();
}
}
急切初始化
默认情况下,HttpServer资源的初始化是按需进行的。这意味着bind operation吸收了初始化和加载所需的额外时间:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
HttpServer httpServer =
HttpServer.create()
.handle((request, response) -> request.receive().then());
httpServer.warmup() // 初始化并加载事件循环组、本机传输库和用于安全性的本机库
.block();
DisposableServer server = httpServer.bindNow();
server.onDispose()
.block();
}
}
路由HTTP
为HTTP服务器定义路由需要配置提供的HttpServerRoutes构建器:
注意:服务器路由是唯一的,并且只调用声明顺序中的第一个匹配项。
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.route(routes -> Routes
// 提供GET请求/hello并返回Hello World
.get("/hello", (request, response) -> response.sendString(Mono.just("Hello World!")))
// 为POST请求提供服务/echo并将接收到的请求正文作为响应返回
.post("/echo", (request, response) -> response.send(request.receive().retain()))
// 提供GET请求/path/{param}并返回路径参数的值
.get("/path/{param}", (request, response) -> response.sendString(Mono.just(request.param("param"))))
// 为websocket提供服务/ws,并将接收到的传入数据作为传出数据返回
.ws("/ws", (wsInbound, wsOutbound) -> wsOutbound.send(wsInbound.receive().retain())))
.bindNow();
server.onDispose()
.block();
}
}
SSE:数据流单指令序列扩展
SSE是“因特网数据流单指令序列扩展(Internet Streaming SIMD Extensions的缩写。SSE除保持原有的MMX指令外,又新增了70条指令,在加快浮点运算的同时,改善了内存的使用效率,使内存速度更快。它对游戏性能的改善十分显著,按Intel的说法,SSE对下述几个领域的影响特别明显:3D几何运算及动画处理、图形处理(如Photoshop)、视频编辑/压缩/解压(如MPEG和DVD)、语音识别以及声音压缩和合成等
配置HTTP服务器以提供服务Server-Sent Events:
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.http.server.HttpServerRequest;
import reactor.netty.http.server.HttpServerResponse;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.function.BiFunction;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.route(routes -> routes.get("/sse", serveSse()))
.bindNow();
server.onDispose()
.block();
}
/**
* Prepares SSE response
* The "Content-Type" is "text/event-stream"
* The flushing strategy is "flush after every element" emitted by the provided Publisher
*/
private static BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>> serveSse() {
Flux<Long> flux = Flux.interval(Duration.ofSeconds(10));
return (request, response) ->
response.sse()
.send(flux.map(Application::toByteBuf), b -> true);
}
/**
* Transforms the Object to ByteBuf following the expected SSE format.
*/
private static ByteBuf toByteBuf(Object any) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
out.write("data: ".getBytes(Charset.defaultCharset()));
MAPPER.writeValue(out, any);
out.write("\n\n".getBytes(Charset.defaultCharset()));
}
catch (Exception e) {
throw new RuntimeException(e);
}
return ByteBufAllocator.DEFAULT
.buffer()
.writeBytes(out.toByteArray());
}
private static final ObjectMapper MAPPER = new ObjectMapper();
}
静态资源(Static Resources)
配置HTTP服务器以提供静态资源:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Application {
public static void main(String[] args) throws URISyntaxException {
Path file = Paths.get(Application.class.getResource("/logback.xml").toURI());
DisposableServer server =
HttpServer.create()
.route(routes -> routes.file("/index.html", file))
.bindNow();
server.onDispose()
.block();
}
}
写入数据
要将数据发送到连接的客户端,必须使用handle(…)或route(…)附加I/O处理程序。I/O处理程序有权访问HttpServerResponse,以便能够写入数据:
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
// hello向连接的客户端发送字符串
.handle((request, response) -> response.sendString(Mono.just("hello")))
.bindNow();
server.onDispose()
.block();
}
}
(1)添加标题和其他元数据
当向连接的客户端发送数据时,可能需要发送额外的headers(头)、cookies、status code(状态码)和其他元数据。可以使用HttpServerResponse:
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.route(routes ->
routes.get("/hello",
(request, response) ->
response.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_LENGTH, "12")
.sendString(Mono.just("Hello World!"))))
.bindNow();
server.onDispose()
.block();
}
}
(2)压缩:Compression
可以将HTTP服务器配置为发送压缩响应,具体取决于请求标头Accept-Encoding
Reactor Netty提供了三种不同的压缩输出数据的策略:
①:compress(boolean):是否压缩
根据提供的布尔值,启用(true)或禁用(false)压缩
②:compress(int):压缩大小值
一旦响应大小超过给定值(以字节为单位),就会执行压缩
③:compress(BiPredicate<HttpServerRequest, HttpServerResponse>):是否立即压缩
如果谓词返回,则执行压缩true
使用compress方法(设置为true)启用压缩:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Application {
public static void main(String[] args) throws URISyntaxException {
Path file = Paths.get(Application.class.getResource("/logback.xml").toURI());
DisposableServer server =
HttpServer.create()
.compress(true) // 是否启用压缩
.route(routes -> routes.file("/index.html", file))
.bindNow();
server.onDispose()
.block();
}
}
消费数据
要从连接的客户端接收数据,必须使用handle(…)或route(…)附加I/O处理程序 。I/O处理程序有权访问HttpServerRequest,以便能够读取数据。
使用该handle(…)方法:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
// 从连接的客户端接收数据
.handle((request, response) -> request.receive().then())
.bindNow();
server.onDispose()
.block();
}
}
读取Headers、URI参数和其他元数据
当从连接的客户端接收数据时,可能需要检查请求标头、参数和其他元数据。可以使用HttpServerRequest获取此附加元数据
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.route(routes ->
routes.get("/{param}", (request, response) -> {
if (request.requestHeaders().contains("Some-Header")) {
return response.sendString(Mono.just(request.param("param")));
}
return response.sendNotFound();
}))
.bindNow();
server.onDispose()
.block();
}
}
读取Post表单和文件数据(Multipart)
当从连接的客户端接收数据时,可能想要访问POST form(application/x-www-form-urlencoded)或multipart(multipart/form-data)数据。可以使用HttpServerRequest获取此数据
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.route(routes ->
// 接收POST表单/多部分数据
routes.post("/multipart", (request, response) -> response.sendString(
request.receiveForm()
.flatMap(data -> Mono.just('[' + data.getName() + ']')))))
.bindNow();
server.onDispose()
.block();
}
}
当需要更改默认设置时,可以配置HttpServer或者可以根据请求提供配置:
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
// HttpServer指定数据仅存储在磁盘上的配置
.httpFormDecoder(builder -> builder.maxInMemorySize(0))
.route(routes ->
routes.post("/multipart", (request, response) -> response.sendString(
// 每个请求的配置,指定如果数据大小超过指定大小,则将数据存储在磁盘上
request.receiveForm(builder -> builder.maxInMemorySize(256))
.flatMap(data -> Mono.just('[' + data.getName() + ']')))))
.bindNow();
server.onDispose()
.block();
}
}
可用配置
(1)baseDirectory:磁盘存储目录
配置在磁盘上存储数据的目录。默认为生成的临时目录。
(2)charset:字符集编码
配置charset数据语言编码。默认为StandardCharsets#UTF_8.
(3)maxInMemorySize:每个数据的最大内存大小
配置每个数据的最大内存大小,即如果大小大于maxInMemorySize,则将数据写入磁盘,否则它在内存中。如果设置为-1全部内容存储在内存中。如果设置为0整个内容存储在磁盘上。默认为16kb.
(4)maxSize:每个数据的最大值
配置每个数据的最大值。当达到限制时,会引发异常。如果设置为“-1”这意味着没有限制。默认:-1,无限制。
(5)scheduler:解码阶段卸载磁盘操作的调度程序
配置用于在解码阶段卸载磁盘操作的调度程序。默认为Schedulers#boundedElastic()
(6)streaming:数据是否直接从解析的输入缓冲区流中流式传输
当设置为true时,数据直接从解析的输入缓冲区流中流式传输,这意味着它既不存储在内存中也不存储在文件中。
当false时,部分由内存和/或文件存储支持。默认:false
注意:启用流式传输后,提供的数据可能不会处于完整状态,即HttpData#isCompleted()必须进行检查。另外请注意,启用此属性有效地忽略maxInMemorySize、baseDirectory和scheduler。
获取远程(客户端)地址
除了可以从请求中获取的元数据外,还可以接收host(server)地址、remote(client)地址和scheme。根据所选的工厂方法,可以直接从通道或使用Forwarded或X-Forwarded-* HTTP请求标头检索信息:
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
// 如果可能,指定从Forwarded和X-Forwarded-* HTTP请求标头获取有关连接的信息
.forwarded(true)
.route(routes ->
routes.get("/clientip",
(request, response) ->
// 返回远程(客户端)对等方的地址
response.sendString(Mono.just(request.remoteAddress().getHostString()))))
.bindNow();
server.onDispose()
.block();
}
}
也可以自定义Forwarded或X-Forwarded-*标头处理程序的行为:
import java.net.InetSocketAddress;
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.transport.AddressUtils;
public class CustomForwardedHeaderHandlerApplication {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.forwarded((connectionInfo, request) -> {
// 添加自定义标头处理程序
String hostHeader = request.headers().get("X-Forwarded-Host");
if (hostHeader != null) {
String[] hosts = hostHeader.split(",", 2);
InetSocketAddress hostAddress = AddressUtils.createUnresolved(
hosts[hosts.length - 1].trim(),
connectionInfo.getHostAddress().getPort());
connectionInfo = connectionInfo.withHostAddress(hostAddress);
}
return connectionInfo;
})
.route(routes ->
routes.get("/clientip",
(request, response) ->
// 返回远程(客户端)对等方的地址
response.sendString(Mono.just(request.remoteAddress()
.getHostString()))))
.bindNow();
server.onDispose()
.block();
}
}
HTTP请求解码器
默认情况下,Netty为传入的请求配置一些限制:HttpRequestDecoder和HttpServerUpgradeHandler
(1)初始行的最大长度
(2)所有标题的最大长度
(3)内容或每个块的最大长度
默认情况下,HTTP服务器配置有以下设置:HttpDecoderSpec.java
public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096; // 默认最大初始行长度
public static final int DEFAULT_MAX_HEADER_SIZE = 8192; // 默认最大标题大小
public static final int DEFAULT_MAX_CHUNK_SIZE = 8192; // 默认最大块大小
public static final boolean DEFAULT_VALIDATE_HEADERS = true; // 默认验证标头
public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128; // 默认初始缓冲区大小
// 默认允许重复内容长度
public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;
HttpRequestDecoderSpec.java
// HTTP/2.0明文升级请求内容的最大长度。默认情况下,服务器将拒绝包含非空内容的升级请求,因为升级请求很可能是GET请求。
public static final int DEFAULT_H2C_MAX_CONTENT_LENGTH = 0;
当需要更改这些默认设置时,可以HTTP按如下方式配置服务器:
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
// 所有标头的最大长度将为16384. 当超过此值时, 会引发TooLongFrameException
.httpRequestDecoder(spec -> spec.maxHeaderSize(16384))
.handle((request, response) -> response.sendString(Mono.just("hello")))
.bindNow();
server.onDispose()
.block();
}
}
生命周期回调
提供了以下生命周期回调以扩展HttpServer:
回调函数 | 描述 |
---|---|
doOnBind | 在服务器通道即将绑定时调用 |
doOnBound | 在绑定服务器通道时调用 |
doOnChannelInit | 初始化通道时调用 |
doOnConnection | 连接远程客户端时调用 |
doOnUnbound | 当服务器通道未绑定时调用 |
使用doOnConnection和doOnChannelInit回调:
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import java.util.concurrent.TimeUnit;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
// Netty管道ReadTimeoutHandler在连接远程客户端时扩展
.doOnConnection(conn ->
conn.addHandler(new ReadTimeoutHandler(10, TimeUnit.SECONDS)))
// Netty管道LoggingHandler在初始化通道时扩展
.doOnChannelInit((observer, channel, remoteAddress) ->
channel.pipeline()
.addFirst(new LoggingHandler("reactor.netty.examples")))
.bindNow();
server.onDispose()
.block();
}
}
TCP-level配置
(1)Setting Channel Options - 设置通道参数选项
当需要在TCP级别更改配置时,可以使用以下代码段来扩展默认TCP服务器配置:
import io.netty.channel.ChannelOption;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
// TCP级别配置
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.bindNow();
server.onDispose()
.block();
}
}
(2)Wire Logger:连线日志记录
Reactor Netty提供线路日志记录,用于何时需要检查对等点之间的流量。默认情况下,线路日志记录处于禁用状态。要启用它,必须将记录器reactor.netty.http.server.HttpServer级别设置为DEBUG并应用以下配置:(与TCP配置类似)
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.wiretap(true) // 启用连线记录
.bindNow();
server.onDispose()
.block();
}
}
Reactor Netty支持3种不同的格式化程序:
连线日志格式化 | 描述 |
---|---|
AdvancedByteBufFormat#HEX_DUM | 同时记录事件和内容。内容将采用十六进制格式(默认) |
AdvancedByteBufFormat#SIMPLE | 使用此格式启用连线记录时,仅记录事件 |
AdvancedByteBufFormat#TEXTUAL | 同时记录事件和内容。内容将采用纯文本格式 |
当需要更改默认格式化程序时,可以按如下方式进行配置:
import io.netty.handler.logging.LogLevel;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.transport.logging.AdvancedByteBufFormat;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
// 启用连线记录,AdvancedByteBufFormat#TEXTUAL用于打印内容
.wiretap("logger-name", LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL)
.bindNow();
server.onDispose()
.block();
}
}
SSL和TLS
当需要SSL或TLS时,可以应用下一个示例中显示的配置。
默认情况下,如果OpenSSL可用,SslProvider.OPENSSL则使用provider作为提供者。
否则SslProvider.JD使用。可以通过使用SslContextBuilder或通过设置来切换提供程序:-Dio.netty.handler.ssl.noOpenSsl=true
以下示例使用SslContextBuilder:
import reactor.netty.DisposableServer;
import reactor.netty.http.Http11SslContextSpec;
import reactor.netty.http.server.HttpServer;
import java.io.File;
public class Application {
public static void main(String[] args) {
File cert = new File("certificate.crt");
File key = new File("private.key");
Http11SslContextSpec http11SslContextSpec = Http11SslContextSpec.forServer(cert, key);
DisposableServer server =
HttpServer.create()
.secure(spec -> spec.sslContext(http11SslContextSpec))
.bindNow();
server.onDispose()
.block();
}
}
(1)服务器名称指示
可以配置HTTP多个SslContext映射到特定域的服务器。配置SNI映射时可以使用确切的域名或包含通配符的域名。
使用包含通配符的域名:
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import java.io.File;
public class Application {
public static void main(String[] args) throws Exception {
File defaultCert = new File("default_certificate.crt");
File defaultKey = new File("default_private.key");
File testDomainCert = new File("default_certificate.crt");
File testDomainKey = new File("default_private.key");
SslContext defaultSslContext = SslContextBuilder.forServer(defaultCert, defaultKey).build();
SslContext testDomainSslContext = SslContextBuilder.forServer(testDomainCert, testDomainKey).build();
DisposableServer server =
HttpServer.create()
.secure(spec -> spec.sslContext(defaultSslContext)
.addSniMapping("*.test.com",
testDomainSpec -> testDomainSpec.sslContext(testDomainSslContext)))
.bindNow();
server.onDispose()
.block();
}
}
HTTP访问日志
可以HTTP以编程方式或通过配置启用访问日志。默认情况下是禁用的。
①可以通过配置“-Dreactor.netty.http.server.accessLogEnabled=true”来启用HTTP访问日志:
以编程方式启用它:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.accessLog(true) // 调用此方法优先于系统属性配置
.bindNow();
server.onDispose()
.block();
}
}
②可以使用以下配置(用于Logback或类似的日志框架)来拥有单独的HTTP访问日志文件:
<appender name="accessLog" class="ch.qos.logback.core.FileAppender">
<file>access_log.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="accessLog" />
</appender>
<logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false">
<appender-ref ref="async"/>
</logger>
默认情况下,日志格式为Common Log Format,但可以指定自定义格式作为参数:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.http.server.logging.AccessLog;
public class CustomLogAccessFormatApplication {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.accessLog(true, x -> AccessLog.create("method={}, uri={}", x.method(), x.uri()))
.bindNow();
server.onDispose()
.block();
}
}
还可以HTTP使用该AccessLogFactory#createFilter方法过滤访问日志:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.http.server.logging.AccessLogFactory;
public class FilterLogAccessApplication {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.accessLog(true,
AccessLogFactory.createFilter(p -> !String.valueOf(p.uri()).startsWith("/health/")))
.bindNow();
server.onDispose()
.block();
}
}
请注意,此方法也可以采用自定义格式参数:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.http.server.logging.AccessLog;
import reactor.netty.http.server.logging.AccessLogFactory;
public class CustomFormatAndFilterAccessLogApplication {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
// 指定要使用的过滤谓词
.accessLog(true, AccessLogFactory.createFilter(p -> !String.valueOf(p.uri()).startsWith("/health/"),
x -> AccessLog.create("method={}, uri={}", x.method(), x.uri()))) // 指定要应用的自定义格式
.bindNow();
server.onDispose()
.block();
}
}
HTTP/2
默认情况下,HTTP服务器支持HTTP/1.1. 如果需要HTTP/2,可以通过配置获取。除了协议配置,如果需要H2但不需要H2C(cleartext),还必须配置SSL。
由于应用层协议协商(ALPN)不受JDK8的“开箱即用”支持(尽管一些供应商将ALPN向后移植到JDK8),因此需要额外依赖支持它的本机库。
例如:netty-tcnative-boringssl-static
什么是HTTP/2?
HTTP/2通过引入标头字段压缩并允许在同一连接上进行多个并发交换,从而更有效地使用网络资源并减少延迟感知。它还引入了从服务器到客户端的主动推送。
HTTP/2由两个规范组成:
(1)超文本传输协议版本2(Hypertext) - RFC7540
(2)HPACK - HTTP/2的头压缩 - RFC7541
(1)简单的H2例子:
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.Http2SslContextSpec;
import reactor.netty.http.HttpProtocol;
import reactor.netty.http.server.HttpServer;
import java.io.File;
public class H2Application {
public static void main(String[] args) {
File cert = new File("certificate.crt");
File key = new File("private.key");
Http2SslContextSpec http2SslContextSpec = Http2SslContextSpec.forServer(cert, key);
DisposableServer server =
HttpServer.create()
.port(8080)
// 将服务器配置为仅支持 HTTP/2
.protocol(HttpProtocol.H2)
// 配置SSL
.secure(spec -> spec.sslContext(http2SslContextSpec))
.handle((request, response) -> response.sendString(Mono.just("hello")))
.bindNow();
server.onDispose()
.block();
}
}
(2)简单的H2C例子:
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.HttpProtocol;
import reactor.netty.http.server.HttpServer;
public class H2CApplication {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.port(8080)
.protocol(HttpProtocol.H2C)
.handle((request, response) -> response.sendString(Mono.just("hello")))
.bindNow();
server.onDispose()
.block();
}
}
协议选择:HttpProtocol.java
public enum HttpProtocol {
/**
* 默认支持HTTP协议httpserver和httpclient
*/
HTTP11,
/**
* TLS支持HTTP/2.0如果与HTTP/1.1协议一起使用,HTTP/2.0将是首选协议。在协商应用程序级协议时,可以选择HTTP/2.0或HTTP/1.1。如果在没有使用HTTP/1.1协议的情况下使用,HTTP/2.0将始终作为通信协议提供,而不会回退到HTTP/1
*/
H2,
/**
* HTTP/2.0支持明文。如果与HTTP/1.1协议一起使用,将支持H2C“升级”:首先作为HTTP/1.1请求或使用请求,查找HTTP/2.0头和{@literal Connection:upgrade}。如果升级成功或出现回退HTTP/1.1响应,服务器通常会回复成功101状态。成功后,客户端将开始发送HTTP/2.0流量。如果在没有HTTP/1.1协议的情况下使用,将支持H2C“先验知识”:不需要客户机和服务器之间的{@literal Connection:Upgrade}握手,但将不支持回退到HTTP/1.1。
*/
H2C
}
Metrics(监控指标)
HTTP服务器支持内置集成的Micrometer,公开了前缀为reactor.netty.http.server的所有指标
为了避免启用指标的内存和CPU开销,重要的是尽可能将真实URI转换为模板化URI。如果不转换为类似模板的形式,每个不同的URI都会导致创建一个不同的标签,这会为指标占用大量内存。
始终为带有URI标记的计量器应用上限。在无法对真实URI进行模板化的情况下,,配置Metrics数量上限会有所帮助。可以在maximumAllowableTags上找到更多信息。
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.config.MeterFilter;
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
Metrics.globalRegistry
.config()
// 对带URI标签的仪表应用上限
.meterFilter(MeterFilter.maximumAllowableTags("reactor.netty.http.server", "URI", 100, MeterFilter.deny()));
DisposableServer server =
HttpServer.create()
// 模板化URI将尽可能用作URI标记值
.metrics(true, s -> {
if (s.startsWith("/stream/")) {
return "/stream/{n}";
}
else if (s.startsWith("/bytes/")) {
return "/bytes/{n}";
}
return s;
})
// 启用与 Micrometer 的内置集成
.route(r ->
r.get("/stream/{n}",
(req, res) -> res.sendString(Mono.just(req.param("n"))))
.get("/bytes/{n}",
(req, res) -> res.sendString(Mono.just(req.param("n")))))
.bindNow();
server.onDispose()
.block();
}
}
当需要HTTP服务器指标与其他系统集成时,Micrometer或者想提供自己的集成Micrometer,可以提供自己的指标记录器:
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.http.server.HttpServerMetricsRecorder;
import java.net.SocketAddress;
import java.time.Duration;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
// 启用HTTP服务器指标并提供HttpServerMetricsRecorder实现
.metrics(true, CustomHttpServerMetricsRecorder::new)
.route(r ->
r.get("/stream/{n}",
(req, res) -> res.sendString(Mono.just(req.param("n"))))
.get("/bytes/{n}",
(req, res) -> res.sendString(Mono.just(req.param("n")))))
.bindNow();
server.onDispose()
.block();
}
}
(1)HTTP服务器指标的信息
Metrics名称 | 类型 | 描述 |
---|---|---|
reactor.netty.http.server.data.received | DistributionSummary | 接收的数据量,以字节为单位 |
reactor.netty.http.server.data.sent | DistributionSummary | 发送的数据量,以字节为单位 |
reactor.netty.http.server.errors | Counter | 发生的错误数 |
reactor.netty.http.server.data.received.time | Timer | 消耗传入数据所花费的时间 |
reactor.netty.http.server.data.sent.time | Timer | 发送传出数据所花费的时间 |
reactor.netty.http.server.response.time | Timer | 请求/响应的总时间 |
(2)ByteBufAllocator指标
Metrics名称 | 类型 | 描述 |
---|---|---|
reactor.netty.bytebuf.allocator.used.heap.memory | Gauge | 堆内存的字节数 |
reactor.netty.bytebuf.allocator.used.direct.memory | Gauge | 直接内存的字节数 |
reactor.netty.bytebuf.allocator.used.heap.arenas | Gauge | 堆区域的数量(当PooledByteBufAllocator) |
reactor.netty.bytebuf.allocator.used.direct.arenas | Gauge | 直接竞技场的数量(当PooledByteBufAllocator) |
reactor.netty.bytebuf.allocator.used.threadlocal.caches | Gauge | 线程本地缓存的数量(当PooledByteBufAllocator) |
reactor.netty.bytebuf.allocator.used.small.cache.size | Gauge | 小缓存的大小(当PooledByteBufAllocator) |
reactor.netty.bytebuf.allocator.used.normal.cache.size | Gauge | 正常缓存的大小(当PooledByteBufAllocator) |
reactor.netty.bytebuf.allocator.used.chunk.size | Gauge | 竞技场的块大小(当PooledByteBufAllocator) |
(3)EventLoop指标
Metrics名称 | 类型 | 描述 |
---|---|---|
reactor.netty.eventloop.pending.tasks | Gauge | 事件循环中待处理的任务数 |