Skip to content

Commit b30bc59

Browse files
authored
Merge pull request #4 from oboard/feat/websocket
Feat/websocket
2 parents 42965bf + a168bd7 commit b30bc59

25 files changed

Lines changed: 1350 additions & 38 deletions

AGENTS.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Project Agents.md Guide
2+
3+
This is a [MoonBit](https://docs.moonbitlang.com) project.
4+
5+
## Project Structure
6+
7+
- MoonBit packages are organized per directory, for each directory, there is a
8+
`moon.pkg.json` file listing its dependencies. Each package has its files and
9+
blackbox test files (common, ending in `_test.mbt`) and whitebox test files
10+
(ending in `_wbtest.mbt`).
11+
12+
- In the toplevel directory, this is a `moon.mod.json` file listing about the
13+
module and some meta information.
14+
15+
## Coding convention
16+
17+
- MoonBit code is organized in block style, each block is separated by `///|`,
18+
the order of each block is irrelevant. In some refactorings, you can process
19+
block by block independently.
20+
21+
- Try to keep deprecated blocks in file called `deprecated.mbt` in each
22+
directory.
23+
24+
## Tooling
25+
26+
- `moon fmt` is used to format your code properly.
27+
28+
- `moon info` is used to update the generated interface of the package, each
29+
package has a generated interface file `.mbti`, it is a brief formal
30+
description of the package. If nothing in `.mbti` changes, this means your
31+
change does not bring the visible changes to the external package users, it is
32+
typically a safe refactoring.
33+
34+
- In the last step, run `moon info && moon fmt` to update the interface and
35+
format the code. Check the diffs of `.mbti` file to see if the changes are
36+
expected.
37+
38+
- Run `moon test` to check the test is passed. MoonBit supports snapshot
39+
testing, so when your changes indeed change the behavior of the code, you
40+
should run `moon test --update` to update the snapshot.
41+
42+
- You can run `moon check` to check the code is linted correctly.
43+
44+
- When writing tests, you are encouraged to use `inspect` and run
45+
`moon test --update` to update the snapshots, only use assertions like
46+
`assert_eq` when you are in some loops where each snapshot may vary. You can
47+
use `moon coverage analyze > uncovered.log` to see which parts of your code
48+
are not covered by tests.
49+
50+
- agent-todo.md has some small tasks that are easy for AI to pick up, agent is
51+
welcome to finish the tasks and check the box when you are done

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.6",
3+
"version": "0.5.7",
44
"deps": {
55
"illusory0x0/native": "0.2.1",
66
"moonbitlang/x": "0.4.34",

src/async.mbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
///|
2-
pub fn run(f : async () -> Unit noraise) -> Unit = "%async.run"
2+
pub fn async_run(f : async () -> Unit noraise) -> Unit = "%async.run"
33

44
///|
55
/// `suspend` 会中断当前协程的运行。

src/body_reader.mbt

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,89 @@ fn read_body(
1111
body_bytes : BytesView,
1212
) -> HttpBody raise BodyError {
1313
let content_type = req_headers.get("Content-Type")
14-
match content_type {
15-
Some([.. "application/json", ..]) => {
16-
let json = @encoding/utf8.decode(body_bytes) catch {
17-
_ => raise BodyError::InvalidJsonCharset
14+
if content_type is Some(content_type) {
15+
let content_type = parse_content_type(content_type)
16+
if content_type is Some(content_type) {
17+
return match content_type {
18+
{ subtype: "json", .. } => {
19+
let json = @encoding/utf8.decode(body_bytes) catch {
20+
_ => raise BodyError::InvalidJsonCharset
21+
}
22+
Json(@json.parse(json) catch { _ => raise BodyError::InvalidJson })
23+
}
24+
{ media_type: "text", .. } =>
25+
Text(
26+
@encoding/utf8.decode(body_bytes) catch {
27+
_ => raise BodyError::InvalidText
28+
},
29+
)
30+
{ subtype: "x-www-form-urlencoded", .. } =>
31+
Form(parse_form_data(body_bytes))
32+
_ => Bytes(body_bytes)
1833
}
19-
Json(@json.parse(json) catch { _ => raise BodyError::InvalidJson })
2034
}
21-
Some([.. "text/plain", ..] | [.. "text/html", ..]) =>
22-
Text(
23-
@encoding/utf8.decode(body_bytes) catch {
24-
_ => raise BodyError::InvalidText
25-
},
26-
)
27-
_ => Bytes(body_bytes)
2835
}
36+
Bytes(body_bytes)
37+
}
38+
39+
///|
40+
priv struct ContentType {
41+
media_type : StringView
42+
subtype : StringView
43+
params : Map[StringView, StringView]
44+
} derive(Show)
45+
46+
///|
47+
fn parse_content_type(s : String) -> ContentType? {
48+
let parts = s.split(";").to_array()
49+
if parts.is_empty() {
50+
None
51+
} else {
52+
let main_part_str = parts[0].trim_space()
53+
let media_type_parts = main_part_str.split("/").to_array()
54+
if media_type_parts.length() != 2 {
55+
None
56+
} else {
57+
let media_type = media_type_parts[0].trim_space()
58+
let subtype = media_type_parts[1].trim_space()
59+
let params = {}
60+
for i in 1..<parts.length() {
61+
let param_part = parts[i].trim_space()
62+
let eq_index = param_part.find("=")
63+
try {
64+
match eq_index {
65+
Some(idx) => {
66+
let key = param_part[0:idx].trim_space()
67+
let value = param_part[idx + 1:].trim_space()
68+
params[key] = value
69+
}
70+
None => () // Ignore malformed parameters
71+
}
72+
} catch {
73+
_ => ()
74+
}
75+
}
76+
Some({ media_type, subtype, params })
77+
}
78+
}
79+
}
80+
81+
///|
82+
test "parse_content_type" {
83+
inspect(
84+
parse_content_type("application/json; charset=utf-8"),
85+
content=(
86+
#|Some({media_type: "application", subtype: "json", params: {"charset": "utf-8"}})
87+
),
88+
)
89+
}
90+
91+
///|
92+
test "parse_form_data" {
93+
inspect(
94+
parse_form_data(b"name=John+Doe&age=30"),
95+
content=(
96+
#|{"name": "John Doe", "age": "30"}
97+
),
98+
)
2999
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Generated using `moon info`, DON'T EDIT IT
2-
package "oboard/mocket/example"
2+
package "oboard/mocket/examples/route"
33

44
// Values
55

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"is-main": true,
3+
"import": [
4+
"oboard/mocket"
5+
]
6+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Generated using `moon info`, DON'T EDIT IT
2+
package "oboard/mocket/examples/websocket"
3+
4+
// Values
5+
6+
// Errors
7+
8+
// Types and methods
9+
10+
// Type aliases
11+
12+
// Traits
13+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
///|
2+
fn main {
3+
let app = @mocket.new(logger=@mocket.new_logger())
4+
app.ws("/ws", event => match event {
5+
Open(peer) => println("WS open: " + peer.to_string())
6+
Message(peer, body) => {
7+
let msg = match body {
8+
Text(s) => s.to_string()
9+
_ => ""
10+
}
11+
println("WS message: " + msg)
12+
peer.send(msg)
13+
}
14+
Close(peer) => println("WS close: " + peer.to_string())
15+
})
16+
println("WebSocket echo server listening on ws://localhost:8080/ws")
17+
app.serve(port=8080)
18+
}

0 commit comments

Comments
 (0)