我需要对我的 Ktor 应用程序进行一些初始化,但我只想在 Netty 准备好接受连接后才执行此操作。另一方面,如果 Netty 启动失败(例如,典型的“地址已在使用中”),我不希望发生此类初始化。
我实现了简单的方法(见下文),但我想知道是否有可能实现它不那么难看的方法?
首先我保存对 NettyApplicationEngine 的引用:
embeddedServer = embeddedServer(Netty, port, module)
然后我使用 NettyApplicationEngine 中的
channels
字段来确定其状态:
private fun NettyApplicationEngine.channelsReady(): Boolean {
val channelsField = this::class.members.find { it.name == "channels" }!!
channelsField.isAccessible = true
val channels = channelsField.call(this) as List<Channel>?
return !channels.isNullOrEmpty() && channels.all { it.isActive }
}
最后,我捕获 ApplicationStarted 事件并旋转直到通道准备就绪:
environment.monitor.subscribe(ApplicationStarted) {
thread(start = true, name = "real netty postinit") {
for (i in 1..100) {
TimeUnit.MILLISECONDS.sleep(100)
if (embeddedServer.channelsReady()) break
}
if (embeddedServer.channelsReady()) {
// Initialization here
} else {
// Server didn't start
embeddedServer.stop(1, 1, TimeUnit.SECONDS)
}
}
}
在尝试了几种不同的方法之后,我得出了一个自测试的结论,它只是将 HTTP 请求发送到我自己的端点,如果 HttpClient 和路由处理程序都成功执行,我认为 Netty 已准备就绪。
首先,我使用
Routing.RoutingCallFinished
注册 NettyApplicationEngine.environment.monitor
事件(稍后我会 dispose
创建的处理程序)。
然后我迭代所有
NettyApplicationEngine.environment.connectors
并创建将从 Deferred
处理程序完成的 RoutingCallFinished
。另外,我启动了异步协程,它将使用 HttpClient 检查相应的端点。
之后,我在那些
awaitAll
上Deferred
(以及来自 Deferred
事件处理程序的 ApplicationStarted
)。
根据文档,正确的方法是创建一个监视器。
我是这样做的
private var serverStarted = false
fun Application.module() {
monitor.subscribe(ApplicationStarted) {
serverStarted = true
}
monitor.subscribe(ApplicationStopped) {
serverStarted = false
}
// The rest of the module code goes here
}
到目前为止似乎运作良好。