Vert.x + Kotlin 手写 Web 框架:从 Tomcat + Spring MVC 到响应式核心

王者杯·14天创作挑战营·第8期 10w+人浏览 377人参与

——仅用 Vert.x Core,从零实现请求抽象、正则路由与中间件管道

在经典的 Nginx → Tomcat → Spring MVC 架构中:

  • Tomcat 负责将原始 HTTP 字节流解析为结构化的请求对象;
  • Spring MVC 在此基础上,通过注解路由、拦截器链等机制,将请求分发给业务逻辑。

今天,我们仅使用 Vert.x Core(即 vertx-core),不依赖任何高层 Web 模块,亲手实现一个轻量但完整的 Web 框架。我们将逐步构建:

  • Request / Response:封装原始 HTTP 数据,提供清晰的编程接口;
  • 动态路由:通过正则表达式支持 /users/:id 这类路径;
  • 中间件系统:以可组合的方式插入通用逻辑,如日志、认证、CORS。

每一步都可运行、可验证,最终形成一个你完全理解其内部工作原理的 Web 内核


依赖说明

项目仅依赖以下两个库:

implementation("io.vertx:vertx-core:4.5.8")
implementation("org.jetbrains.kotlin:kotlin-stdlib")

不使用 vert.x-web,所有 HTTP 解析、路由匹配、响应构造均由我们自行实现。这让我们能清晰看到 Web 框架每一层的职责边界。


第一步:启动服务器 + 封装请求与响应

目标:

  • 启动原生 HTTP 服务器;
  • 定义 Request(≈ HttpServletRequest)和 Response(≈ HttpServletResponse);
  • 实现 /hello 静态路由。

核心数据结构

// Request.kt
data class Request(
    val method: String,
    val uri: String,
    val path: String,
    val headers: Map<String, String>,
    val query: Map<String, String>
)

// Response.kt
import io.vertx.core.http.HttpServerResponse

class Response(private val raw: HttpServerResponse) {
    fun text(content: String, statusCode: Int = 200) {
        raw
            .setStatusCode(statusCode)
            .putHeader("Content-Type", "text/plain; charset=utf-8")
            .end(content)
    }

    fun json(content: String, statusCode: Int = 200) {
        raw
            .setStatusCode(statusCode)
            .putHeader("Content-Type", "application/json; charset=utf-8")
            .end(content)
    }
}

URI 解析工具

// HttpUtils.kt
object HttpUtils {
    fun parsePath(uri: String): String =
        uri.split('?').first().ifEmpty { "/" }

    fun parseQuery(uri: String): Map<String, String> {
        val queryPart = uri.split('?').getOrNull(1) ?: return emptyMap()
        return queryPart.split('&').associate { param ->
            val parts = param.split('=', limit = 2)
            val key = java.net.URLDecoder.decode(parts.getOrElse(0) { "" }, "UTF-8")
            val value = java.net.URLDecoder.decode(parts.getOrElse(1) { "" }, "UTF-8")
            key to value
        }
    }
}

初始路由器

// Router.kt
import io.vertx.core.http.HttpServerRequest
import io.vertx.core.http.HttpServerResponse

class Router {
    private val routes = mutableMapOf<String, (Request, Response) -> Unit>()

    fun get(path: String, handler: (Request, Response) -> Unit) {
        routes[path] = handler
    }

    fun handle(req: HttpServerRequest, res: HttpServerResponse) {
        val path = HttpUtils.parsePath(req.uri())
        val handler = routes[path]

        if (handler != null) {
            val request = Request(
                method = req.method().name(),
                uri = req.uri(),
                path = path,
                headers = req.headers().entries().associate { it.key to it.value },
                query = HttpUtils.parseQuery(req.uri())
            )
            handler(request, Response(res))
        } else {
            res.setStatusCode(404).end("Not Found")
        }
    }
}

启动入口

// Main.kt
import io.vertx.core.Vertx

fun main() {
    val vertx = Vertx.vertx()
    val router = Router()

    router.get("/hello") { _, res -> res.text("Hello from Vert.x Core!") }

    vertx.createHttpServer()
        .requestHandler { httpReq -> router.handle(httpReq, httpReq.response()) }
        .listen(8080) { result ->
            if (result.succeeded()) println("✅ Server running on http://localhost:8080")
        }
}

运行后访问 http://localhost:8080/hello,即可看到响应。
你已用纯 Vert.x Core 实现了最基础的 Web 请求处理流程。


第二步:动态路由 —— 用正则匹配路径参数

目标:

  • 支持 /users/:id 这样的路径模板;
  • 自动提取 :id 对应的值;
  • 无需第三方路由库,完全手动实现。

路由条目:模板 → 正则 → 参数名

// RouteEntry.kt
import java.util.regex.Pattern

data class RouteEntry(
    val template: String,
    private val pattern: Pattern,
    private val paramNames: List<String>,
    val handler: (Request, Response) -> Unit
) {
    companion object {
        fun compile(template: String, handler: (Request, Response) -> Unit): RouteEntry {
            val segments = template.trim('/').split('/')
            val regexParts = mutableListOf<String>()
            val names = mutableListOf<String>()

            for (segment in segments) {
                if (segment.startsWith(":")) {
                    names += segment.substring(1)
                    regexParts += "([^/]+)"  // 匹配任意非斜杠字符
                } else {
                    regexParts += Pattern.quote(segment)  // 转义静态段
                }
            }

            val regex = "^/${regexParts.joinToString("/")}$"
            return RouteEntry(template, Pattern.compile(regex), names, handler)
        }
    }

    fun match(path: String): Map<String, String>? {
        val matcher = pattern.matcher(path)
        if (!matcher.matches()) return null

        return paramNames.withIndex().associate { (i, name) ->
            name to (matcher.group(i + 1) ?: "")
        }
    }
}

