HTTP 服务器
目录
Reactor Netty 提供了易于使用、易于配置的 HttpServer 类。它屏蔽了创建 HTTP 服务器所需的大部分 Netty 功能,并加入了 Reactive Streams 背压。
1. 启动与停止
要启动 HTTP 服务器,您必须创建并配置一个 HttpServer 实例。默认情况下,host 被配置为任意本地地址,当调用 bind 操作时,系统会选用一个临时端口。以下示例展示了如何创建 HttpServer 实例:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create() 1
.bindNow(); 2
server.onDispose()
.block();
}
}
创建一个可供配置的 HttpServer 实例。
以阻塞方式启动服务器,并等待其完成初始化。
返回的 DisposableServer 提供了简单的服务器 API,其中包括 disposeNow(),它以阻塞方式关闭服务器。
1.1. 主机与端口
要在指定的 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") 1
.port(8080) 2
.bindNow();
server.onDispose()
.block();
}
}
配置
HTTP服务器主机配置
HTTP服务器端口
要在多个地址上提供服务,在配置好 HttpServer 之后,您可以多次绑定它以获得各自独立的 DisposableServer。所有创建的服务器都会共享诸如 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") 1
.port(8080) 2
.bindNow();
DisposableServer server2 = httpServer
.host("0.0.0.0") 3
.port(8081) 4
.bindNow();
Mono.when(server1.onDispose(), server2.onDispose())
.block();
}
}
配置第一个
HTTP服务器主机配置第一个
HTTP服务器端口配置第二个
HTTP服务器主机配置第二个
HTTP服务器端口
2. 预先初始化
默认情况下,HttpServer 资源是按需初始化的。这意味着 bind operation 会承担初始化与加载所需的额外时间:
-
事件循环组
-
原生传输库(当使用原生传输时)
-
用于安全的原生库(在使用
OpenSsl的情况下)
当您需要预加载这些资源时,可以按如下方式配置 HttpServer:
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() 1
.block();
DisposableServer server = httpServer.bindNow();
server.onDispose()
.block();
}
}
初始化并加载事件循环组、原生传输库以及用于安全的原生库
3. 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", 1
(request, response) -> response.sendString(Mono.just("Hello World!")))
.post("/echo", 2
(request, response) -> response.send(request.receive().retain()))
.get("/path/{param}", 3
(request, response) -> response.sendString(Mono.just(request.param("param"))))
.ws("/ws", 4
(wsInbound, wsOutbound) -> wsOutbound.send(wsInbound.receive().retain())))
.bindNow();
server.onDispose()
.block();
}
}
处理发往
/hello的GET请求,并返回Hello World!处理发往
/echo的POST请求,并将收到的请求体作为响应返回。处理发往
/path/{param}的GET请求,并返回路径参数的值。处理发往
/ws的 WebSocket 请求,并将收到的入站数据作为出站数据返回。
服务器路由是唯一的,只会调用按声明顺序匹配到的第一个路由。
如果配置了基于 HTTP/2 的 WebSocket,请确保启用了 HTTP/2 初始设置 projectreactor.io/docs/netty/1.3.0-RC1/api/reactor/netty/http/Http2SettingsSpec.Builder.html#connectProtocolEnabled-boolean-。
3.1. SSE
以下代码展示了如何配置 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();
}
3.2. 静态资源
以下代码展示了如何配置 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();
}
}
4. 写入数据
要向已连接的客户端发送数据,您必须通过 handle(…) 或 route(…) 附加一个 I/O 处理器。该 I/O 处理器可访问 HttpServerResponse,以便写入数据。以下示例使用了 handle(…) 方法:
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()
.handle((request, response) -> response.sendString(Mono.just("hello"))) 1
.bindNow();
server.onDispose()
.block();
}
}
向已连接的客户端发送
hello字符串
4.1. 添加请求头与其他元数据
当您向已连接的客户端发送数据时,可能还需要发送额外的请求头、Cookie、状态码及其他元数据。您可以通过 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();
}
}
4.2. 压缩
您可以根据请求头 Accept-Encoding,配置 HTTP 服务器发送压缩后的响应。
Reactor Netty 提供了三种不同的出站数据压缩策略:
-
compress(boolean):根据所提供的布尔值启用(true)或禁用(false)压缩。 -
compress(int):当响应大小超过给定值(字节)时执行压缩。 -
compress(BiPredicate<HttpServerRequest, HttpServerResponse>):当谓词返回true时执行压缩。 -
compressOptions(HttpCompressionOption… compressionOptions):指定 GZip、Deflate 和 ZSTD 的压缩选项。备注GZip 压缩选项
-
compression level:仅允许 0 到 9 的范围。(默认值:6)
-
window bits:仅允许 0 到 9 的范围。(默认值:15)
-
memory level:仅允许 1 到 9 的范围。(默认值:8)
Deflate 压缩选项
-
compression level:仅允许 0 到 9 的范围。(默认值:6)
-
window bits:仅允许 0 到 9 的范围。(默认值:15)
-
memory level:仅允许 1 到 9 的范围。(默认值:8)
ZSTD 压缩选项
-
compression level:仅允许 -131072 到 9 的范围。(默认值:3)
-
block size:仅允许正数。(默认值:65536,即 64KB)
-
max encode size:仅允许正数。(默认值:33554432,即 32MB)
-
以下示例使用 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();
}
}
5. 读取数据
要从已连接的客户端接收数据,您必须通过 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()) 1
.bindNow();
server.onDispose()
.block();
}
}
从已连接的客户端接收数据
5.1. 读取请求头、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();
}
}
5.2. 读取 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 ->
routes.post("/multipart", (request, response) -> response.sendString(
request.receiveForm() 1
.flatMap(data -> Mono.just('[' + data.getName() + ']')))))
.bindNow();
server.onDispose()
.block();
}
}
接收
POST表单/multipart 数据。
当您需要更改默认设置时,可以配置 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()
.httpFormDecoder(builder -> builder.maxInMemorySize(0)) 1
.route(routes ->
routes.post("/multipart", (request, response) -> response.sendString(
request.receiveForm(builder -> builder.maxInMemorySize(256)) 2
.flatMap(data -> Mono.just('[' + data.getName() + ']')))))
.bindNow();
server.onDispose()
.block();
}
}
在
HttpServer上配置,指定数据仅存储在磁盘上。按请求配置,指定当数据大小超过指定大小时,将数据存储到磁盘上。
以下清单展示了可用的配置:
| 配置名称 | 说明 |
|---|---|
baseDirectory | 配置在磁盘上存储数据的目录。默认为生成的临时目录。 |
charset | 配置数据的 Charset。默认为 StandardCharsets#UTF_8。 |
maxInMemorySize | 配置每条数据的最大内存大小;即当数据大小大于 maxInMemorySize 时写入磁盘,否则保存在内存中。设为 -1 时全部内容保存在内存中。设为 0 时全部内容保存在磁盘上。默认为 16kb。 |
maxSize | 配置每条数据的最大大小。达到该限制时会抛出异常。设为 -1 表示不限制。默认为 -1(无限制)。 |
scheduler | 配置在解码阶段用于卸载磁盘操作的调度器。默认为 Schedulers#boundedElastic() |
streaming | 设为 true 时,数据直接从解析后的输入缓冲区流中流式传输,意味着既不存储在内存中也不存储在文件中。设为 false 时,各部分由内存和/或文件存储支撑。默认为 false。注意:启用流式传输后,所提供的数据可能并非处于完整状态,即必须检查 HttpData#isCompleted()。另请注意,启用此属性会使 maxInMemorySize、baseDirectory 和 scheduler 实际上失效。 |
5.2.1. 获取远程(客户端)地址
除了可以从请求中获取的元数据之外,您还可以获取 host(服务器) 地址、remote(客户端) 地址以及 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(true) 1
.route(routes ->
routes.get("/clientip",
(request, response) ->
response.sendString(Mono.just(request.remoteAddress() 2
.getHostString()))))
.bindNow();
server.onDispose()
.block();
}
}
指定在可能时从
Forwarded和X-Forwarded-*HTTP请求头获取连接相关信息。返回远程(客户端)对端的地址。
也可以自定义 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) -> { 1
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() 2
.getHostString()))))
.bindNow();
server.onDispose()
.block();
}
}
添加自定义请求头处理器。
返回远程(客户端)对端的地址。
5.3. HTTP 请求解码器
默认情况下,Netty 对入站请求配置了一些限制,例如:
-
初始行的最大长度
-
所有请求头的最大长度
-
内容或每个数据块的最大长度
有关更多信息,请参阅 HttpRequestDecoder 与 HttpServerUpgradeHandler
默认情况下,HTTP 服务器配置了以下设置:
public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096;
public static final int DEFAULT_MAX_HEADER_SIZE = 8192;
/**
* Default max chunk size.
*
* @deprecated as of 1.1.0. This will be removed in 2.0.0 as Netty 5 does not support this configuration.
*/
@Deprecated
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;
public static final boolean DEFAULT_ALLOW_PARTIAL_CHUNKS = true;
/**
* The maximum length of the content of the HTTP/2.0 clear-text upgrade request.
* By default, the server will reject an upgrade request with non-empty content,
* because the upgrade request is most likely a GET request.
*/
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()
.httpRequestDecoder(spec -> spec.maxHeaderSize(16384)) 1
.handle((request, response) -> response.sendString(Mono.just("hello")))
.bindNow();
server.onDispose()
.block();
}
}
所有请求头的最大长度将为
16384。超过该值时会抛出 TooLongFrameException。
6. 生命周期回调
提供了以下生命周期回调,供您扩展 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()
.doOnConnection(conn ->
conn.addHandlerFirst(new ReadTimeoutHandler(10, TimeUnit.SECONDS))) 1
.doOnChannelInit((observer, channel, remoteAddress) ->
channel.pipeline()
.addFirst(new LoggingHandler("reactor.netty.examples"))) 2
.bindNow();
server.onDispose()
.block();
}
}
当远程客户端连接时,
Netty管道会加入ReadTimeoutHandler。在初始化通道时,
Netty管道会加入LoggingHandler。
7. TCP 层配置
当您需要更改 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()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.bindNow();
server.onDispose()
.block();
}
}
有关 TCP 层配置的更多详细信息,请参阅 TCP Server。
7.1. 线路日志
当需要检查各对端之间的流量时,Reactor Netty 提供了线路日志。默认情况下线路日志是禁用的。要启用它,您必须将日志记录器 reactor.netty.http.server.HttpServer 的级别设为 DEBUG,并应用以下配置:
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) 1
.bindNow();
server.onDispose()
.block();
}
}
启用线路日志
7.1.1. 线路日志格式化器
Reactor Netty 支持 3 种不同的格式化器:
/**
* When wire logging is enabled with this format, both events and content will be logged.
* The content will be in hex format.
* <p>Examples:</p>
* <pre>
* {@code
* reactor.netty.http.HttpTests - [d5230a14, L:/0:0:0:0:0:0:0:1:60267 - R:/0:0:0:0:0:0:0:1:60269] REGISTERED
* reactor.netty.http.HttpTests - [d5230a14, L:/0:0:0:0:0:0:0:1:60267 - R:/0:0:0:0:0:0:0:1:60269] ACTIVE
* reactor.netty.http.HttpTests - [d5230a14, L:/0:0:0:0:0:0:0:1:60267 - R:/0:0:0:0:0:0:0:1:60269] READ: 145B
* +-------------------------------------------------+
* | 0 1 2 3 4 5 6 7 8 9 a b c d e f |
* +--------+-------------------------------------------------+----------------+
* |00000000| 50 4f 53 54 20 2f 74 65 73 74 2f 57 6f 72 6c 64 |POST /test/World|
* |00000010| 20 48 54 54 50 2f 31 2e 31 0d 0a 43 6f 6e 74 65 | HTTP/1.1..Conte|
* |00000020| 6e 74 2d 54 79 70 65 3a 20 74 65 78 74 2f 70 6c |nt-Type: text/pl|
* |00000030| 61 69 6e 0d 0a 75 73 65 72 2d 61 67 65 6e 74 3a |ain..user-agent:|
* |00000040| 20 52 65 61 63 74 6f 72 4e 65 74 74 79 2f 64 65 | ReactorNetty/de|
* ...
* reactor.netty.http.HttpTests - [d5230a14, L:/0:0:0:0:0:0:0:1:60267 - R:/0:0:0:0:0:0:0:1:60269] WRITE: 38B
* +-------------------------------------------------+
* | 0 1 2 3 4 5 6 7 8 9 a b c d e f |
* +--------+-------------------------------------------------+----------------+
* |00000000| 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d |HTTP/1.1 200 OK.|
* |00000010| 0a 63 6f 6e 74 65 6e 74 2d 6c 65 6e 67 74 68 3a |.content-length:|
* |00000020| 20 30 0d 0a 0d 0a | 0.... |
* +--------+-------------------------------------------------+----------------+
* }
* </pre>
*/
/**
* When wire logging is enabled with this format, only the events will be logged.
* <p>Examples:</p>
* <pre>
* {@code
* reactor.netty.http.HttpTests - [230d3686, L:/0:0:0:0:0:0:0:1:60241 - R:/0:0:0:0:0:0:0:1:60245] REGISTERED
* reactor.netty.http.HttpTests - [230d3686, L:/0:0:0:0:0:0:0:1:60241 - R:/0:0:0:0:0:0:0:1:60245] ACTIVE
* reactor.netty.http.HttpTests - [230d3686, L:/0:0:0:0:0:0:0:1:60241 - R:/0:0:0:0:0:0:0:1:60245] READ: 145B
* reactor.netty.http.HttpTests - [230d3686, L:/0:0:0:0:0:0:0:1:60241 - R:/0:0:0:0:0:0:0:1:60245] WRITE: 38B
* }
* </pre>
*/
/**
* When wire logging is enabled with this format, both events and content will be logged.
* The content will be in plain text format.
* <p>Examples:</p>
* <pre>
* {@code
* reactor.netty.http.HttpTests - [02c3db6c, L:/0:0:0:0:0:0:0:1:60317 - R:/0:0:0:0:0:0:0:1:60319] REGISTERED
* reactor.netty.http.HttpTests - [02c3db6c, L:/0:0:0:0:0:0:0:1:60317 - R:/0:0:0:0:0:0:0:1:60319] ACTIVE
* reactor.netty.http.HttpTests - [02c3db6c, L:/0:0:0:0:0:0:0:1:60317 - R:/0:0:0:0:0:0:0:1:60319] READ: 145B POST /test/World HTTP/1.1
* Content-Type: text/plain
* user-agent: ReactorNetty/dev
* ...
* reactor.netty.http.HttpTests - [02c3db6c, L:/0:0:0:0:0:0:0:1:60317 - R:/0:0:0:0:0:0:0:1:60319] WRITE: 38B HTTP/1.1 200 OK
* content-length: 0
* }
* </pre>
*/
当您需要更改默认格式化器时,可以按如下方式配置:
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()
.wiretap("logger-name", LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL) 1
.bindNow();
server.onDispose()
.block();
}
}
启用线路日志,并使用 AdvancedByteBufFormat#TEXTUAL 打印内容。
7.2. 事件循环组
默认情况下,Reactor Netty 使用一个“事件循环组”,其中工作线程的数量等于初始化时运行时可用的处理器数量(但最少为 4)。该“事件循环组”在同一个 JVM 中的所有服务器与客户端之间共享。当您需要不同的配置时,可以使用 LoopResources 的 #create 方法之一。
以下清单展示了事件循环组的默认配置:
/**
* Default worker thread count, fallback to available processor
* (but with a minimum value of 4).
*/
public static final String IO_WORKER_COUNT = "reactor.netty.ioWorkerCount";
/**
* Default selector thread count, fallback to -1 (no selector thread)
* <p><strong>Note:</strong> In most use cases using a worker thread also as a selector thread works well.
* A possible use case for specifying a separate selector thread might be when the worker threads are too busy
* and connections cannot be accepted fast enough.
* <p><strong>Note:</strong> Although more than 1 can be configured as a selector thread count, in reality
* only 1 thread will be used as a selector thread.
*/
public static final String IO_SELECT_COUNT = "reactor.netty.ioSelectCount";
/**
* Default worker thread count for UDP, fallback to available processor
* (but with a minimum value of 4).
*/
public static final String UDP_IO_THREAD_COUNT = "reactor.netty.udp.ioThreadCount";
/**
* Default quiet period that guarantees that the disposal of the underlying LoopResources
* will not happen, fallback to 2 seconds.
*/
public static final String SHUTDOWN_QUIET_PERIOD = "reactor.netty.ioShutdownQuietPeriod";
/**
* Default maximum amount of time to wait until the disposal of the underlying LoopResources
* regardless if a task was submitted during the quiet period, fallback to 15 seconds.
*/
public static final String SHUTDOWN_TIMEOUT = "reactor.netty.ioShutdownTimeout";
/**
* Default value whether the native transport (epoll, io_uring, kqueue) will be preferred,
* fallback it will be preferred when available.
* <p><strong>Note:</strong> On {@code Linux}, {@code Epoll} will be preferred by default.
* If {@code IO_Uring} needs to be configured, a dependency to {@code io.netty:netty-transport-native-io_uring}
* has to be added.
*/
public static final String NATIVE = "reactor.netty.native";
如果您需要更改这些设置,可以应用以下配置:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.resources.LoopResources;
public class Application {
public static void main(String[] args) {
LoopResources loop = LoopResources.create("event-loop", 1, 4, true);
DisposableServer server =
HttpServer.create()
.runOn(loop)
.bindNow();
server.onDispose()
.block();
}
}
7.2.1. 释放事件循环组
- 如果您使用 Reactor Netty 提供的默认
Event Loop Group,请调用 HttpResources 的#disposeLoopsAndConnections/#disposeLoopsAndConnectionsLater方法。
释放 HttpResources 意味着正在使用它的每个服务器/客户端都将无法再使用它!
- 如果您使用自定义
LoopResources,请调用 LoopResources 的#dispose/#disposeLater方法。
释放自定义 LoopResources 意味着配置为使用它的每个服务器/客户端都将无法再使用它!
8. SSL 与 TLS
当您需要 SSL 或 TLS 时,可以应用下例所示的配置。默认情况下,如果 OpenSSL 可用,则使用 SslProvider.OPENSSL 作为提供方;否则使用 SslProvider.JDK。您可以通过 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();
}
}
8.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();
}
}
9. HTTP 日志
您可以通过编程方式或配置来启用 HTTP 访问日志或错误日志。默认情况下是禁用的。
9.1. 访问日志
您可以使用 -Dreactor.netty.http.server.accessLogEnabled=true 通过配置来启用 HTTP 访问日志。
您可以使用以下配置(适用于 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>
以下示例通过编程方式启用它:
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();
}
}
调用此方法的优先级高于系统属性配置。
默认情况下,日志格式为 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();
}
}
您还可以使用 AccessLogFactory#createFilter 方法过滤 HTTP 访问日志,如下例所示:
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/"), 1
x -> AccessLog.create("method={}, uri={}", x.method(), x.uri()))) 2
.bindNow();
server.onDispose()
.block();
}
}
指定要使用的过滤谓词
指定要应用的自定义格式
9.2. 错误日志
您可以使用 -Dreactor.netty.http.server.errorLogEnabled=true 通过配置来启用 HTTP 错误日志。
您可以使用以下配置(适用于 Logback 或类似日志框架)来获得独立的 HTTP 错误日志文件:
<appender name="errorLog" class="ch.qos.logback.core.FileAppender">
<file>error_log.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="errorLog" />
</appender>
<logger name="reactor.netty.http.server.ErrorLog" level="ERROR" additivity="false">
<appender-ref ref="async"/>
</logger>
以下示例通过编程方式启用它:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.errorLog(true)
.bindNow();
server.onDispose()
.block();
}
}
调用此方法的优先级高于系统属性配置。
默认情况下,日志格式为 [{datetime}] [pid {PID}] [client {remote address}] {exception message},但您可以通过参数指定自定义格式,如下例所示:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.http.server.logging.error.ErrorLog;
public class CustomLogErrorFormatApplication {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.errorLog(true, x -> ErrorLog.create("method={}, uri={}", x.httpServerInfos().method(), x.httpServerInfos().uri()))
.bindNow();
server.onDispose()
.block();
}
}
您还可以使用 ErrorLogFactory#createFilter 方法过滤 HTTP 错误日志,如下例所示:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.http.server.logging.error.ErrorLogFactory;
public class FilterLogErrorApplication {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.errorLog(true, ErrorLogFactory.createFilter(p -> p.cause() instanceof RuntimeException))
.bindNow();
server.onDispose()
.block();
}
}
请注意,此方法也可以接受自定义格式参数,如下例所示:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.http.server.logging.error.ErrorLog;
import reactor.netty.http.server.logging.error.ErrorLogFactory;
public class CustomFormatAndFilterErrorLogApplication {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.errorLog(true, ErrorLogFactory.createFilter(p -> p.cause() instanceof RuntimeException, 1
x -> ErrorLog.create("method={}, uri={}", x.httpServerInfos().method(), x.httpServerInfos().uri()))) 2
.bindNow();
server.onDispose()
.block();
}
}
指定要使用的过滤谓词
指定要应用的自定义格式
10. HTTP/2
默认情况下,HTTP 服务器支持 HTTP/1.1。如果您需要 HTTP/2,可以通过配置获得。除了协议配置之外,如果您需要 H2 而不需要 H2C(明文),还必须配置 SSL。
由于 JDK8 并不“开箱即用”地支持应用层协议协商(ALPN)(尽管部分厂商已将 ALPN 移植回 JDK8),您需要额外依赖一个支持它的原生库——例如 netty-tcnative-boringssl-static。
以下清单给出了一个简单的 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)
.protocol(HttpProtocol.H2) 1
.secure(spec -> spec.sslContext(http2SslContextSpec)) 2
.handle((request, response) -> response.sendString(Mono.just("hello")))
.bindNow();
server.onDispose()
.block();
}
}
配置服务器仅支持
HTTP/2配置
SSL
现在应用程序应表现为:
$ curl --http2 https://localhost:8080 -i
HTTP/2 200
hello
以下清单给出了一个简单的 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();
}
}
现在应用程序应表现为:
$ curl --http2-prior-knowledge http://localhost:8080 -i
HTTP/2 200
hello
10.1. 协议选择
/**
* The default supported HTTP protocol by HttpServer and HttpClient.
*/
HTTP11,
/**
* HTTP/2.0 support with TLS
* <p>If used along with HTTP/1.1 protocol, HTTP/2.0 will be the preferred protocol.
* While negotiating the application level protocol, HTTP/2.0 or HTTP/1.1 can be chosen.
* <p>If used without HTTP/1.1 protocol, HTTP/2.0 will always be offered as a protocol
* for communication with no fallback to HTTP/1.1.
*/
H2,
/**
* HTTP/2.0 support with clear-text.
* <p>If used along with HTTP/1.1 protocol, will support H2C "upgrade":
* Request or consume requests as HTTP/1.1 first, looking for HTTP/2.0 headers
* and {@literal Connection: Upgrade}. A server will typically reply a successful
* 101 status if upgrade is successful or a fallback HTTP/1.1 response. When
* successful the client will start sending HTTP/2.0 traffic.
* <p>If used without HTTP/1.1 protocol, will support H2C "prior-knowledge": Doesn't
* require {@literal Connection: Upgrade} handshake between a client and server but
* fallback to HTTP/1.1 will not be supported.
*/
H2C,
/**
* HTTP/3.0 support.
* @since 1.2.0
*/
HTTP3
}
11. HTTP/3
默认情况下,HTTP 服务器支持 HTTP/1.1。如果您需要 HTTP/3,可以通过配置获得。除了协议配置之外,您还需要添加对 io.netty:netty-codec-native-quic 的依赖。
以下清单给出了一个简单的 HTTP3 示例:
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.Http3SslContextSpec;
import reactor.netty.http.HttpProtocol;
import reactor.netty.http.server.HttpServer;
import java.io.File;
import java.time.Duration;
public class Application {
public static void main(String[] args) throws Exception {
File certChainFile = new File("certificate chain file");
File keyFile = new File("private key file");
Http3SslContextSpec serverCtx = Http3SslContextSpec.forServer(keyFile, null, certChainFile);
DisposableServer server =
HttpServer.create()
.port(8080)
.protocol(HttpProtocol.HTTP3) 1
.secure(spec -> spec.sslContext(serverCtx)) 2
.http3Settings(spec -> spec.idleTimeout(Duration.ofSeconds(5)) 3
.maxData(10000000)
.maxStreamDataBidirectionalLocal(1000000)
.maxStreamDataBidirectionalRemote(1000000)
.maxStreamsBidirectional(100))
.handle((request, response) -> response.header("server", "reactor-netty")
.sendString(Mono.just("hello")))
.bindNow();
server.onDispose()
.block();
}
}
配置服务器仅支持
HTTP/3配置
SSL配置
HTTP/3设置
现在应用程序应表现为:
$ curl --http3 https://localhost:8080 -i
HTTP/3 200
server: reactor-netty
content-length: 5
hello
12. 指标
HTTP 服务器支持与 Micrometer 的内置集成。它以 reactor.netty.http.server 为前缀暴露所有指标。
下表提供了 HTTP 服务器指标的相关信息:
| 指标名称 | 类型 | 说明 |
|---|---|---|
| reactor.netty.http.server.streams.active | Gauge | 活动的 HTTP/2 流数量。请参阅 Streams Active |
| reactor.netty.http.server.connections.active | Gauge | 当前正在处理请求的 http 连接数。请参阅 Connections Active |
| reactor.netty.http.server.connections.total | Gauge | 已打开的全部连接数。请参阅 Connections Total |
| reactor.netty.http.server.data.received | DistributionSummary | 接收的数据量(字节)。请参阅 Data Received |
| reactor.netty.http.server.data.sent | DistributionSummary | 发送的数据量(字节)。请参阅 Data Sent |
| reactor.netty.http.server.errors | Counter | 发生的错误数量。请参阅 Errors Count |
| reactor.netty.http.server.data.received.time | Timer | 消费入站数据所花费的时间。请参阅 Http Server Data Received Time |
| reactor.netty.http.server.data.sent.time | Timer | 发送出站数据所花费的时间。请参阅 Http Server Data Sent Time |
| reactor.netty.http.server.response.time | Timer | 请求/响应的总时间。请参阅 Http Server Response Time |
还有以下附加指标可用:
ByteBufAllocator 指标
| 指标名称 | 类型 | 说明 |
|---|---|---|
| reactor.netty.bytebuf.allocator.used.heap.memory | Gauge | 堆缓冲区分配器预留的字节数。请参阅 Used Heap Memory |
| reactor.netty.bytebuf.allocator.used.direct.memory | Gauge | 直接缓冲区分配器预留的字节数。请参阅 Used Direct Memory |
| reactor.netty.bytebuf.allocator.heap.arenas | Gauge | 堆 arena 数量(当使用 PooledByteBufAllocator 时)。请参阅 Heap Arenas |
| reactor.netty.bytebuf.allocator.direct.arenas | Gauge | 直接 arena 数量(当使用 PooledByteBufAllocator 时)。请参阅 Direct Arenas |
| reactor.netty.bytebuf.allocator.threadlocal.caches | Gauge | 线程本地缓存数量(当使用 PooledByteBufAllocator 时)。请参阅 Thread Local Caches |
| reactor.netty.bytebuf.allocator.small.cache.size | Gauge | 小型缓存的大小(当使用 PooledByteBufAllocator 时)。请参阅 Small Cache Size |
| reactor.netty.bytebuf.allocator.normal.cache.size | Gauge | 普通缓存的大小(当使用 PooledByteBufAllocator 时)。请参阅 Normal Cache Size |
| reactor.netty.bytebuf.allocator.chunk.size | Gauge | arena 的块大小(当使用 PooledByteBufAllocator 时)。请参阅 Chunk Size |
| reactor.netty.bytebuf.allocator.active.heap.memory | Gauge | 从堆缓冲池分配的在用缓冲区实际消耗的字节数(当使用 PooledByteBufAllocator 时)。请参阅 Active Heap Memory |
| reactor.netty.bytebuf.allocator.active.direct.memory | Gauge | 从直接缓冲池分配的在用缓冲区实际消耗的字节数(当使用 PooledByteBufAllocator 时)。请参阅 Active Direct Memory |
EventLoop 指标
| 指标名称 | 类型 | 说明 |
|---|---|---|
| reactor.netty.eventloop.pending.tasks | Gauge | 事件循环上待处理的任务数量。请参阅 Pending Tasks |
以下示例启用了该集成:
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 1
.config()
.meterFilter(MeterFilter.maximumAllowableTags("reactor.netty.http.server", "URI", 100, MeterFilter.deny()));
DisposableServer server =
HttpServer.create()
.metrics(true, s -> {
if (s.startsWith("/stream/")) { 2
return "/stream/{n}";
}
else if (s.startsWith("/bytes/")) {
return "/bytes/{n}";
}
return s;
}) 3
.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();
}
}
对带有
URI标签的计量器应用上限在可能时使用模板化 URI 作为 URI 标签的值
启用与 Micrometer 的内置集成
为避免启用指标带来的内存与 CPU 开销,重要的是在可能时将真实 URI 转换为模板化 URI。若不转换为类似模板的形式,每个不同的 URI 都会产生一个不同的标签,从而占用大量指标内存。
务必为带有 URI 标签的计量器应用上限。当真实 URI 无法模板化时,对计量器数量配置上限会有所帮助。有关更多信息,请参阅 maximumAllowableTags。
当需要将 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()
.metrics(true, CustomHttpServerMetricsRecorder::new) 1
.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 服务器指标,并提供 HttpServerMetricsRecorder 实现。
13. 追踪
HTTP 服务器支持与 Micrometer Tracing 的内置集成。
下表提供了 HTTP 服务器跨度的相关信息:
| 上下文名称 | 说明 |
|---|---|
<HTTP METHOD>_<URI> | 请求的信息与总时间。请参阅 Http Server Response Span。 |
以下示例启用了该集成。本示例使用 Brave,并将信息上报到 Zipkin。有关 OpenTelemetry 的设置,请参阅 Micrometer Tracing 文档。
import brave.Tracing;
import brave.propagation.StrictCurrentTraceContext;
import brave.sampler.Sampler;
import io.micrometer.tracing.CurrentTraceContext;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.brave.bridge.BraveBaggageManager;
import io.micrometer.tracing.brave.bridge.BraveCurrentTraceContext;
import io.micrometer.tracing.brave.bridge.BravePropagator;
import io.micrometer.tracing.brave.bridge.BraveTracer;
import io.micrometer.tracing.propagation.Propagator;
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.observability.ReactorNettyPropagatingReceiverTracingObservationHandler;
import reactor.netty.http.server.HttpServer;
import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
import zipkin2.reporter.urlconnection.URLConnectionSender;
import static reactor.netty.Metrics.OBSERVATION_REGISTRY;
public class Application {
public static void main(String[] args) {
init(); 1
DisposableServer server =
HttpServer.create()
.metrics(true, s -> {
if (s.startsWith("/stream/")) { 2
return "/stream/{n}";
}
return s;
}) 3
.route(r -> r.get("/stream/{n}",
(req, res) -> res.sendString(Mono.just(req.param("n")))))
.bindNow();
server.onDispose()
.block();
}
/**
* This setup is based on
* <a href="https://micrometer.io/docs/tracing#_micrometer_tracing_brave_setup">Micrometer Tracing Brave Setup</a>.
*/
static void init() {
AsyncZipkinSpanHandler spanHandler = AsyncZipkinSpanHandler
.create(URLConnectionSender.create("http://localhost:9411/api/v2/spans"));
StrictCurrentTraceContext braveCurrentTraceContext = StrictCurrentTraceContext.create();
CurrentTraceContext bridgeContext = new BraveCurrentTraceContext(braveCurrentTraceContext);
Tracing tracing =
Tracing.newBuilder()
.currentTraceContext(braveCurrentTraceContext)
.supportsJoin(false)
.traceId128Bit(true)
.sampler(Sampler.ALWAYS_SAMPLE)
.addSpanHandler(spanHandler)
.localServiceName("reactor-netty-examples")
.build();
brave.Tracer braveTracer = tracing.tracer();
Tracer tracer = new BraveTracer(braveTracer, bridgeContext, new BraveBaggageManager());
Propagator propagator = new BravePropagator(tracing);
OBSERVATION_REGISTRY.observationConfig()
.observationHandler(new ReactorNettyPropagatingReceiverTracingObservationHandler(tracer, propagator));
}
}
初始化 Brave、Zipkin 以及 Observation 注册表。
在可能时使用模板化 URI 作为 URI 标签的值。
启用与 Micrometer 的内置集成。
Zipkin 中的结果如下所示:

