Skip to content

Commit da7ea4f

Browse files
committed
feat(middleware): implement middleware chain with CORS support
add new Middleware type and refactor middleware execution to support chaining introduce CORS middleware with preflight request handling update example to demonstrate middleware usage
1 parent cdc20c7 commit da7ea4f

8 files changed

Lines changed: 144 additions & 25 deletions

File tree

src/cors/cors.mbt

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
///|
2+
pub fn is_preflight_request(event : @mocket.HttpEvent) -> Bool {
3+
let origin = event.req.headers.get("origin")
4+
let access_control_request_method = event.req.headers.get(
5+
"access-control-request-method",
6+
)
7+
return event.req.http_method == "OPTIONS" &&
8+
origin != None &&
9+
access_control_request_method != None
10+
}
11+
12+
///|
13+
pub fn append_cors_preflight_headers(
14+
event : @mocket.HttpEvent,
15+
origin? : String = "*",
16+
methods? : String = "*",
17+
allow_headers? : String = "*",
18+
credentials? : Bool = false,
19+
max_age? : Int = 86400,
20+
) -> Unit noraise {
21+
let headers = event.res.headers
22+
headers["Access-Control-Allow-Origin"] = origin
23+
if credentials {
24+
headers["Access-Control-Allow-Credentials"] = "true"
25+
}
26+
headers["Access-Control-Allow-Methods"] = methods
27+
headers["Access-Control-Allow-Headers"] = allow_headers
28+
headers["Access-Control-Max-Age"] = max_age.to_string()
29+
}
30+
31+
///|
32+
pub fn append_cors_headers(
33+
event : @mocket.HttpEvent,
34+
origin? : String = "*",
35+
methods? : String = "*",
36+
allow_headers? : String = "*",
37+
expose_headers? : String = "*",
38+
credentials? : Bool = false,
39+
) -> Unit noraise {
40+
let headers = event.res.headers
41+
headers["Access-Control-Allow-Origin"] = origin
42+
headers["Access-Control-Allow-Methods"] = methods
43+
headers["Access-Control-Allow-Headers"] = allow_headers
44+
headers["Access-Control-Expose-Headers"] = expose_headers
45+
if credentials {
46+
headers["Access-Control-Allow-Credentials"] = "true"
47+
}
48+
}
49+
50+
///|
51+
pub fn handle_cors(
52+
origin? : String = "*",
53+
methods? : String = "*",
54+
allow_headers? : String = "*",
55+
expose_headers? : String = "*",
56+
credentials? : Bool = false,
57+
max_age? : Int = 86400,
58+
) -> @mocket.Middleware {
59+
(event, next) => if is_preflight_request(event) {
60+
append_cors_preflight_headers(
61+
event,
62+
origin~,
63+
methods~,
64+
allow_headers~,
65+
credentials~,
66+
max_age~,
67+
)
68+
// 对于预检请求,直接返回空响应,不调用next()
69+
@mocket.Empty
70+
} else {
71+
append_cors_headers(
72+
event,
73+
origin~,
74+
methods~,
75+
allow_headers~,
76+
expose_headers~,
77+
credentials~,
78+
)
79+
// 对于普通请求,继续执行后续中间件和处理器
80+
next()
81+
}
82+
}

src/cors/moon.pkg.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"import": [
3+
"oboard/mocket"
4+
]
5+
}

