Sign in

    mocket

    A web framework for MoonBit.

    http
    server
    Download zip
    Author
    Version
    0.5.8
    License
    Apache-2.0
    Last updated
    11 months ago
    Downloads
    2K

    #oboard/mocket

    Version GitHub Workflow Status (with event) License

    A web framework for MoonBit.

    #Quick Start

    Mocket supports both js and native backends.

    #JavaScript Backend

    Set the backend of MoonBit to js in Visual Studio Code

    Command: MoonBit: Select Backend -> js

    moon run src/example --target js

    #Native Backend

    Set the backend of MoonBit to native in Visual Studio Code

    Command: MoonBit: Select Backend -> native

    moon run src/example --target native

    Then visit http://localhost:4000

    #Usage

    Minimum Example: https://github.com/oboard/mocket_example

    #Features

    #Dynamic Routes

    Support named parameters with :param syntax:

    app.get("/hello/:name", event => {
    let name = event.params.get("name").or("World")
    Text("Hello, \{name}!")
    })

    #Wildcard Routes

    Support single and double wildcards:

    // Single wildcard - matches one path segment
    app.get("/hello/*", event => {
    let name = event.params.get("_").or("World")
    Text("Hello, \{name}!")
    })

    // Double wildcard - matches multiple path segments
    app.get("/hello/**", event => {
    let path = event.params.get("_").or("")
    Text("Hello, \{path}!")
    })

    #Async Support

    The library supports async/await for I/O operations:

    #Async /GET Example

    // async json data example
    app.get("/async_data", async fn(event) {
    Json({ "name": "John Doe", "age": 30, "city": "New York" })
    })

    #Route Groups

    Group related routes under a common base path with shared middleware:

    app.group("/api", group => {
    // Add group-level middleware
    group.use_middleware(event => println(
    "🔒 API Group Middleware: \{event.req.reqMethod} \{event.req.url}",
    ))

    // Routes under /api prefix
    group.get("/hello", _ => Text("Hello from API!"))
    group.get("/users", _ => Json({ "users": ["Alice", "Bob"] }))
    group.post("/data", e => e.req.body)
    })

    This creates routes:
    • GET /api/hello
    • GET /api/users
    • POST /api/data

    All routes in the group will execute the group middleware in addition to any global middleware.

    #Example usage

    let app = @mocket.new(logger=@mocket.new_debug_logger())

    // Register global middleware
    app
    ..use_middleware(event => println(
    "📝 Request: \{event.req.http_method} \{event.req.url}",
    ))

    // Text Response
    ..get("/", _event => Text("⚡️ Tadaa!"))

    // Hello World
    ..on("GET", "/hello", _ => Text("Hello world!"))
    ..group("/api", group => {
    // 添加组级中间件
    group.use_middleware(event => println(
    "🔒 API Group Middleware: \{event.req.http_method} \{event.req.url}",
    ))
    group.get("/hello", _ => Text("Hello world!"))
    group.get("/json", _ => Json({
    "name": "John Doe",
    "age": 30,
    "city": "New York",
    }))
    })

    // JSON Response
    ..get("/json", _event => Json({
    "name": "John Doe",
    "age": 30,
    "city": "New York",
    }))

    // Async Response
    ..get("/async_data", async fn(_event) noraise {
    Json({ "name": "John Doe", "age": 30, "city": "New York" })
    })

    // Dynamic Routes
    // /hello2/World = Hello, World!
    ..get("/hello/:name", event => {
    let name = event.params.get("name").unwrap_or("World")
    Text("Hello, \{name}!")
    })
    // /hello2/World = Hello, World!
    ..get("/hello2/*", event => {
    let name = event.params.get("_").unwrap_or("World")
    Text("Hello, \{name}!")
    })

    // Wildcard Routes
    // /hello3/World/World = Hello, World/World!
    ..get("/hello3/**", event => {
    let name = event.params.get("_").unwrap_or("World")
    Text("Hello, \{name}!")
    })

    // Echo Server
    ..post("/echo", e => e.req.body)

    // 404 Page
    ..get("/404", e => {
    e.res.status_code = 404
    HTML(
    (
    #|<html>
    #|<body>
    #| <h1>404</h1>
    #|</body>
    #|</html>
    ),
    )
    })

    // Serve
    ..serve(port=4000)

    // Print Server URL
    for path in app.mappings.keys() {
    println("\{path.0} http://localhost:4000\{path.1}")
    }

    #Route Matching Examples

    Route PatternURLParameters
    /hello/:name/hello/worldname: "world"
    /hello/*/hello/world_: "world"
    /hello/**/hello/foo/bar_: "foo/bar"
    /users/:id/posts/:postId/users/123/posts/456id: "123", postId: "456"
    /api/**/api/v1/users/123_: "v1/users/123"
    group("/api", ...).get("/hello")/api/hello-
    group("/users", ...).get("/:id")/users/123id: "123"

    #Q & A

    #Why not moonbitlang/async ?

    moonbitlang/async is a great library, but it is not supported by the js backend.

    🙌快来吧!🙌

    QQ 群号:949886784

    QQ 群

    HttpHandler

    type HttpHandler = async (MocketEvent) -> HttpBody noraise

    Middleware

    type Middleware = async (MocketEvent, async () -> HttpBody noraise) -> HttpBody noraise

    WebSocketHandler

    type WebSocketHandler = (WebSocketEvent) -> Unit

    BodyError

    pub suberror BodyError {
    InvalidJsonCharset
    InvalidJson
    InvalidText
    }

    ExecError

    pub suberror ExecError

    impl Show for ExecError

    IOError

    pub suberror IOError

    impl Show for IOError

    NetworkError

    pub suberror NetworkError

    CookieItem

    pub(all) struct CookieItem {
    name : String
    value : String
    max_age : Int?
    path : String?
    domain : String?
    secure : Bool?
    http_only : Bool?
    same_site : SameSiteOption?
    }

    impl Eq for CookieItem
    impl Show for CookieItem

    HttpBody

    pub(all) enum HttpBody {
    Json(Json)
    Text(StringView)
    HTML(StringView)
    Bytes(BytesView)
    Form(Map[String, String])
    Multipart(Map[String, MultipartFormValue])
    Empty
    }

    HttpBody::content_type

    fn HttpBody::content_type(self : HttpBody, boundary? : String) -> String

    HttpRequest

    pub(all) struct HttpRequest {
    http_method : String
    url : String
    headers : Map[StringView, StringView]
    body : HttpBody
    }

    fn HttpRequest::get_cookie(self : HttpRequest, name : String) -> CookieItem?

    HttpRequestInternal

    #external
    pub type HttpRequestInternal

    HttpRequestInternal::on_complete

    fn HttpRequestInternal::on_complete(self : HttpRequestInternal, cb : FuncRef[() -> Unit]) -> Unit

    HttpRequestInternal::on_headers

    fn HttpRequestInternal::on_headers(self : HttpRequestInternal, cb : FuncRef[(
    CStr
    ) -> Unit]) -> Unit

    HttpResponse

    pub(all) struct HttpResponse {
    status_code : Int
    headers : Map[StringView, StringView]
    cookies : Map[String, CookieItem]
    }

    fn HttpResponse::delete_cookie(self : HttpResponse, key : String) -> Unit

    fn HttpResponse::set_cookie(self : HttpResponse, name : String, value : String, max_age? : Int, path? : String, domain? : String, secure? : Bool, http_only? : Bool, same_site? : SameSiteOption) -> Unit

    HttpResponseInternal

    #external
    pub type HttpResponseInternal

    HttpServerInternal

    #external
    pub type HttpServerInternal

    LogLevel

    pub enum LogLevel {
    Debug
    Info
    Warn
    Error
    }

    Logger

    pub struct Logger {
    enabled : Bool
    level : LogLevel
    }

    Logger::debug

    fn Logger::debug(self : Logger, message_fn : () -> String) -> Unit

    Logger::debug_str

    fn Logger::debug_str(self : Logger, message : String) -> Unit

    Logger::error

    fn Logger::error(self : Logger, message_fn : () -> String) -> Unit

    Logger::info

    fn Logger::info(self : Logger, message_fn : () -> String) -> Unit

    Logger::route_added

    fn Logger::route_added(self : Logger, path : String) -> Unit

    Logger::route_created

    fn Logger::route_created(self : Logger, path : String) -> Unit

    Logger::route_dynamic

    fn Logger::route_dynamic(self : Logger, http_method : String, path : String) -> Unit

    Logger::route_found

    fn Logger::route_found(self : Logger, http_method : String, path : String) -> Unit

    Logger::route_lookup

    fn Logger::route_lookup(self : Logger, http_method : String, path : String) -> Unit

    Logger::route_merge_existing

    fn Logger::route_merge_existing(self : Logger, http_method : String) -> Unit

    Logger::route_merge_new

    fn Logger::route_merge_new(self : Logger, http_method : String) -> Unit

    Logger::route_not_found

    fn Logger::route_not_found(self : Logger, path : String) -> Unit

    Logger::route_register

    fn Logger::route_register(self : Logger, http_method : String, path : String) -> Unit

    Logger::route_static

    fn Logger::route_static(self : Logger, http_method : String, path : String) -> Unit

    Logger::routes_available

    fn Logger::routes_available(self : Logger, routes : Array[String]) -> Unit

    Logger::warn

    fn Logger::warn(self : Logger, message_fn : () -> String) -> Unit

    Mocket

    #alias(T)
    pub(all) struct Mocket {
    base_path : String
    mappings : Map[(String, String), async (MocketEvent) -> HttpBody noraise]
    middlewares : Array[(String, async (MocketEvent, async () -> HttpBody noraise) -> HttpBody noraise)]
    static_routes : Map[String, Map[String, async (MocketEvent) -> HttpBody noraise]]
    dynamic_routes : Map[String, Array[(String, async (MocketEvent) -> HttpBody noraise)]]
    logger : Logger
    ws_static_routes : Map[String, (WebSocketEvent) -> Unit]
    ws_dynamic_routes : Array[(String, (WebSocketEvent) -> Unit)]
    ws_clients : Map[String, Unit]
    ws_channels : Map[String, Map[String, Unit]]
    ws_client_port : Map[String, Int]
    }

    Mocket::all

    fn Mocket::all(self : Mocket, path : String, handler : async (MocketEvent) -> HttpBody noraise) -> Unit

    Mocket::connect

    fn Mocket::connect(self : Mocket, path : String, handler : async (MocketEvent) -> HttpBody noraise) -> Unit

    Mocket::delete

    fn Mocket::delete(self : Mocket, path : String, handler : async (MocketEvent) -> HttpBody noraise) -> Unit

    Mocket::get

    fn Mocket::get(self : Mocket, path : String, handler : async (MocketEvent) -> HttpBody noraise) -> Unit

    Mocket::group

    fn Mocket::group(self : Mocket, base_path : String, configure : (Mocket) -> Unit) -> Unit

    Mocket::head

    fn Mocket::head(self : Mocket, path : String, handler : async (MocketEvent) -> HttpBody noraise) -> Unit

    Mocket::on

    fn Mocket::on(self : Mocket, event : String, path : String, handler : async (MocketEvent) -> HttpBody noraise) -> Unit

    Mocket::options

    fn Mocket::options(self : Mocket, path : String, handler : async (MocketEvent) -> HttpBody noraise) -> Unit

    Mocket::patch

    fn Mocket::patch(self : Mocket, path : String, handler : async (MocketEvent) -> HttpBody noraise) -> Unit

    Mocket::post

    fn Mocket::post(self : Mocket, path : String, handler : async (MocketEvent) -> HttpBody noraise) -> Unit

    Mocket::put

    fn Mocket::put(self : Mocket, path : String, handler : async (MocketEvent) -> HttpBody noraise) -> Unit

    Mocket::serve

    fn Mocket::serve(self : Mocket, port~ : Int) -> Unit

    Mocket::trace

    fn Mocket::trace(self : Mocket, path : String, handler : async (MocketEvent) -> HttpBody noraise) -> Unit

    Mocket::use_middleware

    fn Mocket::use_middleware(self : Mocket, middleware : async (MocketEvent, async () -> HttpBody noraise) -> HttpBody noraise, base_path? : String) -> Unit

    Mocket::ws

    fn Mocket::ws(self : Mocket, path : String, handler : (WebSocketEvent) -> Unit) -> Unit

    MocketEvent

    pub(all) struct MocketEvent {
    req : HttpRequest
    res : HttpResponse
    params : Map[String, StringView]
    }

    MultipartFormValue

    pub(all) struct MultipartFormValue {
    filename : String?
    content_type : String?
    data : BytesView
    }

    SameSiteOption

    pub(all) enum SameSiteOption {
    Lax
    Strict
    SameSiteNone
    }

    WebSocketEvent

    pub enum WebSocketEvent {
    Open(WebSocketPeer)
    Message(WebSocketPeer, HttpBody)
    Close(WebSocketPeer)
    }

    WebSocketPeer

    pub(all) struct WebSocketPeer {
    connection_id : String
    subscribed_channels : Array[String]
    }

    WebSocketPeer::publish

    fn WebSocketPeer::publish(channel : String, message : String) -> Unit

    WebSocketPeer::send

    fn WebSocketPeer::send(self : WebSocketPeer, message : String) -> Unit

    WebSocketPeer::subscribe

    fn WebSocketPeer::subscribe(self : WebSocketPeer, channel : String) -> Unit

    WebSocketPeer::to_string

    fn WebSocketPeer::to_string(self : WebSocketPeer) -> String

    WebSocketPeer::unsubscribe

    fn WebSocketPeer::unsubscribe(self : WebSocketPeer, channel : String) -> Unit

    __ws_emit

    fn __ws_emit(event_type :
    CStr
    , connection_id :
    CStr
    , payload :
    CStr
    ) -> Unit

    async_run

    fn async_run(f : async () -> Unit noraise) -> Unit

    fn cookie_to_string(cookie : Array[CookieItem]) -> String

    execute_middlewares

    async fn execute_middlewares(middlewares : Array[(String, async (MocketEvent, async () -> HttpBody noraise) -> HttpBody noraise)], event : MocketEvent, final_handler : async (MocketEvent) -> HttpBody noraise) -> HttpBody noraise

    form_encode

    fn form_encode(map : Map[String, String]) -> String

    new

    fn new(base_path? : String, logger? : Logger) -> Mocket

    new_debug_logger

    fn new_debug_logger() -> Logger

    new_logger

    fn new_logger(enabled? : Bool, level? : LogLevel) -> Logger

    new_production_logger

    fn new_production_logger() -> Logger

    fn parse_cookie(cookie : StringView) -> Map[String, CookieItem]

    parse_form_data

    fn parse_form_data(bytes : BytesView) -> Map[String, String]

    parse_multipart

    fn parse_multipart(bytes : BytesView, boundary : String) -> Map[String, MultipartFormValue]

    register_ws_handler

    fn register_ws_handler(mocket : Mocket, port : Int) -> Unit

    serve_ffi

    fn serve_ffi(mocket : Mocket, port~ : Int) -> Unit

    suspend

    async fn[T, E : Error] suspend(f : ((T) -> Unit, (E) -> Unit) -> Unit) -> T raise E

    suspend 会中断当前协程的运行。 suspend 会接受一个回调函数,并让这个回调函数来操作中断的协程

    url_decode

    fn url_decode(bytes : BytesView) -> String

    url_encode

    fn url_encode(s : String) -> String

    ws_publish

    fn ws_publish(channel : String, msg : String) -> Unit

    ws_send

    fn ws_send(id : String, msg : String) -> Unit

    ws_subscribe

    fn ws_subscribe(id : String, channel : String) -> Unit

    ws_unsubscribe

    fn ws_unsubscribe(id : String, channel : String) -> Unit