13.1. 获取当前观测
Micrometer 项目提供了一个库,用于在不同类型的上下文机制(如 ThreadLocal、Reactor Context 等)之间协助进行上下文传播。
以下示例展示了如何在自定义 ChannelHandler 中使用该库:
import brave.Tracing;
import brave.propagation.StrictCurrentTraceContext;
import brave.sampler.Sampler;
import io.micrometer.context.ContextSnapshot;
import io.micrometer.context.ContextSnapshotFactory;
import io.micrometer.tracing.CurrentTraceContext;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.brave.bridge.BraveBaggageManager;
import io.micrometer.tracing.brave.bridge.BraveCurrentTraceContext;
import io.micrometer.tracing.brave.bridge.BravePropagator;
import io.micrometer.tracing.brave.bridge.BraveTracer;
import io.micrometer.tracing.propagation.Propagator;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.observability.ReactorNettyPropagatingReceiverTracingObservationHandler;
import reactor.netty.http.server.HttpServer;
import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
import zipkin2.reporter.urlconnection.URLConnectionSender;
import static reactor.netty.Metrics.OBSERVATION_REGISTRY;
public class Application {
public static void main(String[] args) {
init(); 1
DisposableServer server =
HttpServer.create()
.metrics(true, s -> {
if (s.startsWith("/stream/")) { 2
return "/stream/{n}";
}
return s;
}) 3
.doOnConnection(conn -> conn.addHandlerLast(CustomChannelOutboundHandler.INSTANCE)) 4
.route(r -> r.get("/stream/{n}",
(req, res) -> res.sendString(Mono.just(req.param("n")))))
.bindNow();
server.onDispose()
.block();
}
static final class CustomChannelOutboundHandler extends ChannelOutboundHandlerAdapter {
static final ChannelHandler INSTANCE = new CustomChannelOutboundHandler();
@Override
public boolean isSharable() {
return true;
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
try (ContextSnapshot.Scope scope = ContextSnapshotFactory.builder().build().setThreadLocalsFrom(ctx.channel())) {
System.out.println("Current Observation in Scope: " + OBSERVATION_REGISTRY.getCurrentObservation());
//"FutureReturnValueIgnored" this is deliberate
ctx.write(msg, promise);
}
System.out.println("Current Observation: " + OBSERVATION_REGISTRY.getCurrentObservation());
}
}
初始化 Brave、Zipkin 以及 Observation 注册表。
在可能时使用模板化 URI 作为 URI 标签的值。
启用与 Micrometer 的内置集成。
使用上下文传播库的自定义
ChannelHandler。本示例仅重写了ChannelOutboundHandlerAdapter#write;如有需要,其余方法也可使用相同逻辑。此外,本示例会为给定Channel中存在值的所有ThreadLocal值进行设置;如需其他行为,请查阅上下文传播库 API。例如,您可能只想设置部分ThreadLocal值。
当您在某个框架内启用 Reactor Netty 追踪时,可能需要让 Reactor Netty 使用该框架创建的 ObservationRegistry。为此,您需要调用 reactor.netty.Metrics#observationRegistry。您可能还需要使用该框架提供的 API 来配置 Reactor Netty 的 ObservationHandlers。
14. Unix 域套接字
当使用原生传输时,HTTP 服务器在所有 Java 版本上都支持 Unix 域套接字(UDS);当使用 NIO 传输时,则在 Java 17 及以上版本上支持。
以下示例展示了如何使用 UDS 支持:
import io.netty.channel.unix.DomainSocketAddress;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
//import java.net.UnixDomainSocketAddress;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
// The configuration below is available only when Epoll/KQueue transport is used
.bindAddress(() -> new DomainSocketAddress("/tmp/test.sock")) 1
// The configuration below is available only when NIO transport is used with Java 17+
//.bindAddress(() -> UnixDomainSocketAddress.of("/tmp/test.sock"))
.bindNow();
server.onDispose()
.block();
}
}
指定要使用的
DomainSocketAddress
15. 超时配置
本节介绍可在 HttpServer 中使用的各种超时配置选项。配置恰当的超时可以改善或解决通信过程中的问题。这些配置选项可分组如下:
15.1. 请求超时
以下清单列出了所有可用的请求超时配置选项。
-
readTimeout- 读取给定请求内容时,每次网络级读取操作之间的最大时间(精度:ms) -
requestTimeout- 读取给定请求内容的最大时间(精度:ms)。
配置读取/请求超时始终是一种良好实践。
要自定义默认设置,可以按如下方式配置 HttpServer:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import java.time.Duration;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.readTimeout(Duration.ofSeconds(5)) 1
.requestTimeout(Duration.ofSeconds(30)) 2
.handle((request, response) -> request.receive().then())
.bindNow();
server.onDispose()
.block();
}
}
将读取超时配置为 5 秒。
将请求超时配置为 30 秒。
15.2. 连接超时
以下清单列出了所有可用的连接超时配置选项。
idleTimeout- 此连接保持打开并等待 HTTP 请求的最大时间(精度:ms)。一旦达到该超时,连接即被关闭。默认情况下未指定 idleTimeout,这表示不超时(即无限),意味着只有当某一对方显式决定关闭时连接才会被关闭。
配置空闲超时始终是一种良好实践。
要自定义默认设置,可以按如下方式配置 HttpServer:
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import java.time.Duration;
public class Application {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.idleTimeout(Duration.ofSeconds(1)) 1
.bindNow();
server.onDispose()
.block();
}
}
将默认空闲超时配置为 1 秒。
15.3. SSL/TLS 超时
HttpServer 支持 Netty 提供的 SSL/TLS 功能。
以下列表描述了可用的超时配置选项:
handshakeTimeout- 使用此选项配置 SSL 握手超时(精度:ms)。默认值:10s。
当预计会出现缓慢的网络连接时,应考虑增大 SSL 握手超时。
-
closeNotifyFlushTimeout- 使用此选项配置 SSLclose_notify刷写超时(精度:ms)。默认值:3s。 -
closeNotifyReadTimeout- 使用此选项配置 SSLclose_notify读取超时(精度:ms)。默认值:0s。
要自定义默认设置,可以按如下方式配置 HttpServer:
import reactor.netty.DisposableServer;
import reactor.netty.http.Http11SslContextSpec;
import reactor.netty.http.server.HttpServer;
import java.io.File;
import java.time.Duration;
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)
.handshakeTimeout(Duration.ofSeconds(30)) 1
.closeNotifyFlushTimeout(Duration.ofSeconds(10)) 2
.closeNotifyReadTimeout(Duration.ofSeconds(10))) 3
.bindNow();
server.onDispose()
.block();
}
}
将 SSL 握手超时配置为 30 秒。
将 SSL
close_notify刷写超时配置为 10 秒。将 SSL
close_notify读取超时配置为 10 秒。