src/example/main.mbt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ fn main {
44

55
// Register global middleware
66
app
7-
..use_middleware(event => println(
8-
"📝 Request: \{event.req.http_method} \{event.req.url}",
9-
))
7+
..use_middleware((event, next) => {
8+
println("📝 Request: \{event.req.http_method} \{event.req.url}")
9+
next()
10+
})
11+
..use_middleware(@cors.handle_cors())
1012

1113
// Text Response
1214
..get("/", _event => Text("⚡️ Tadaa!"))
@@ -15,9 +17,12 @@ fn main {
1517
..on("GET", "/hello", _ => Text("Hello world!"))
1618
..group("/api", group => {
1719
// 添加组级中间件
18-
group.use_middleware(event => println(
19-
"🔒 API Group Middleware: \{event.req.http_method} \{event.req.url}",
20-
))
20+
group.use_middleware((event, next) => {
21+
println(
22+
"🔒 API Group Middleware: \{event.req.http_method} \{event.req.url}",
23+
)
24+
next()
25+
})
2126
group.get("/hello", _ => Text("Hello world!"))
2227
group.get("/json", _ => Json({
2328
"name": "John Doe",

src/example/moon.pkg.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"is-main": true,
33
"import": [
4-
"oboard/mocket"
4+
"oboard/mocket",
5+
"oboard/mocket/cors"
56
]
67
}

src/index.mbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub(all) enum HttpBody {
1111
pub(all) struct Mocket {
1212
base_path : String
1313
mappings : Map[(String, String), async (HttpEvent) -> HttpBody noraise]
14-
middlewares : Array[(String, async (HttpEvent) -> Unit noraise)]
14+
middlewares : Array[(String, Middleware)]
1515
// 添加静态路由缓存(精确匹配的路由)
1616
static_routes : Map[
1717
String,

src/middleware.mbt

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
///|
2+
// 中间件类型:接受 HttpEvent 和 next 函数,返回 HttpBody
3+
pub type Middleware = async (HttpEvent, async () -> HttpBody noraise) -> HttpBody noraise
4+
15
///|
26
// 注册中间件,支持路径匹配
37
pub fn Mocket::use_middleware(
48
self : Mocket,
5-
middleware : async (HttpEvent) -> Unit noraise,
9+
middleware : Middleware,
610
base_path? : String,
711
) -> Unit {
812
let base_path = base_path.unwrap_or(self.base_path)
@@ -11,20 +15,44 @@ pub fn Mocket::use_middleware(
1115
}
1216

1317
///|
14-
// 执行中间件链,支持路径匹配
15-
fn execute_middlewares(
16-
middlewares : Array[(String, async (HttpEvent) -> Unit noraise)],
18+
// 执行中间件链,支持路径匹配和洋葱模型
19+
pub async fn execute_middlewares(
20+
middlewares : Array[(String, Middleware)],
1721
event : HttpEvent,
18-
) -> Unit {
19-
// 优化:使用迭代而不是递归,避免栈溢出
20-
let mut i = 0
21-
while i < middlewares.length() {
22+
final_handler : async (HttpEvent) -> HttpBody noraise,
23+
) -> HttpBody noraise {
24+
// 过滤出匹配路径的中间件
25+
let matched_middlewares = []
26+
for i = 0; i < middlewares.length(); i = i + 1 {
2227
let (base_path, middleware) = middlewares[i]
2328
// 如果 base_path 为空字符串,则为全局中间件
2429
// 否则检查请求路径是否匹配 base_path
2530
if base_path == "" || event.req.url.has_prefix(base_path) {
26-
run(fn() { middleware(event) })
31+
matched_middlewares.push(middleware)
32+
}
33+
}
34+
35+
// 递归构建中间件链(洋葱模型)
36+
execute_middleware_chain(matched_middlewares, 0, event, final_handler)
37+
}
38+
39+
///|
40+
// 递归执行中间件链
41+
async fn execute_middleware_chain(
42+
middlewares : Array[Middleware],
43+
index : Int,
44+
event : HttpEvent,
45+
final_handler : async (HttpEvent) -> HttpBody noraise,
46+
) -> HttpBody noraise {
47+
if index >= middlewares.length() {
48+
// 所有中间件都执行完毕,调用最终处理器
49+
final_handler(event)
50+
} else {
51+
// 执行当前中间件
52+
let current_middleware = middlewares[index]
53+
let next = async fn() noraise {
54+
execute_middleware_chain(middlewares, index + 1, event, final_handler)
2755
}
28-
i = i + 1
56+
current_middleware(event, next)
2957
}
3058
}

src/mocket.js.mbt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,8 @@ pub fn serve_ffi(mocket : Mocket, port~ : Int) -> Unit {
112112
}
113113
}
114114

115-
// 执行中间件链
116-
execute_middlewares(mocket.middlewares, event)
117-
let body = handler(event)
115+
// 执行中间件链和处理器
116+
let body = execute_middlewares(mocket.middlewares, event, handler)
118117
if not(body is Empty) {
119118
event.res.headers.set(
120119
"Content-Type",

src/mocket.native.mbt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,9 @@ fn handle_request_native(
171171
}
172172
event.req.body = body
173173
}
174-
run(() => {
175-
// 执行中间件链
176-
execute_middlewares(mocket.middlewares, event)
177-
let body = handler(event)
174+
run(async fn() noraise {
175+
// 执行中间件链和处理器
176+
let body = execute_middlewares(mocket.middlewares, event, handler)
178177
if not(body is Empty) {
179178
event.res.headers.set(
180179
"Content-Type",

0 commit comments

Comments
 (0)