——仅用 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
中间件机制让你能横向切分关注点,业务逻辑不再混杂日志或安全代码。
未来拓展方向
当前框架已具备生产级内核的雏形。接下来可考虑:
-
请求体解析
通过req.handler { buffer -> ... }读取 Body,支持 JSON 解码。 -
多方法支持
扩展post(),put(),在RouteEntry中记录 HTTP 方法。 -
类型安全参数
支持:id(int),自动校验并转换为Int。 -
全局异常处理
捕获处理器中的未处理异常,返回 500,防止服务器崩溃。 -
Kotlin 协程集成
提供suspend版本的处理器,用awaitResult包装异步操作。 -
静态文件服务
对/static/**路径返回文件内容。 -
测试支持
利用Vertx的嵌入式能力编写集成测试。
结语:理解比使用更重要
你已用 纯 Vert.x Core 实现了 Web 框架的四大核心组件:
| 组件 | 作用 | Spring 对应 |
|---|---|---|
Request | 封装客户端请求数据 | HttpServletRequest |
Response | 构造并发送 HTTP 响应 | HttpServletResponse |
Router | 路径匹配 → 处理函数 | HandlerMapping |
Middleware | 插入通用逻辑(日志、认证等) | Filter |
没有魔法,没有注解,没有黑盒。每一行代码都有明确职责,每一层抽象都清晰可见。
3491

被折叠的 条评论
为什么被折叠?



