Skip to content

Commit 24d9a14

Browse files
committed
feat: add body reader and improve request handling
- Implement req_body_len function to get request body length - Add body_reader module for centralized body parsing - Update HttpBody types to use string.View and bytes.View - Refactor request handling in both JS and native implementations - Improve error handling for invalid request bodies
1 parent 2428fda commit 24d9a14

7 files changed

Lines changed: 93 additions & 72 deletions

File tree

moon.mod.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "oboard/mocket",
3-
"version": "0.5.0",
3+
"version": "0.5.1",
44
"deps": {
55
"yj-qin/regexp": "0.3.6",
66
"illusory0x0/native": "0.2.1",

src/body_reader.mbt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
///|
2+
pub suberror BodyError {
3+
InvalidJsonCharset
4+
InvalidJson
5+
InvalidText
6+
}
7+
8+
///|
9+
fn read_body(
10+
req_headers : Map[String, String],
11+
body_bytes : @moonbitlang/core/bytes.View,
12+
) -> HttpBody raise BodyError {
13+
let content_type = req_headers.get("Content-Type")
14+
match content_type {
15+
Some([.. "application/json", ..]) => {
16+
let json = @encoding.decoder(UTF8).decode(body_bytes) catch {
17+
_ => raise BodyError::InvalidJsonCharset
18+
}
19+
Json(@json.parse(json) catch { _ => raise BodyError::InvalidJson })
20+
}
21+
Some([.. "text/plain", ..] | [.. "text/html", ..]) =>
22+
Text(
23+
@encoding.decoder(UTF8).decode(body_bytes) catch {
24+
_ => raise BodyError::InvalidText
25+
},
26+
)
27+
_ => Bytes(body_bytes)
28+
}
29+
}

src/index.mbt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
///|
22
pub(all) enum HttpBody {
33
Json(Json)
4-
Text(String)
5-
HTML(String)
6-
Bytes(Bytes)
4+
Text(@moonbitlang/core/string.View)
5+
HTML(@moonbitlang/core/string.View)
6+
Bytes(@moonbitlang/core/bytes.View)
77
Empty
88
}
99

@@ -55,7 +55,7 @@ pub struct HttpRequest {
5555
///|
5656
pub(all) struct HttpResponse {
5757
mut status_code : Int
58-
headers : Map[String, String]
58+
headers : Map[@moonbitlang/core/string.View, @moonbitlang/core/string.View]
5959
// body : Body
6060
}
6161

src/mocket.js.mbt

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ pub fn serve_ffi(mocket : Mocket, port~ : Int) -> Unit {
9494
res: { status_code: 200, headers: {} },
9595
params,
9696
}
97-
let content_type = event.req.headers.get("Content-Type")
9897
run(fn() {
9998
// 如果是 post,先等待 data 事件
10099
if event.req.http_method == "POST" {
@@ -104,16 +103,12 @@ pub fn serve_ffi(mocket : Mocket, port~ : Int) -> Unit {
104103
req.on("end", _ => res(buffer.to_string()))
105104
}))
106105
|> ignore
107-
match content_type {
108-
Some("application/json") =>
109-
event.req.body = Json(
110-
@json.parse(buffer.to_string()) catch {
111-
_ => {}
112-
},
113-
)
114-
Some("text/html") => event.req.body = HTML(buffer.to_string())
115-
Some("text/plain") => event.req.body = Text(buffer.to_string())
116-
_ => event.req.body = Bytes(buffer.to_bytes())
106+
event.req.body = read_body(string_headers, buffer.to_bytes()) catch {
107+
_ => {
108+
res.write_head(400, @js.Object::new().to_value())
109+
res.end(@js.Value::cast_from("Invalid body"))
110+
return
111+
}
117112
}
118113
}
119114

src/mocket.native.mbt

Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ pub extern "c" fn HttpRequestInternal::on_complete(
5151
#owned(self)
5252
extern "c" fn HttpRequestInternal::body(self : HttpRequestInternal) -> Bytes = "req_body"
5353

54+
///|
55+
#owned(self)
56+
extern "c" fn HttpRequestInternal::req_body_len(
57+
self : HttpRequestInternal,
58+
) -> Int = "req_body_len"
59+
5460
///|
5561
#owned(self, body)
5662
extern "c" fn HttpResponseInternal::end(
@@ -127,21 +133,19 @@ fn handle_request_native(
127133
// res.status(200)
128134
// res.end(to_cstr("{\"status\":\"ok\"}"))
129135

130-
let headers = from_cstr(req.headers())
131-
println(headers)
132-
let string_headers = {}
133-
// guard (req.headers()) is Ok(Object(headers)) else {
134-
// res.status(400)
135-
// res.end(to_cstr("Invalid headers"))
136-
// return
137-
// }
138-
139-
// 批量转换 headers,减少单个处理的开销
140-
// headers.each(fn(key, value) {
141-
// if value is String(v) {
142-
// string_headers.set(key, v)
143-
// }
144-
// })
136+
let headers_str = from_cstr(req.headers())
137+
let req_headers = headers_str
138+
.split("\n")
139+
.map(fn(pair) {
140+
if pair.length() > 0 && pair.split(": ").to_array() is [key, value] {
141+
(key.to_string(), value.to_string())
142+
} else {
143+
("", "")
144+
}
145+
})
146+
.filter(fn(pair) { pair.0.length() > 0 })
147+
.to_array()
148+
|> Map::from_array
145149
let (params, handler) = match mocket.find_route(http_method, path) {
146150
Some((h, p)) => (p, h)
147151
_ => {
@@ -151,43 +155,19 @@ fn handle_request_native(
151155
}
152156
}
153157
let event = {
154-
req: { http_method, url, body: Empty, headers: string_headers },
158+
req: { http_method, url, body: Empty, headers: req_headers },
155159
res: { status_code: 200, headers: {} },
156160
params,
157161
}
158162
if http_method == "POST" {
159-
let body_bytes = req.body()
160-
let content_type = Some("text/plain") // req.headers().get("Content-Type")
161-
let body = match content_type {
162-
Some("application/json") => {
163-
let json = @encoding.decoder(UTF8).decode(body_bytes) catch {
164-
_ => {
165-
res.status(400)
166-
res.end(to_cstr("Invalid JSON charset"))
167-
return
168-
}
169-
}
170-
Json(
171-
@json.parse(json) catch {
172-
_ => {
173-
res.status(400)
174-
res.end(to_cstr("Invalid JSON"))
175-
return
176-
}
177-
},
178-
)
163+
let req_body_len = req.req_body_len()
164+
let body_bytes = req.body()[0:req_body_len]
165+
let body = read_body(req_headers, body_bytes) catch {
166+
_ => {
167+
res.status(400)
168+
res.end(to_cstr("Invalid body"))
169+
return
179170
}
180-
Some("text/plain" | "text/html") =>
181-
Text(
182-
@encoding.decoder(UTF8).decode(body_bytes) catch {
183-
_ => {
184-
res.status(400)
185-
res.end(to_cstr("Invalid text charset"))
186-
return
187-
}
188-
},
189-
)
190-
_ => Bytes(body_bytes)
191171
}
192172
event.req.body = body
193173
}
@@ -212,12 +192,12 @@ fn handle_request_native(
212192
res.set_header(to_cstr(key), to_cstr(value))
213193
})
214194
if body is Bytes(bytes) {
215-
res.end_bytes(bytes)
195+
res.end_bytes(bytes.to_bytes())
216196
} else {
217197
res.end(
218198
match body {
219-
HTML(s) => to_cstr(s)
220-
Text(s) => to_cstr(s)
199+
HTML(s) => to_cstr(s.to_string())
200+
Text(s) => to_cstr(s.to_string())
221201
Json(j) => to_cstr(j.stringify())
222202
_ => to_cstr("")
223203
},
@@ -227,8 +207,8 @@ fn handle_request_native(
227207
}
228208

229209
///|
230-
fn to_cstr(s : String) -> @native.CStr {
231-
let bytes = @encoding.encode(UTF8, s)
210+
fn[T : Show] to_cstr(s : T) -> @native.CStr {
211+
let bytes = @encoding.encode(UTF8, s.to_string())
232212
let utf8_ptr = @native.unsafe_coerce(bytes)
233213
utf8_ptr
234214
}

src/mocket.stub.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,15 @@ uint8_t *req_body(request_t *req)
156156
return NULL;
157157
}
158158

159+
size_t req_body_len(request_t *req)
160+
{
161+
if (req && req->hm)
162+
{
163+
return req->hm->body.len;
164+
}
165+
return 0;
166+
}
167+
159168
// Set response header (wrapper for res_set_header)
160169
void res_set_header_line(response_t *res, const char *header_line)
161170
{

src/pkg.generated.mbti

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package "oboard/mocket"
33

44
import(
55
"moonbitlang/core/builtin"
6+
"moonbitlang/core/bytes"
7+
"moonbitlang/core/string"
68
"oboard/mocket/js"
79
)
810

@@ -24,6 +26,12 @@ fn serve_ffi(Mocket, port~ : Int) -> Unit
2426
async fn[T, E : Error] suspend(((T) -> Unit, (E) -> Unit) -> Unit) -> T raise E
2527

2628
// Errors
29+
pub suberror BodyError {
30+
InvalidJsonCharset
31+
InvalidJson
32+
InvalidText
33+
}
34+
2735
pub suberror ExecError
2836
impl Show for ExecError
2937

@@ -36,9 +44,9 @@ impl Show for NetworkError
3644
// Types and methods
3745
pub(all) enum HttpBody {
3846
Json(Json)
39-
Text(String)
40-
HTML(String)
41-
Bytes(Bytes)
47+
Text(@string.View)
48+
HTML(@string.View)
49+
Bytes(@bytes.View)
4250
Empty
4351
}
4452

@@ -62,7 +70,7 @@ fn HttpRequestInternal::url(Self) -> String
6270

6371
pub(all) struct HttpResponse {
6472
mut status_code : Int
65-
headers : Map[String, String]
73+
headers : Map[@string.View, @string.View]
6674
}
6775

6876
#external

0 commit comments

Comments
 (0)