更新 Request 与 Router

// Request.kt(更新)
data class Request(
    val method: String,
    val uri: String,
    val path: String,
    val headers: Map<String, String>,
    val query: Map<String, String>,
    val params: Map<String, String> = emptyMap()  // 新增路径参数
)

// Router.kt(更新)
class Router {
    private val routes = mutableListOf<RouteEntry>()

    fun get(template: String, handler: (Request, Response) -> Unit) {
        routes += RouteEntry.compile(template, handler)
    }

    fun handle(req: HttpServerRequest, res: HttpServerResponse) {
        val rawUri = req.uri()
        val path = HttpUtils.parsePath(rawUri)

        for (entry in routes) {
            val params = entry.match(path)
            if (params != null) {
                val request = Request(
                    method = req.method().name(),
                    uri = rawUri,
                    path = path,
                    headers = req.headers().entries().associate { it.key to it.value },
                    query = HttpUtils.parseQuery(rawUri),
                    params = params
                )
                entry.handler(request, Response(res))
                return
            }
        }

        res.setStatusCode(404).end("Not Found")
    }
}

注册动态路由

router.get("/users/:id") { req, res ->
    res.json("""{"id": "${req.params["id"]}", "name": "User"}""")
}

验证:

curl http://localhost:8080/users/42
# → {"id": "42", "name": "User"}

你现在的路由系统已支持 RESTful 风格,且完全由正则驱动,逻辑透明。


第三步:中间件系统 —— 可组合的通用逻辑层

目标:

  • 在请求到达业务逻辑前/后插入通用处理;
  • 支持日志、CORS、认证等;
  • 中间件可按需组合,顺序可控。

中间件定义

// Middleware.kt
typealias Middleware = (Request, Response, () -> Unit) -> Unit

中间件接收请求、响应和“继续执行”的回调。调用 next() 表示放行,否则可提前终止流程(如返回 401)。

内置中间件

// Middlewares.kt
object Middlewares {
    val logger: Middleware = { req, _, next ->
        val start = System.currentTimeMillis()
        println("[${req.method}] ${req.path}")
        next()
        println("  → ${System.currentTimeMillis() - start}ms")
    }

    fun bearerAuth(expectedToken: String): Middleware = { req, res, next ->
        val auth = req.headers["authorization"]
        if (auth?.startsWith("Bearer ") == true && auth.substring(7) == expectedToken) {
            next()
        } else {
            res.text("Unauthorized", 401)
        }
    }
}

路由器集成中间件

// Router.kt(最终版)
class Router {
    private val middlewares = mutableListOf<Middleware>()
    private val routes = mutableListOf<RouteEntry>()

    fun use(middleware: Middleware) {
        middlewares.add(middleware)
    }

    fun get(template: String, handler: (Request, Response) -> Unit) {
        routes += RouteEntry.compile(template, handler)
    }

    fun handle(req: HttpServerRequest, res: HttpServerResponse) {
        val rawUri = req.uri()
        val path = HttpUtils.parsePath(rawUri)

        val matched = routes.firstOrNull { it.match(path) != null }
        if (matched == null) {
            res.setStatusCode(404).end("Not Found")
            return
        }

        val params = matched.match(path)!!
        val request = Request(
            method = req.method().name(),
            uri = rawUri,
            path = path,
            headers = req.headers().entries().associate { it.key to it.value },
            query = HttpUtils.parseQuery(rawUri),
            params = params
        )
        val response = Response(res)

        // 构建中间件调用链(洋葱模型)
        var next: () -> Unit = { matched.handler(request, response) }
        for (i in middlewares.lastIndex downTo 0) {
            val currentNext = next
            next = { middlewares[i](request, response, currentNext) }
        }
        next()
    }
}

使用中间件

val router = Router()
router.use(Middlewares.logger)
router.use(Middlewares.bearerAuth("my-token"))

router.get("/profile") { _, res -> res.text("Hello, authenticated user!") }

验证:

# 无 token → 401
curl http://localhost:8080/profile

# 有 token → 200 + 日志
curl -H "Authorization: Bearer my-token" http://localhost:8080/profile

控制台输出:

[GET] /profile
  → 2ms

中间件机制让你能横向切分关注点,业务逻辑不再混杂日志或安全代码。


未来拓展方向

当前框架已具备生产级内核的雏形。接下来可考虑:

  1. 请求体解析
    通过 req.handler { buffer -> ... } 读取 Body,支持 JSON 解码。

  2. 多方法支持
    扩展 post(), put(),在 RouteEntry 中记录 HTTP 方法。

  3. 类型安全参数
    支持 :id(int),自动校验并转换为 Int

  4. 全局异常处理
    捕获处理器中的未处理异常,返回 500,防止服务器崩溃。

  5. Kotlin 协程集成
    提供 suspend 版本的处理器,用 awaitResult 包装异步操作。

  6. 静态文件服务
    /static/** 路径返回文件内容。

  7. 测试支持
    利用 Vertx 的嵌入式能力编写集成测试。


结语:理解比使用更重要

你已用 纯 Vert.x Core 实现了 Web 框架的四大核心组件:

组件作用Spring 对应
Request封装客户端请求数据HttpServletRequest
Response构造并发送 HTTP 响应HttpServletResponse
Router路径匹配 → 处理函数HandlerMapping
Middleware插入通用逻辑(日志、认证等)Filter

没有魔法,没有注解,没有黑盒。每一行代码都有明确职责,每一层抽象都清晰可见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值