应使用自定义异常链包装“拒绝连接”异常,即捕获connectexception等底层ioexception,构造含requestid、upstreamhost等上下文的分层业务异常(如upstreamconnectionrejectedexception),保留originalcause形成可追溯链,并在netty连接监听器及outbound pipeline中统一处理,实现语义化、可分类、可追踪的网关级错误响应。

在基于 Netty 的网关中,“拒绝连接”本身不是业务异常,而是底层网络层(如 TCP 握手失败、目标不可达、端口未监听)触发的原始 IOException 或 ConnectException。直接抛出原生异常不利于统一拦截、日志归因和下游响应封装。因此,需要用**自定义异常链**进行包装——即捕获底层异常,构造语义明确、携带上下文的业务异常,并保留原始 cause,形成可追溯的异常链。
明确拒绝连接的典型来源
网关作为反向代理,常见拒绝连接场景包括:
- 上游服务未启动或监听地址错误(
java.net.ConnectException: Connection refused) - 上游服务所在主机不可达(
java.net.UnknownHostException或 ICMP 不通) - 防火墙/安全组拦截了目标端口
- Netty 客户端连接超时(
io.netty.channel.ConnectTimeoutException) - 上游服务主动 reset 连接(
java.io.IOException: Connection reset by peer)
定义分层异常类型
避免用通用 RuntimeException,建议按职责建模:
-
UpstreamConnectionRejectedException:顶层网关连接拒绝异常,继承
RuntimeException - UpstreamUnavailableException:表示上游服务不可用(含宕机、未部署、DNS 解析失败)
- UpstreamNetworkBlockedException:表示网络策略阻断(防火墙、路由不通、端口被占)
每个异常都应包含:requestId、upstreamHost、upstreamPort、originalCause(通过 super(message, cause) 传入)。
在 Netty 客户端连接阶段做包装
以 Netty Bootstrap 发起连接为例,在 channelFuture.addListener 或 ChannelFuture.awaitUninterruptibly() 后检查失败原因:
bootstrap.connect(address).addListener((ChannelFutureListener) future -> {
if (!future.isSuccess()) {
Throwable cause = future.cause();
String upstream = address.toString();
String msg = String.format("Failed to connect to upstream %s", upstream);
// 分类包装
if (cause instanceof ConnectException || cause instanceof ConnectTimeoutException) {
throw new UpstreamConnectionRejectedException(msg, cause)
.withUpstream(upstream)
.withRequestId(currentRequestId());
} else if (cause instanceof UnknownHostException) {
throw new UpstreamUnavailableException(msg, cause)
.withUpstream(upstream);
} else if (cause instanceof ClosedChannelException) {
throw new UpstreamNetworkBlockedException(msg, cause)
.withUpstream(upstream);
} else {
throw new UpstreamConnectionRejectedException(msg, cause);
}
}
});
统一异常处理器注入 Pipeline
在网关的 outbound pipeline(即发起上游请求的 ChannelPipeline)末尾添加一个 ChannelInboundHandler,专门捕获连接建立阶段的异常:
- 重写
exceptionCaught方法,只处理ConnectException、ConnectTimeoutException等连接期异常 - 不处理
IOException(可能是已连接后读写失败,属于另一类问题) - 将包装后的异常传递给网关统一错误响应逻辑(如返回 502 Bad Gateway + 可读错误码)
这样能确保所有连接拒绝路径都经过同一出口,便于埋点、告警和灰度降级。
关键不是加一层 try-catch,而是让“拒绝连接”从网络错误变成有业务含义、可分类、可追踪、可响应的网关事件。异常链的存在,也让日志里能看到完整调用栈和原始根因,排查时不用再翻底层堆栈。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











