gemini
这是一个非常棒的架构设计。在基于虚拟线程(Loom)的架构中,同步的 Event Bus 既高效又易于维护,完全符合 Koa 风格的“洋葱模型”之外的生命周期管理。
针对你的需求,我将分两部分来解答:
Event Bus 的设计与实现(支持类型安全和全生命周期)。
Request 变异与不可变性的优雅处理(如何在特定阶段允许修改,之后锁定)。
第一部分:Event Bus 设计与实现思路
为了保持 Kotlin 的优雅性,我们使用 sealed interface 来定义事件层级,利用泛型和 reified 关键字来实现类型安全的监听。
1. 定义事件体系
我们将事件分为三类:生命周期事件(Server)、配置事件(Route/App)和运行时事件(Request/Response)。
Kotlin
import java.time.Instant
// 基础事件接口
sealed interface AppEvent
// 1. 生命周期事件
data object ServerStarting : AppEvent
data class ServerStarted(val port: Int, val host: String, val elapsedMs: Long) : AppEvent
data object ServerStopping : AppEvent
data object ServerStopped : AppEvent
// 2. 配置/结构事件
data class RouteAdded(val method: String, val path: String) : AppEvent
data class SubAppLoaded(val mountPath: String, val app: Any) : AppEvent
// 3. 运行时事件 (核心交互)
// 注意:为了支持修改,部分 Event 类包含 var 属性,或者持有可变状态
class RequestReceivedEvent(var request: Request) : AppEvent
data class RouteMatchedEvent(val ctx: Context, val routePath: String) : AppEvent
data class ExceptionThrownEvent(val error: Throwable, val ctx: Context) : AppEvent
class ResponseGeneratedEvent(var response: Response, val ctx: Context) : AppEvent
data class ResponseSentEvent(val ctx: Context, val durationMs: Long) : AppEvent2. 实现 Event Bus
这是一个轻量级的发布/订阅系统。由于运行在虚拟线程上,我们直接同步执行监听器即可,无需复杂的线程池切换。
Kotlin
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.reflect.KClass
class EventBus {
// 存储监听器,使用 CopyOnWriteArrayList 保证并发读写安全
private val listeners = CopyOnWriteArrayList<ListenerEntry<*>>()
// 定义监听器包装类
private data class ListenerEntry<T : AppEvent>(
val type: KClass<T>,
val handler: (T) -> Unit
)
// 注册监听器 (使用 reified 让调用更优雅: bus.on<ServerStarted> { ... })
inline fun <reified T : AppEvent> on(noinline handler: (T) -> Unit) {
listeners.add(ListenerEntry(T::class, handler))
}
// 发布事件
@Suppress("UNCHECKED_CAST")
fun emit(event: AppEvent) {
// 过滤出订阅了该事件类型的监听器并执行
listeners.filter { it.type.isInstance(event) }
.forEach { (it as ListenerEntry<AppEvent>).handler(event) }
}
}第二部分:解决 Request 修改与不可变性
你希望在 RequestReceived 阶段支持修改 method 或 path(例如处理 Method Override),但在进入 Handler 后 Request 必须不可变。
解决方案:Copy-on-Event 模式
Request 保持不可变:
Request类的属性全部保持val。Event 是可变的:
RequestReceivedEvent持有一个var request: Request。流程控制:在创建 Context 之前触发事件。监听器通过
request.copy(...)生成新实例并赋值给 Event。Event 处理结束后,取出的request传入Context,从此之后它就是不可变的了。
代码实现方案
我们需要修改 createHttpHandler 和 listen 方法,集成 EventBus。
1. 修改 Request 类 (保持原样,确保有 copy 方法)
由于 Request 是 data class,Kotlin 自动生成了 copy() 方法,这非常适合不可变对象的局部修改。
2. 集成到主类 (Application.kt)
Kotlin
class Application(private val config: AppConfig = AppConfig()) {
val eventBus = EventBus()
// ... 其他属性 (router, etc.)
// ==========================================
// 核心修改:listen 方法集成生命周期事件
// ==========================================
fun listen(port: Int = 8080, host: String = "127.0.0.1", callback: Runnable = Runnable {}) {
val start = System.currentTimeMillis()
// 1. 发送启动中事件
eventBus.emit(ServerStarting)
// 配置更新逻辑...
if (host != config.server.host) config.server.host = host
if (port != config.server.port) config.server.port = port
val server = UndertowServer(config.server)
webServer = server
Runtime.getRuntime().addShutdownHook(Thread({ stop() }, "shutdown"))
try {
// 2. 启动服务器,传入处理函数
server.start(createHttpHandler())
// 3. 发送启动完成事件
eventBus.emit(ServerStarted(port, host, System.currentTimeMillis() - start))
callback.run()
} catch (e: Exception) {
// 4. 发送启动失败/停止事件
logger.error("Failed to start server", e)
stop() // stop 内部会触发 ServerStopping/Stopped
throw e
}
}
fun stop() {
eventBus.emit(ServerStopping)
webServer?.stop()
eventBus.emit(ServerStopped)
}
// ==========================================
// 核心修改:createHttpHandler 处理请求变异
// ==========================================
internal fun createHttpHandler(): (Request) -> Response {
return { rawRequest ->
// A. 创建可变的事件对象,包裹原始 Request
val event = RequestReceivedEvent(rawRequest)
// B. 发布事件:允许监听器通过 event.request = event.request.copy(...) 修改
eventBus.emit(event)
// C. 获取最终的 Request (可能是被修改过的)
val finalRequest = event.request
// D. 创建 Context。此时 Context 接收的是 finalRequest。
// 由于 Context 内部也是 val request,且 data class 属性是 val,
// 所以进入 handleRequest 后,没人能再修改它。
val ctx = Context(request = finalRequest, app = this)
handleRequest(ctx)
// E. 响应生成事件 (如果需要支持修改响应,原理同上)
val responseEvent = ResponseGeneratedEvent(ctx.response, ctx)
eventBus.emit(responseEvent)
// 返回最终响应(如果 ResponseGeneratedEvent 允许修改,这里取 event.response)
responseEvent.response
}
}
// ==========================================
// 核心修改:handleRequest 集成异常与完成事件
// ==========================================
internal fun handleRequest(ctx: Context) {
val start = System.nanoTime()
try {
// 触发路由匹配前逻辑...
router.handleRequest(ctx)
// 假设 router 内部匹配成功后,你可以手动 emit(RouteMatchedEvent(...))
ctx.response.materialize(ctx)
} catch (e: Exception) {
// F. 异常事件
eventBus.emit(ExceptionThrownEvent(e, ctx))
if (mountPath != "" && config.error.propagateSubAppExceptions) {
throw e
} else {
handleError(e, ctx)
ctx.response.materialize(ctx)
}
} finally {
// G. 请求完成/响应发送事件
val duration = (System.nanoTime() - start) / 1_000_000
eventBus.emit(ResponseSentEvent(ctx, duration))
}
}
}用户使用示例
这就是你实现后的框架如何被用户使用,以及如何实现“方法重写”功能:
Kotlin
fun main() {
val app = Application()
// 示例 1: 监听生命周期
app.eventBus.on<ServerStarted> { event ->
println("🚀 Server running on ${event.host}:${event.port} (Took ${event.elapsedMs}ms)")
}
// 示例 2: 实现 HTTP Method Override (解决你的核心需求)
// 允许客户端通过 Query 参数 ?_method=DELETE 来伪造 DELETE 请求
app.eventBus.on<RequestReceivedEvent> { event ->
val methodOverride = event.request.queryString
.split("&")
.find { it.startsWith("_method=") }
?.substringAfter("=")
?.uppercase()
if (methodOverride != null) {
// 优雅修改:使用 copy 创建新实例并赋值回去
// 这一步之后,所有的 Middleware 和 Handler 看到的 method 都是 "DELETE"
event.request = event.request.copy(method = methodOverride)
}
}
// 示例 3: 全局异常监控
app.eventBus.on<ExceptionThrownEvent> { event ->
println("⚠️ Error processing ${event.ctx.request.path}: ${event.error.message}")
}
// 示例 4: 性能监控
app.eventBus.on<ResponseSentEvent> { event ->
println("Request to ${event.ctx.request.path} took ${event.durationMs}ms")
}
app.listen(8080)
}总结
这种方案的优点:
安全性:
Request对象定义本身不需要变成可变的 (var),保持了数据类的纯洁性。明确的边界:只有
RequestReceivedEvent这个特定的时间窗口允许“替换”请求对象,一旦进入Context构造阶段,大门就关闭了。扩展性:基于
sealed interface的事件系统非常容易添加新事件,且对 IDE 友好(智能提示)。Koa 风格:这种通过事件系统介入底层逻辑的方式,非常符合 Koa 社区通过中间件或钩子修改行为的习惯,但比中间件更底层、更早介入。