gemini

这是一个非常棒的架构设计。在基于虚拟线程(Loom)的架构中,同步的 Event Bus 既高效又易于维护,完全符合 Koa 风格的“洋葱模型”之外的生命周期管理。

针对你的需求,我将分两部分来解答:

  1. Event Bus 的设计与实现(支持类型安全和全生命周期)。

  2. 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) : AppEvent

2. 实现 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 阶段支持修改 methodpath(例如处理 Method Override),但在进入 Handler 后 Request 必须不可变。

解决方案:Copy-on-Event 模式

  1. Request 保持不可变:Request 类的属性全部保持 val

  2. Event 是可变的:RequestReceivedEvent 持有一个 var request: Request

  3. 流程控制:在创建 Context 之前触发事件。监听器通过 request.copy(...) 生成新实例并赋值给 Event。Event 处理结束后,取出的 request 传入 Context,从此之后它就是不可变的了。

代码实现方案

我们需要修改 createHttpHandlerlisten 方法,集成 EventBus。

1. 修改 Request 类 (保持原样,确保有 copy 方法)

由于 Requestdata 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)
}

总结

这种方案的优点:

  1. 安全性:Request 对象定义本身不需要变成可变的 (var),保持了数据类的纯洁性。

  2. 明确的边界:只有 RequestReceivedEvent 这个特定的时间窗口允许“替换”请求对象,一旦进入 Context 构造阶段,大门就关闭了。

  3. 扩展性:基于 sealed interface 的事件系统非常容易添加新事件,且对 IDE 友好(智能提示)。

  4. Koa 风格:这种通过事件系统介入底层逻辑的方式,非常符合 Koa 社区通过中间件或钩子修改行为的习惯,但比中间件更底层、更早介入。