目录
Netty HTTP客户端(HttpClient)
Reactor Netty提供了易于使用和易于配置的HttpClient。它隐藏了创建HTTP客户端所需的大部分 Netty功能,并添加了Reactive Streams背压(Reactive Streams是具有无阻塞背压的异步流处理的标准)
连接
要将HTTP客户端连接到给定HTTP端点,必须创建并配置一个HttpClient实例:
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client = HttpClient.create(); // 创建一个HttpClient实例
client.get() // 指定GET将使用的方法
.uri("https://example.com/") // 指定路径
.response() // 获取响应HttpClientResponse
.block();
}
}
使用WebSocket:
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import reactor.core.publisher.Flux;
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client = HttpClient.create();
client.websocket()
.uri("wss://echo.websocket.org")
.handle((inbound, outbound) -> {
inbound.receive()
.asString()
.take(1)
.subscribe(System.out::println);
final byte[] msgBytes = "hello".getBytes(CharsetUtil.ISO_8859_1);
return outbound.send(Flux.just(Unpooled.wrappedBuffer(msgBytes), Unpooled.wrappedBuffer(msgBytes)))
.neverComplete();
})
.blockLast();
}
}
主机和端口
为了连接到特定的主机和端口,可以将以下配置应用到HTTP客户端:
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
.host("example.com") // 配置HTTP主机
.port(80); // 配置HTTP端口
client.get()
.uri("/")
.response()
.block();
}
}
急切初始化
默认情况下,HttpClient资源的初始化是按需进行的。这意味着first request吸收了初始化和加载所需的额外时间:
(1)事件循环组
(2)主机名解析器
(3)本机传输库(使用本机传输时)
(4)用于安全性的本机库(在OpenSsl的情况下)
当需要预加载这些资源时,可以进行HttpClient如下配置:
import reactor.core.publisher.Mono;
import reactor.netty.ByteBufFlux;
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client = HttpClient.create();
// 初始化并加载事件循环组、主机名解析器、本机传输库和用于安全性的本机库
client.warmup()
.block();
// 主机名解析发生在第一个请求中。在此示例中,使用了连接池,因此在第一个请求中建立了到URL的连接,对同一URL的后续请求会重用连接池中的连接
client.post()
.uri("https://example.com/")
.send(ByteBufFlux.fromString(Mono.just("hello")))
.response()
.block();
}
}
写入数据
要将数据发送到给定的HTTP端点,可以使用send(Publisher)方法提供发布服务器。默认情况下,Transfer Encoding:chunked应用于那些需要请求主体的HTTP方法。通过请求头提供的内容长度禁用传输编码:必要时分块。以下示例发送hello:
import reactor.core.publisher.Mono;
import reactor.netty.ByteBufFlux;
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client = HttpClient.create();
client.post()
.uri("https://example.com/")
// 将hello字符串发送到给定的HTTP端点
.send(ByteBufFlux.fromString(Mono.just("hello")))
.response()
.block();
}
}
1.添加标题和其他元数据
向给定HTTP端点发送数据时,可能需要发送额外的headers、cookie和其他元数据:
import io.netty.handler.codec.http.HttpHeaderNames;
import reactor.core.publisher.Mono;
import reactor.netty.ByteBufFlux;
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
// 禁用Transfer-Encoding: chunked并提供Content-Length标头
.headers(h -> h.set(HttpHeaderNames.CONTENT_LENGTH, 5));
client.post()
.uri("https://example.com/")
.send(ByteBufFlux.fromString(Mono.just("hello")))
.response()
.block();
}
}
2.压缩
可以在HTTP客户端启用压缩,这意味着将请求标头Accept-Encoding添加到请求标头中:
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
.compress(true);
client.get()
.uri("https://example.com/")
.response()
.block();
}
}
3.自动重定向支持
可以配置HTTP客户端以启用自动重定向支持。
Reactor Netty提供了两种不同的自动重定向支持策略:
(1)followRedirect(boolean)
指定是否为statuses启用HTTP自动重定向支持301|302|307|308。
(2)followRedirect(BiPredicate<HttpClientRequest, HttpClientResponse>)
如果提供的谓词匹配,则启用自动重定向支持
使用followRedirect(true):
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
.followRedirect(true);
client.get()
.uri("https://example.com/")
.response()
.block();
}
}
消费数据
要从给定HTTP端点接收数据,可以使用HttpClient.ResponseReceiver,使用该responseContent():
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client = HttpClient.create();
client.get()
.uri("https://example.com/")
.responseContent() // 从给定HTTP端点接收数据
.aggregate() // 聚合数据
.asString() // 将数据转换为字符串
.block();
}
}
1.读取标题和其他元数据
从给定HTTP端点接收数据时,可以检查响应标头、状态代码和其他元数据。可以使用HttpClientResponse获取此附加元数据:
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client = HttpClient.create();
client.get()
.uri("https://example.com/")
.responseSingle((resp, bytes) -> {
System.out.println(resp.status()); // 获取状态码
return bytes.asString();
})
.block();
}
}
2.HTTP响应解码器
默认情况下,Netty为传入的响应配置一些限制。详细 -> HttpResponseDecoder
(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; // 默认验证Headers
public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128; // 默认初始缓冲区大小
// 默认允许重复内容长度
public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;
HttpResponseDecoderSpec.java
// 缺少响应时默认失败
public static final boolean DEFAULT_FAIL_ON_MISSING_RESPONSE = false;
// 连接请求后默认解析HTTP
public static final boolean DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST = false;
/**
* HTTP/2.0明文升级请求内容的最大长度。默认情况下,客户端将允许升级请求,最多65536作为聚合内容的最大长度
*/
public static final int DEFAULT_H2C_MAX_CONTENT_LENGTH = 65536;
当需要更改这些默认设置时,可以HTTP按如下方式配置客户端:
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
// 所有标头的最大长度将为16384. 当超过此值时, 会引发TooLongFrameException
.httpResponseDecoder(spec -> spec.maxHeaderSize(16384));
client.get()
.uri("https://example.com/")
.responseContent()
.aggregate()
.asString()
.block();
}
}
生命周期回调
提供了以下生命周期回调以扩展HttpClient
回调函数 | 描述 |
---|---|
doAfterRequest | 在发送请求时调用 |
doAfterResolve | 在成功解析远程地址后调用 |
doAfterResponseSuccess | 在完全收到响应后调用 |
doOnChannelInit | 初始化通道时调用 |
doOnConnect | 当通道即将连接时调用 |
doOnConnected | 在通道连接后调用 |
doOnDisconnected | 在通道断开连接后调用 |
doOnError | 当请求尚未发送且未完全收到响应时调用 |
doOnRedirect | 当收到响应头并且请求即将被重定向时调用 |
doOnRequest | 在即将发送请求时调用 |
doOnRequestError | 在请求尚未发送时调用 |
doOnResolve | 在即将解析远程地址时调用 |
doOnResolveError | 在远程地址未成功解析的情况下调用 |
doOnResponse | 在收到响应标头后调用 |
doOnResponseError | 在未完全收到响应时调用 |
使用doOnConnected和doOnChannelInit回调:
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import reactor.netty.http.client.HttpClient;
import java.util.concurrent.TimeUnit;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
// NettyReadTimeoutHandler当通道被连接时,管道被扩展
.doOnConnected(conn ->
conn.addHandler(new ReadTimeoutHandler(10, TimeUnit.SECONDS)))
// Netty管道LoggingHandler在初始化通道时扩展
.doOnChannelInit((observer, channel, remoteAddress) ->
channel.pipeline()
.addFirst(new LoggingHandler("reactor.netty.examples")));
client.get()
.uri("https://example.com/")
.response()
.block();
}
}
TCP-level配置
当需要TCP级别的配置时,可以使用以下代码段来扩展默认TCP客户端配置(添加选项、绑定地址等):配置与TCP配置一致
(1)Setting Channel Options - 设置通道参数选项
当需要在TCP级别更改配置时,可以使用以下代码段来扩展默认TCP服务器配置:
import io.netty.channel.ChannelOption;
import io.netty.channel.epoll.EpollChannelOption;
//import io.netty.channel.socket.nio.NioChannelOption;
//import jdk.net.ExtendedSocketOptions;
import reactor.netty.http.client.HttpClient;
import java.net.InetSocketAddress;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
.bindAddress(() -> new InetSocketAddress("host", 1234))
// 将连接建立超时配置为10秒
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
// 启用TCP keepalive。这意味着TCPkeepalive在连接空闲一段时间后开始发送探测
.option(ChannelOption.SO_KEEPALIVE, true)
// 在TCP开始发送keepalive探测之前,连接需要保持空闲5分钟
.option(EpollChannelOption.TCP_KEEPIDLE, 300)
// 将各个keepalive探测之间的时间配置为1分钟
.option(EpollChannelOption.TCP_KEEPINTVL, 60)
// 将TCPkeepalive探测的最大数量配置为8
.option(EpollChannelOption.TCP_KEEPCNT, 8);
/*
以下选项仅在使用NIO传输(Java11)时可用
.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPIDLE), 300)
.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPINTERVAL), 60)
.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPCOUNT),
以下选项仅在使用Epoll传输时可用
*/
String response =
client.get()
.uri("https://example.com/")
.responseContent()
.aggregate()
(2)Wire Logger:连线日志记录
Reactor Netty提供线路日志记录,用于何时需要检查对等点之间的流量。默认情况下,连线日志记录处于禁用状态。要启用它,必须将记录器reactor.netty.http.client.HttpClient级别设置为DEBUG并应用以下配置:
Reactor Netty支持3种不同的格式化程序:
连线日志格式化 | 描述 |
---|---|
AdvancedByteBufFormat#HEX_DUM | 同时记录事件和内容。内容将采用十六进制格式(默认) |
AdvancedByteBufFormat#SIMPLE | 使用此格式启用连线记录时,仅记录事件 |
AdvancedByteBufFormat#TEXTUAL | 同时记录事件和内容。内容将采用纯文本格式 |
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
.wiretap(true); // 启用连线记录
client.get()
.uri("https://example.com/")
.response()
.block();
}
}
连接池
默认情况下,Reactor Netty client(客户端)使用一个“固定”连接池,其中500个为活动通道的最大数量(Channel),1000个为允许保持挂起状态的进一步通道采集尝试的最大数量(对于其余配置,请检查下面的系统属性或构建器配置)。这意味着,如果有人试图获取一个通道,只要创建的通道少于500个,并且由池管理,那么实现就会创建一个新的通道。当达到池中通道的最大数量时,最多会延迟1000次获取通道的新尝试(挂起),直到通道再次返回到池中,并且会因错误而拒绝进一步尝试。
默认“固定”连接池:
①500个为活动通道Channel)
②1000个为允许保持挂起状态的进一步通道采集尝试的最大数量
ReactorNetty.java
/**
*默认最大连接数。退回到2个可用处理器(但最小值为16)
*/
public static final String POOL_MAX_CONNECTIONS = "reactor.netty.pool.maxConnections";
/**
* Default acquisition timeout (milliseconds) before error. If -1 will never wait to
* acquire before opening a new
* connection in an unbounded fashion. Fallback 45 seconds
*/
public static final String POOL_ACQUIRE_TIMEOUT = "reactor.netty.pool.acquireTimeout";
/**
* 默认最大空闲时间,回退-未指定最大空闲时间
*/
public static final String POOL_MAX_IDLE_TIME = "reactor.netty.pool.maxIdleTime";
/**
* 默认最大使用寿命,默认未指定。
*/
public static final String POOL_MAX_LIFE_TIME = "reactor.netty.pool.maxLifeTime";
/**
默认租赁策略(先进先出、后进先出),默认fifo-先进先出
fifo - 连接选择为先进先出
lifo - 连接选择为后进先出
*/
public static final String POOL_LEASING_STRATEGY = "reactor.netty.pool.leasingStrategy";
/**
*默认使用SamplingAllocationStrategy}#getPermitsSamplingRate(介于0d和1d之间(百分比))
此策略包装了PoolBuilder#sizeBetween(int,int) 、AllocationStrategy、以及对AllocationStrategy#getpermissions(int)的调用示例。
*回退 - 未启用。
*/
public static final String POOL_GET_PERMITS_SAMPLING_RATE = "reactor.netty.pool.getPermitsSamplingRate";
/**
* 默认使用SamplingAllocationStrategy#returnPermitsSamplingRate(介于0d和1d之间(百分比))
此策略包装了PoolBuilder#sizeBetween(int,int)、AllocationStrategy、以及对AllocationStrategy#returnpermissions(int)的调用示例
*回退-未启用采样。
*/
public static final String POOL_RETURN_PERMITS_SAMPLING_RATE = "reactor.netty.pool.returnPermitsSamplingRate";
当需要更改默认设置时,可以进行ConnectionProvider如下配置:
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import java.time.Duration;
public class Application {
public static void main(String[] args) {
ConnectionProvider provider =
ConnectionProvider.builder("custom")
.maxConnections(50)
// 将连接保持空闲的最长时间配置为20秒
.maxIdleTime(Duration.ofSeconds(20))
// 将连接保持活动的最长时间配置为60秒
.maxLifeTime(Duration.ofSeconds(60))
// 将挂起获取操作的最长时间配置为60秒
.pendingAcquireTimeout(Duration.ofSeconds(60))
// 每两分钟,将定期检查连接池中是否存在适用于删除的连接
.evictInBackground(Duration.ofSeconds(120))
.build();
HttpClient client = HttpClient.create(provider);
String response =
client.get()
.uri("https://example.com/")
.responseContent()
.aggregate()
.asString()
.block();
System.out.println("Response " + response);
provider.disposeLater()
.block();
}
}
当期望高负载时,请谨慎使用具有非常高的最大连接值的连接池。
reactor.netty.http.client.PrematureCloseException由于打开/获取的并发连接过多,可能会遇到 根本原因“连接超时”的异常。
需要禁用连接池,可以应用如下配置:
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client = HttpClient.newConnection();
String response =
client.get()
.uri("https://example.com/")
.responseContent()
.aggregate()
.asString()
.block();
System.out.println("Response " + response);
}
}
可用的配置:
(1)disposeInactivePoolsInBackground:检查连接池
启用此选项后,会在后台定期检查连接池,那些在指定时间内为空且不活动的连接池将有资格进行处理。默认情况下,禁用非活动池的这种后台处理。
(2)disposeTimeout:处理超时
当ConnectionProvider#dispose()或ConnectionProvider#disposeLater()被调用时,使用此宽限期超时触发连接池的正常关闭。从那时起,所有获取连接的调用都会快速失败,但会出现异常。但是,对于所提供的Duration,待处理的获取将有机会得到服务。注:拒绝新的获取和计时器立即启动风度,不论认购到Mono的返回ConnectionProvider#disposeLater()。随后的调用返回相同的Mono,有效地从第一个正常关闭调用中获取通知并忽略随后提供的超时。默认情况下,不指定处理超时。
(3)evictInBackground:定期检查符合删除条件的连接
启用此选项后,每个连接池都会根据驱逐标准定期检查符合删除条件的连接maxIdleTime。默认情况下,此后台驱逐是禁用的。
(4)fifo:默认租赁策略 - 先进先出
配置连接池,如果有空闲连接(即池未充分利用),下一次获取操作将获取Least Recently Used连接(LRU,即当前空闲连接中最先释放的连接)。默认租赁策略。
(5)lifo:租赁策略 - 后进先出
配置连接池,如果有空闲连接(即池未充分利用),下一次获取操作将获取Most Recently Used连接(MRU,即当前空闲连接中最后释放的连接)。
(6)maxConnections:连接数
开始挂起之前的最大连接数(每个连接池)。默认为 2 * 可用处理器数(但最小值为 16)。
(7)maxIdleTime:最大空闲时间
空闲时通道可以关闭的时间(分辨率:ms)。默认值:未指定最大空闲时间。
(8)maxLifeTime:最大寿命
通道有资格关闭的总生命周期(分辨率:ms)。默认值:未指定最大寿命。
(9)metrics:监控指标
启用/禁用与 Micrometer 的内置集成。ConnectionProvider.MeterRegistrar可以提供与另一个度量系统的集成。默认情况下,指标未启用。
(10)pendingAcquireMaxCount:挂起队列中的最大额外尝试次数
获取连接以保留在挂起队列中的最大额外尝试次数。如果指定 -1,则挂起队列没有上限。默认为 2 * 最大连接数。
(11)pendingAcquireTimeout:挂起获取必须完成或抛出TimeoutException之前的最长时间
挂起获取必须完成或抛出 TimeoutException 之前的最长时间(分辨率:毫秒)。如果指定了 -1,则不会应用此类超时。默认值:45秒
SSL和TLS
当需要SSL或TLS时,可以应用下一个示例中显示的配置。默认情况下,如果OpenSSL可用, 则使用SslProvider.OPENSSL提供程序作为提供程序。否则,将使用SslProvider.JDK提供程序您可以通过使用SslContextBuilder或通过设置来切换提供程序“-Dio.netty.handler.ssl.noOpenSsl=true”。
使用SslContextBuilder:
import reactor.netty.http.Http11SslContextSpec;
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
Http11SslContextSpec http11SslContextSpec = Http11SslContextSpec.forClient();
HttpClient client =
HttpClient.create()
.secure(spec -> spec.sslContext(http11SslContextSpec));
client.get()
.uri("https://example.com/")
.response()
.block();
}
}
(1)服务器名称指示
默认情况下,HTTP客户端将远程主机名作为SNI服务器名发送。当需要更改此默认设置时,您可以HTTP按如下方式配置客户端:
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import reactor.netty.http.client.HttpClient;
import javax.net.ssl.SNIHostName;
public class Application {
public static void main(String[] args) throws Exception {
SslContext sslContext = SslContextBuilder.forClient().build();
HttpClient client =
HttpClient.create()
.secure(spec -> spec.sslContext(sslContext)
.serverNames(new SNIHostName("test.com")));
client.get()
.uri("https://127.0.0.1:8080/")
.response()
.block();
}
}
重试策略
默认情况下,HTTP如果请求在TCP级别上中止,客户端会重试一次请求
HTTP/2
默认情况下,HTTP客户端支持HTTP/1.1. 如果需要HTTP/2,可以通过配置获取。除了协议配置,如果需要H2但不需要H2C(cleartext),还必须配置SSL。
由于应用层协议协商(ALPN)不受JDK8的“开箱即用”支持(尽管一些供应商将ALPN向后移植到 JDK8),因此需要额外依赖支持它的本机库——例如:netty-tcnative-boringssl-static
(1)简单的H2例子:
import io.netty.handler.codec.http.HttpHeaders;
import reactor.core.publisher.Mono;
import reactor.netty.http.HttpProtocol;
import reactor.netty.http.client.HttpClient;
import reactor.util.function.Tuple2;
public class H2Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
.protocol(HttpProtocol.H2) // 将客户端配置为仅支持HTTP/2
.secure(); // 配置SSL
Tuple2<String, HttpHeaders> response =
client.get()
.uri("https://example.com/")
.responseSingle((res, bytes) -> bytes.asString()
.zipWith(Mono.just(res.responseHeaders())))
.block();
System.out.println("Used stream ID: " + response.getT2().get("x-http2-stream-id"));
System.out.println("Response: " + response.getT1());
}
}
(2)简单的H2C例子:
import io.netty.handler.codec.http.HttpHeaders;
import reactor.core.publisher.Mono;
import reactor.netty.http.HttpProtocol;
import reactor.netty.http.client.HttpClient;
import reactor.util.function.Tuple2;
public class H2CApplication {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
.protocol(HttpProtocol.H2C);
Tuple2<String, HttpHeaders> response =
client.get()
.uri("http://localhost:8080/")
.responseSingle((res, bytes) -> bytes.asString()
.zipWith(Mono.just(res.responseHeaders())))
.block();
System.out.println("Used stream ID: " + response.getT2().get("x-http2-stream-id"));
System.out.println("Response: " + response.getT1());
}
}
协议选择: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
}
代理支持
Reactor Netty支持Netty提供的代理功能,并提供了通过ProxyProvider生成器指定非代理主机的方法。
Netty的HTTP代理支持始终使用CONNECT方法来建立到指定代理的隧道,而不考虑使用HTTP或https的方案。当方案为http时,某些代理可能不支持CONNECT方法,或者可能需要进行配置以支持这种通信方式。有时这可能是无法连接到代理的原因。考虑检查代理文档是否支持或需要附加配置以支持Connect方法。
使用ProxyProvider:
import reactor.netty.http.client.HttpClient;
import reactor.netty.transport.ProxyProvider;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
.proxy(spec -> spec.type(ProxyProvider.Proxy.HTTP)
.host("proxy")
.port(8080)
.nonProxyHosts("localhost")
// 将连接建立超时配置为20秒
.connectTimeoutMillis(20_000));
String response =
client.get()
.uri("https://example.com/")
.responseContent()
.aggregate()
.asString()
.block();
}
}
Metrics监控指标
HTTP客户端支持与Micrometer,它公开了前缀为reactor.netty.http.client的所有指标。
在应用程序中,通常会记录日志以便事后分析,在很多情况下是产生了问题之后,再去查看日志,是一种事后的静态分析。在很多时候,可能需要了解整个系统在当前,或者某一时刻运行的情况,比如一个系统后台服务,可能需要了解一些实时监控的数据
1、每秒钟的请求数是多少(TPS)?
2、平均每个请求处理的时间?
3、请求处理的最长耗时?
4、请求处理的响应的直方图?
5、请求处理正确响应率?
6、等待处理的请求队列长度?
7、查看整个系统的的CPU使用率、内存占用、jvm运行情况;以及系统运行出错率等等一系列的实时数据采集时,最简单的方法就是在系统的入口、出口和关键位置设置埋点,然后将采集到的信息发送到实时监控平台或者存入到缓存和DB中做进一步的分析和展示。
Metrics作为一款监控指标的度量类库,提供了许多工具帮助开发者来完成各项数据的监控。
Metrics提供5种基本的度量类型:Meters、Gauges、Counters、Histograms和Timers
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
public class Application {
public static void main(String[] args) {
ConnectionProvider provider =
ConnectionProvider.builder("custom")
.maxConnections(50)
.metrics(true) // 启用与Micrometer的内置集成
.build();
HttpClient client = HttpClient.create(provider);
String response =
client.get()
.uri("https://example.com/")
.responseContent()
.aggregate()
.asString()
.block();
System.out.println("Response " + response);
provider.disposeLater()
.block();
}
}
(1)HTTP客户端指标的信息
Metric名称 | 类型 | 描述 |
---|---|---|
reactor.netty.http.client.data.received | DistributionSummary | 接收的数据量,以字节为单位 |
reactor.netty.http.client.data.sent | DistributionSummary | 发送的数据量,以字节为单位 |
reactor.netty.http.client.errors | Counter | 发生的错误数 |
reactor.netty.http.client.tls.handshake.time | Timer | TLS 握手所花费的时间 |
reactor.netty.http.client.connect.time | Timer | 连接到远程地址所花费的时间 |
reactor.netty.http.client.address.resolver | Timer | 解析地址所花费的时间 |
reactor.netty.http.client.data.received.time | Timer | 消耗传入数据所花费的时间 |
reactor.netty.http.client.data.sent.time | Timer | 发送传出数据所花费的时间 |
reactor.netty.http.client.response.time | Timer | 请求/响应的总时间 |
(2)汇总ConnectionProvider指标
Metric名称 | 类型 | 描述 |
---|---|---|
reactor.netty.connection.provider.total.connections | Gauge | 所有连接数,活动或空闲 |
reactor.netty.connection.provider.active.connections | Gauge | 已成功获取且正在使用中的连接数 |
reactor.netty.connection.provider.max.connections | Gauge | 允许的最大活动连接数 |
reactor.netty.connection.provider.idle.connections | Gauge | 空闲连接数 |
reactor.netty.connection.provider.pending.connections | Gauge | 等待连接的请求数 |
reactor.netty.connection.provider.max.pending.connections | Gauge | 等待就绪连接时将排队的最大请求数 |
HTTP客户端指标配置为服务HTTP/2流量时的信息:
Metric名称 | 类型 | 描述 |
---|---|---|
reactor.netty.connection.provider.active.streams | Gauge | 活动HTTP/2流的数量 |
reactor.netty.connection.provider.pending.streams | Gauge | 等待打开 HTTP/2 流的请求数 |
(3)ByteBufAllocator指标
Metric名称 | 类型 | 描述 |
---|---|---|
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) |
(4)EventLoop指标
Metric名称 | 类型 | 描述 |
---|---|---|
reactor.netty.eventloop.pending.tasks | Gauge | 事件循环中待处理的任务数 |
启用该集成:
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.config.MeterFilter;
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
Metrics.globalRegistry // 对带URI标签的仪表应用上限
.config()
.meterFilter(MeterFilter.maximumAllowableTags("reactor.netty.http.client", "URI", 100, MeterFilter.deny()));
HttpClient client =
HttpClient.create()
.metrics(true, s -> {
// 模板化 URI 将尽可能用作 URI 标记值
if (s.startsWith("/stream/")) {
return "/stream/{n}";
}
else if (s.startsWith("/bytes/")) {
return "/bytes/{n}";
}
return s;
}); // 启用与Micrometer的内置集成
client.get()
.uri("https://httpbin.org/stream/2")
.responseContent()
.blockLast();
client.get()
.uri("https://httpbin.org/bytes/1024")
.responseContent()
.blockLast();
}
}
避免启用指标的内存和CPU开销
为了避免启用指标的内存和CPU开销,重要的是尽可能将真实URI转换为模板化URI。如果不转换为类似模板的形式,每个不同的URI都会导致创建一个不同的标签,这会为指标占用大量内存。
始终为带有URI标记的仪表应用上限。在实际URI无法模板化的情况下,配置仪表(meters)数量上限会有所帮助。可以在maximumAllowableTags上找到更多信息。
当需要HTTP客户端指标与系统集成时,Micrometer或者想提供自己的集成Micrometer,可以提供自己的指标记录器:
import reactor.netty.http.client.HttpClient;
import reactor.netty.http.client.HttpClientMetricsRecorder;
import java.net.SocketAddress;
import java.time.Duration;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
// 启用HTTP客户端指标并提供HttpClientMetricsRecorder实现
.metrics(true, CustomHttpClientMetricsRecorder::new);
client.get()
.uri("https://httpbin.org/stream/2")
.response()
.block();
}
}
## Unix域套接字 HTTP当使用本机传输时,客户端支持Unix域套接字(UDS)
如何使用UDS支持:
import io.netty.channel.unix.DomainSocketAddress;
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
// 指定DomainSocketAddress将被使用
.remoteAddress(() -> new DomainSocketAddress("/tmp/test.sock"));
client.get()
.uri("/")
.response()
.block();
}
}
主机名解析
默认情况下,HttpClient使用 Netty 的域名查找机制异步解析域名。这是JVM内置阻塞解析器的替代方案。
当需要更改默认设置时,可以进行HttpClient如下配置:
import reactor.netty.http.client.HttpClient;
import java.time.Duration;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
// 此解析器执行的每个DNS查询的超时将为500毫秒
.resolver(spec -> spec.queryTimeout(Duration.ofMillis(500)));
String response =
client.get()
.uri("https://example.com/")
.responseContent()
.aggregate()
.asString()
.block();
System.out.println("Response " + response);
}
}
切换到JVM内置解析器。进行HttpClient如下配置:
import io.netty.resolver.DefaultAddressResolverGroup;
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
// 设置JVM内置解析器。
.resolver(DefaultAddressResolverGroup.INSTANCE);
String response =
client.get()
.uri("https://example.com/")
.responseContent()
.aggregate()
.asString()
.block();
System.out.println("Response " + response);
}
}
可用的配置:
(1)cacheMaxTimeToLive:缓存的DNS资源记录的最大生存时间
缓存的DNS资源记录的最大生存时间(秒)。如果DNS服务器返回的DNS资源记录的生存时间大于此最大生存时间,则此解析器将忽略来自DNS服务器的生存时间并使用此最大生存时间。默认为Integer.MAX_VALUE.
(2)cacheMinTimeToLive:缓存的DNS资源记录的最小生存时间
缓存的DNS资源记录的最小生存时间(秒)。如果DNS服务器返回的DNS资源记录的生存时间小于此最小生存时间,则此解析器将忽略来自DNS服务器的生存时间并使用此最小生存时间。默认值:0。
(3)cacheNegativeTimeToLive:失败的DNS查询的缓存的生存时间
失败的DNS查询的缓存的生存时间(秒)。默认值:0。
(4)completeOncePreferredResolved:解析器查询完成后立即通知
启用此设置后,解析器会在首选地址类型的所有查询完成后立即通知。禁用此设置后,解析器会在所有可能的地址类型完成时发出通知。此配置适用于DnsNameResolver#resolveAll(String)。
默认情况下,启用此设置。
(5)disableOptionalRecord:禁用可选记录的自动包含
禁用可选记录的自动包含,该记录试图向远程DNS服务器提供有关解析器每个响应可以读取多少数据的提示。默认情况下,启用此设置。
(6)disableRecursionDesired:解析器是否必须发送设置了递归所需(RD)标志的DNS查询
指定此解析器是否必须发送设置了递归所需(RD)标志的DNS查询。默认情况下,启用此设置。
(7)hostsFileEntriesResolver:主机文件条目的自定义
设置HostsFileEntriesResolver用于主机文件条目的自定义。默认值:DefaultHostsFileEntriesResolver。
(8)maxPayloadSize:设置数据报包缓冲区的容量
设置数据报包缓冲区的容量(以byte字节为单位)。默认值:4096。
(9)maxQueriesPerResolve:解析主机名时允许发送的最大DNS查询数
设置解析主机名时允许发送的最大DNS查询数。默认值:16。
(10)ndots:进行初始绝对查询之前必须出现在名称中的点数
设置在进行初始绝对查询之前必须出现在名称中的点数。默认值:-1(确定来自Unix上的操作系统的值,否则使用值 1)。
(11)queryTimeout:解析器执行的每个DNS查询的超时时间
设置此解析器执行的每个DNS查询的超时时间(毫秒)。默认值:5000。
(12)resolvedAddressTypes:解析地址的协议族列表
解析地址的协议族列表
(13)bindAddressSupplier:绑定到的本地地址的供应商
要绑定到的本地地址的供应商
(14)roundRobinSelection:服务器提供的目的地地址循环选择
使一个AddressResolverGroup的DnsNameResolver如果多个由名称服务器提供的目的地地址的支持随机选择。见RoundRobinDnsAddressResolverGroup。默认:DnsAddressResolverGroup
(15)runOn:与给定的DNS服务器进行通信LoopResources
与给定的DNS服务器进行通信LoopResources。
默认情况下,使用在客户端级别指定的LoopResources。
(16)searchDomains:解析器的搜索域列表
解析器的搜索域列表。默认情况下,使用系统 DNS 搜索域填充有效搜索域列表。
(17)trace:解析器使用的特定记录器和日志级别
在解析失败的情况下生成详细跟踪信息时,此解析器将使用的特定记录器和日志级别。
超时配置
本节介绍可用于HttpClient. 配置适当的超时可能会改善或解决通信过程中的问题。配置选项可以分组如下:
1.连接池超时
默认情况下,HttpClient使用连接池。当一个请求成功完成并且该连接没有安排关闭时,该连接将返回到连接池,因此可以重新用于处理另一个请求。该连接可能会立即为另一个请求重用,或者可能会在连接池中闲置一段时间。
可用的超时配置选项:
默认情况下,在连接release或acquire操作时检查这些超时,如果达到某个超时,连接将关闭并从连接池中删除。但是,也可以通过设置evictInBackground来配置连接池,以对连接执行定期检查。
要自定义默认设置,可以HttpClient进行如下配置:
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import java.time.Duration;
public class Application {
public static void main(String[] args) {
ConnectionProvider provider =
ConnectionProvider.builder("custom")
.maxConnections(50)
// 将连接保持空闲的最长时间配置为20秒
.maxIdleTime(Duration.ofSeconds(20))
// 将连接保持活动的最长时间配置为60秒
.maxLifeTime(Duration.ofSeconds(60))
// 将挂起获取操作的最长时间配置为60秒
.pendingAcquireTimeout(Duration.ofSeconds(60))
// 每两分钟,将定期检查连接池中是否存在适用于删除的连接
.evictInBackground(Duration.ofSeconds(120))
.build();
HttpClient client = HttpClient.create(provider);
String response =
client.get()
.uri("https://example.com/")
.responseContent()
.aggregate()
.asString()
.block();
System.out.println("Response " + response);
provider.disposeLater()
.block();
}
}
(1)maxIdleTime:连接在连接池中保持空闲的最长时间
此连接在连接池中保持空闲的最长时间(ms)。默认情况下,maxIdleTime未指定。
配置时maxIdleTime,应考虑目标服务器上的空闲超时配置。选择等于或小于目标服务器上的配置。通过这样做,您可以减少由目标服务器关闭的连接引起的 I/O 问题。
(2)maxLifeTime:连接保持活动状态的最长时间
此连接保持活动状态的最长时间(ms)。默认情况下,maxLifeTime未指定。
(3)pendingAcquireTimeout:完成挂起的获取操作或引发PoolAcquireTimeoutException的最长时间
必须完成挂起的获取操作或引发PoolAcquireTimeoutException的最长时间(ms)。默认值:45秒。
2.HttpClient超时
Reactor Netty使用Reactor Core作为其Reactive Streams实现,可能想要使用timeout它Mono和Flux提供的运算符。但是请记住,最好使用Reactor Netty中可用的更具体的超时配置选项,因为它们为特定目的和用例提供了更多控制。相比之下,timeout操作员只能申请整个操作,从建立到远程对等点的连接到接收响应。
注意:提供有关HttpClient级别的各种超时配置选项的信息
(1)响应超时
HttpClient提供用于为所有请求配置默认响应超时的API。可以通过特定请求的API更改此默认响应超时。默认情况下,responseTimeout未指定。
自定义默认设置,可以HttpClient进行如下配置:
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import java.time.Duration;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
// 将默认响应超时配置为1秒
.responseTimeout(Duration.ofSeconds(1));
String response1 =
client.post()
.uri("https://example.com/")
.send((req, out) -> { // 将特定请求的响应超时配置为2秒
req.responseTimeout(Duration.ofSeconds(2));
return out.sendString(Mono.just("body1"));
})
.responseContent()
.aggregate()
.asString()
.block();
System.out.println("Response " + response1);
String response2 =
client.post()
.uri("https://example.com/")
.send((req, out) -> out.sendString(Mono.just("body2")))
.responseContent()
.aggregate()
.asString()
.block();
System.out.println("Response " + response2);
}
}
(2)连接超时
以下列表显示了所有可用的连接超时配置选项,但其中一些可能仅适用于特定传输。
①:CONNECT_TIMEOUT_MILLIS - 连接超时
如果到远程对等体的连接建立尝试未在配置的连接超时(ms)内完成,则连接建立尝试失败。默认值:30秒。
②:SO_KEEPALIVE - 连接空闲活动时间
当连接空闲一段时间(时间取决于实现,但默认通常为两个小时),TCP自动向keepalive远程对等方发送探测。默认情况下,SO_KEEPALIVE未启用。当您使用Epoll/ NIO(自Java 11起)传输运行时,还可以配置:
配置 | 描述 |
---|---|
TCP_KEEPIDLE | keepalive如果SO_KEEPALIVE已设置,此连接在 TCP 开始发送探测之前保持空闲的最长时间(分辨率:秒)。最长时间取决于实现,但默认值通常为两小时。 |
TCP_KEEPINTVL(Epoll)/ TCP_KEEPINTERVAL(NIO) | 各个keepalive探针之间的时间(分辨率:秒)。 |
TCP_KEEPCNT(Epoll)/ TCP_KEEPCOUNT(NIO) | keepalive在断开连接之前 TCP 应该发送的最大探测数。 |
有时,在客户端和服务器之间,可能有一个网络组件,它会在不发送响应的情况下静默删除空闲连接。从Reactor Netty的角度来看,在这个用例中,远程对等方只是不响应。为了能够处理这样的用例,可以考虑配置SO_KEEPALIVE
自定义默认设置,可以HttpClient进行如下配置:
import io.netty.channel.ChannelOption;
import io.netty.channel.epoll.EpollChannelOption;
//import io.netty.channel.socket.nio.NioChannelOption;
//import jdk.net.ExtendedSocketOptions;
import reactor.netty.http.client.HttpClient;
import java.net.InetSocketAddress;
public class Application {
/*
以下选项仅在使用NIO传输(Java11)时可用
.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPIDLE), 300)
.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPINTERVAL), 60)
.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPCOUNT), 8);
*/
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
.bindAddress(() -> new InetSocketAddress("host", 1234))
// 将连接建立超时配置为10秒
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
// 启用TCP keepalive。这意味着TCP keepalive在连接空闲一段时间后开始发送探测
.option(ChannelOption.SO_KEEPALIVE, true)
// 以下选项仅在使用Epoll传输时可用
// 在TCP开始发送keepalive探测之前,连接需要保持空闲5分钟
.option(EpollChannelOption.TCP_KEEPIDLE, 300)
// 将各个keepalive探测之间的时间配置为 1 分钟
.option(EpollChannelOption.TCP_KEEPINTVL, 60)
// 将TCPkeepalive探测的最大数量配置为8
.option(EpollChannelOption.TCP_KEEPCNT, 8);
String response =
client.get()
.uri("https://example.com/")
.responseContent()
.aggregate()
.asString()
.block();
System.out.println("Response " + response);
}
}
(3)SSL/TLS超时
HttpClient支持Netty提供的SSL/TLS功能。
可用的超时配置选项:
①handshakeTimeout
使用此选项配置SSL握手超时(ms)。默认值:10秒。当网络连接速度较慢时,应该考虑增加SSL握手超时。
②closeNotifyFlushTimeout
使用此选项配置SSLclose_notify刷新超时(ms)。默认值:3秒。
③closeNotifyReadTimeout
使用此选项配置SSLclose_notify读取超时(ms)。默认值:0s。
自定义默认设置,可以HttpClient进行如下配置:
import reactor.netty.http.Http11SslContextSpec;
import reactor.netty.http.client.HttpClient;
import java.time.Duration;
public class Application {
public static void main(String[] args) {
Http11SslContextSpec http11SslContextSpec = Http11SslContextSpec.forClient();
HttpClient client =
HttpClient.create()
.secure(spec -> spec.sslContext(http11SslContextSpec)
// 将SSL握手超时配置为30秒
.handshakeTimeout(Duration.ofSeconds(30))
// 将SSLclose_notify刷新超时配置为10秒
.closeNotifyFlushTimeout(Duration.ofSeconds(10))
// 将SSLclose_notify读取超时配置为10秒
.closeNotifyReadTimeout(Duration.ofSeconds(10)));
String response =
client.get()
.uri("https://example.com/")
.responseContent()
.aggregate()
.asString()
.block();
System.out.println("Response " + response);
}
}
(4)代理超时
HttpClient支持Netty提供的代理功能,并提供了一种指定连接建立超时的方法。如果到远程对等方的连接建立尝试未在超时内完成,则连接建立尝试失败。默认值:10秒。
要自定义默认设置,可以HttpClient进行如下配置:
import reactor.netty.http.client.HttpClient;
import reactor.netty.transport.ProxyProvider;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
.proxy(spec -> spec.type(ProxyProvider.Proxy.HTTP)
.host("proxy")
.port(8080)
.nonProxyHosts("localhost")
// 将连接建立超时配置为20秒
.connectTimeoutMillis(20_000));
String response =
client.get()
.uri("https://example.com/")
.responseContent()
.aggregate()
.asString()
.block();
System.out.println("Response " + response);
}
}
(5)主机名解析超时
默认情况下,HttpClient使用Netty的域名查找机制来异步解析域名
可用的超时配置选项:
①cacheMaxTimeToLive - 缓存的DNS资源记录的最大生存时间
缓存的DNS资源记录的最大生存时间(s)。如果DNS服务器返回的DNS资源记录的生存时间大于此最大生存时间,则此解析器将忽略来自DNS服务器的生存时间并使用此最大生存时间。默认值:Integer.MAX_VALUE。
②cacheMinTimeToLive - 缓存的DNS资源记录的最短生存时间
缓存的DNS资源记录的最短生存时间(s)。如果DNS服务器返回的DNS资源记录的生存时间小于此最短生存时间,则此解析器将忽略来自DNS服务器的生存时间并使用此最短生存时间。默认值:0s。
③cacheNegativeTimeToLive - 失败的DNS查询的缓存的生存时间
失败的DNS查询的缓存的生存时间(s)。默认值:0s。
④queryTimeout - 解析器执行的每个DNS查询的超时时间
设置此解析器执行的每个DNS查询的超时时间(s)。默认值:5秒
自定义默认设置,可以HttpClient进行如下配置:
import reactor.netty.http.client.HttpClient;
import java.time.Duration;
public class Application {
public static void main(String[] args) {
HttpClient client =
HttpClient.create()
// 此解析器执行的每个DNS查询的超时将为500毫秒
.resolver(spec -> spec.queryTimeout(Duration.ofMillis(500)));
String response =
client.get()
.uri("https://example.com/")
.responseContent()
.aggregate()
.asString()
.block();
System.out.println("Response " + response);
}
}