Sign in

    express

    An Express-compatible web framework for native MoonBit, served by µWebSockets (uWebSockets/uSockets bound through a C ABI shim).

    express
    http
    http-server
    router
    middleware
    webserver
    uWebSockets
    native
    Download zip
    Author
    Version
    0.2.0
    License
    Apache-2.0
    Last updated
    4 days ago
    Downloads
    16

    No dependencies

    #express

    An Express-style web framework for native MoonBit, served by µWebSockets — the same C++ engine behind uWebSockets.js. Routing, middleware, params and error handling are written in MoonBit; the HTTP parser, router entry point and event loop come from µWebSockets through a small C ABI shim.

    Inspired by fulmine.js, which puts an Express-compatible API on µWebSockets for Node.js.

    #Quick start

    ///|
    fn main {
    let app = @express.express()

    app.use(@express.logger())
    app.use(@express.express_json())

    app.get("/users/:id", fn(req, res, _next) {
    res.json_string("{\"id\":\"" + req.param("id") + "\"}")
    })

    app.post("/echo", fn(req, res, _next) {
    match req.json_body() {
    Some(value) => res.json(value)
    None => res.send("body=" + req.body())
    }
    })

    app.get("/boom", fn(_req, _res, next) { next.fail("boom!") })

    app.use_error(fn(message, _req, res, _next) {
    res.status(500).send("caught: " + message)
    })

    app.listen(8080, fn(ok) { println(ok.to_string()) })
    }

    #Features

    • Express-compatible routing — app.get/post/put/patch/delete/options/head/all(path, handler), app.add_route(method, path, handler), app.route(path).get(...).post(...) chainable builder, app.use(mw) / app.use_at(path, mw)
    • Route parameters — :name params, * and *name wildcards, req.param(name) / req.params()
    • Middleware chain — (Req, Res, Next) -> Unit with next.proceed() / next.fail(msg) / next.resume_with(err?)
    • Error middleware — app.use_error(mw) / app.use_error_at(path, mw) with (String, Req, Res, Next) -> Unit signature
    • Query & body parsing — req.query_param(name) / req.query_map() / req.raw_query(), JSON / URL-encoded (flat or extended) / text / raw body middleware
    • Cookies — req.cookie(name) / req.cookies(), signed cookies (req.signed_cookie, req.signed_cookies, res.cookie_with(..., signed=true)), res.cookie(name, value) / res.cookie_with(...) / res.clear_cookie(name)
    • Static files — express_static(root, options?) with ETag, Last-Modified, 304, Range (206/416), directory index, dotfiles and fallthrough control
    • Content negotiation — req.accepts/accepts_charsets/accepts_encodings/accepts_languages, res.format, res.type_
    • Conditional responses — req.fresh / req.stale, weak ETags, If-None-Match / If-Modified-Since → 304
    • Mounted routers — app.use_router(path, router) / router.use_router(path, router) with prefix stripping, base_url accumulation and merge_params
    • View rendering — app.engine(ext, fn) / app.set("view engine", ext) / res.render(view, locals?) / app.render(view, locals?)
    • Streaming — res.write / res.write_bytes / res.end_stream / res.flush_headers
    • Trust proxy — app.set("trust proxy", ...) drives req.ip / req.ips / req.protocol / req.secure / req.host / req.subdomains
    • Native WebSockets — direct µWebSockets upgrade, no Node.js http workaround
    • TLS / HTTPS — app.listen_tls(cert, key, port, cb) and listen_tls_host / listen_tls_async, with full UwsSslOptions support
    • CORS — built-in permissive CORS middleware with preflight handling
    • No X-Powered-By — the framework never sends that header

    #Requirements

    • MoonBit toolchain with the native backend (preferred_target = "native")
    • A C++17 compiler (g++ / clang++) — µWebSockets is header-only C++
    • zlib development headers, linked as -lz
    • OpenSSL development headers (libssl-dev), linked as -lssl -lcrypto

    TLS support is compiled into the library unconditionally, so every executable that depends on this package must link OpenSSL. A local build can drop TLS by removing MOONBIT_UWS_WITH_TLS from stub-cc-flags in moon.pkg; the vendored libusockets.h then falls back to LIBUS_NO_SSL and the link flags can omit -lssl -lcrypto.

    #Building an application

    This package is a library. The executable package that depends on it must provide the C++ runtime, zlib and OpenSSL at link time:

    import { "kupas/express" @lib, } supported_targets = "native" pkgtype(kind: "executable") options( "link": { "native": { "cc-link-flags": "-lstdc++ -lz -lssl -lcrypto", }, }, )

    Then:

    moon run example/main # demo server on :8080 moon test # router/query unit tests

    #API

    #Application

    MoonBitExpress.jsNotes
    express()express()Creates a new app
    app.get/post/put/patch/delete/options/head/all(path, handler)app.METHOD(path, handler)Route registration
    app.add_route(method, path, handler)app.METHOD(path, handler)Programmatic route registration (renamed from route)
    app.route(path)app.route(path)Chainable builder returning a RouteBuilder
    app.use(mw)app.use(mw)Global middleware
    app.use_at(path, mw)app.use(path, mw)Path-scoped middleware
    app.use_error(mw)app.use((err, req, res, next) => ...)Error middleware
    app.use_error_at(path, mw)app.use(path, (err, req, res, next) => ...)Path-scoped error middleware
    app.use_router(path, router)app.use(path, router)Mount a sub-router
    app.listen(port, cb)app.listen(port, cb)Blocks in event loop
    app.listen_host(host, port, cb)app.listen(port, host, cb)Specific host, blocks
    app.listen_async(host, port, cb)—Non-blocking; call app.run() after
    app.enable_tls(cert, key) / enable_tls_with(options)—Switch the app to HTTPS
    app.listen_tls(cert, key, port, cb)https.createServer(...).listen(...)TLS, blocks
    app.listen_tls_host(cert, key, host, port, cb)—TLS on a specific host, blocks
    app.listen_tls_async(cert, key, host, port, cb)—TLS, non-blocking
    app.run()—Runs the event loop (after listen_async)
    app.close()—Closes all sockets, stops event loop
    app.set(key, value) / app.get_setting(key)app.set(key, value) / app.get(key)App settings
    app.enable(name) / app.disable(name)app.enable(name) / app.disable(name)Toggle boolean settings
    app.enabled(name) / app.disabled(name)app.enabled(name) / app.disabled(name)Read boolean settings
    app.engine(ext, render)app.engine(ext, callback)Register a view engine
    app.render(view, locals?)app.render(view, locals?, cb)Render a view through an engine
    app.ws(path, behavior)app.ws(path, { open, message, close })WebSocket route
    app.param(name, callback)app.param(name, callback)Param middleware
    app.localsapp.localsApp-scoped local variables

    #Request (Req)

    MoonBitExpress.jsNotes
    req.method() / req.http_method()req.methodHTTP method (uppercase)
    req.path()req.pathURL path
    req.original_url()req.originalUrlURL before router mounting
    req.base_url()req.baseUrlAccumulated mount prefix
    req.url()req.urlURL relative to the current mount
    req.param(name)req.params[name]Route parameter
    req.params()req.paramsAll route parameters
    req.has_param(name)—Whether a param exists
    req.query_param(name)req.query[name]Parsed query parameter
    req.query_map()req.queryAll query parameters
    req.raw_query() / req.query()req.query (raw)Raw query string
    req.header(name) / req.get(name)req.get(name)Request header (case-insensitive)
    req.xhr()req.xhrWhether X-Requested-With is XMLHttpRequest
    req.body()req.bodyRaw body text
    req.body_bytes()req.body (Buffer)Raw body bytes
    req.json_body()req.body (parsed)Parsed JSON body
    req.form(name)req.body[name]Form field value
    req.form_map()req.bodyAll form fields
    req.form_json()req.bodyNested form body (extended mode)
    req.cookie(name)req.cookies[name]Cookie value
    req.cookies()req.cookiesAll cookies
    req.signed_cookie(name)req.signedCookies[name]Verified signed cookie
    req.signed_cookies()req.signedCookiesAll verified signed cookies
    req.ip()req.ipRemote IP (trust-proxy aware)
    req.ips()req.ipsIP chain from X-Forwarded-For
    req.protocol()req.protocolProtocol (trust-proxy aware)
    req.secure()req.secureWhether the request is TLS
    req.host()req.get('host')Host header (trust-proxy aware)
    req.hostname()req.hostnameHost without port
    req.subdomains()req.subdomainsSubdomain list
    req.remote_port()—Remote TCP port
    req.route()req.routeThe matched RouteInfo
    req.app()req.appThe owning application
    req.accepts(types)req.accepts(types)Content negotiation (media)
    req.accepts_charsets(types)req.acceptsCharsets(types)Charset negotiation
    req.accepts_encodings(types)req.acceptsEncodings(types)Encoding negotiation
    req.accepts_languages(types)req.acceptsLanguages(types)Language negotiation
    req.range(size)req.range(size)Parse a Range header
    req.fresh()req.freshConditional request matches
    req.stale()req.staleInverse of fresh
    req.is(type)req.is(type)Content-Type check
    req.is_aborted()—Whether the request was aborted
    req.localsreq.localsRequest-scoped local variables
    req.setting(key)req.app.get(key)Read an app setting

    #Response (Res)

    MoonBitExpress.jsNotes
    res.status(code)res.status(code)Set status code
    res.status_code()res.statusCodeGet status code
    res.send_status(code)res.sendStatus(code)Send status with its reason phrase
    res.set(name, value)res.set(field, value)Set response header
    res.set_header(name, value)res.setHeader(name, value)Alias for set
    res.get(name)res.get(name)Get response header
    res.headers_sent()res.headersSentWhether headers were flushed
    res.type_(content_type)res.type(type)Set Content-Type (normalised)
    res.send(text)res.send(text)Send text response
    res.send_bytes(bytes)res.send(Buffer)Send binary response
    res.send_empty()res.send()Send empty response
    res.json(value)res.json(value)Send JSON response
    res.json_string(raw)—Send pre-serialized JSON
    res.jsonp(value)res.jsonp(value)JSONP response
    res.redirect(url, status?)res.redirect(url, status?)Redirect (302 by default)
    res.render(view, locals?)res.render(view, locals?, cb)Render a view through an engine
    res.format(handlers)res.format(obj)Content-negotiated response
    res.cookie(name, value)res.cookie(name, value)Set cookie
    res.cookie_with(name, value, path?, max_age?, http_only?, domain?, expires?, secure?, same_site?, signed?)res.cookie(name, value, options)Cookie with options
    res.clear_cookie(name, path?)res.clearCookie(name, options?)Expire cookie
    res.write(chunk)res.write(chunk)Stream a text chunk
    res.write_bytes(chunk)res.write(chunk)Stream a binary chunk
    res.end_stream()res.end()End a streamed response
    res.flush_headers()res.flushHeaders()Flush headers without ending
    res.is_ended()—Whether response was sent
    res.is_aborted()—Whether response was aborted
    res.send_file(path, options?)res.sendFile(path, options?)Send file (ETag, Range, 304)
    res.download(path, filename?)res.download(path, filename?)Download file as attachment
    res.attachment(filename?)res.attachment(filename?)Set Content-Disposition
    res.append(field, value)res.append(field, value)Append response header
    res.links(links)res.links(links)Set Link header
    res.location(url)res.location(url)Set Location header
    res.vary(field)res.vary(field)Add Vary header
    res.localsres.localsResponse-scoped local variables

    #Middleware

    A middleware is (Req, Res, Next) -> Unit:

    • Call next.proceed() to continue to the next middleware/route
    • Call next.fail(message) to jump to error middleware
    • A route handler is also (Req, Res, Next) -> Unit; calling next.proceed() falls through to later routes

    fn(req, res, next) {
    println("before: " + req.path())
    next.proceed()
    println("after: " + req.path())
    }

    #Built-in middleware

    MoonBitExpress.jsDescription
    express_json(limit?)express.json(limit?)Parse application/json bodies
    express_urlencoded(limit?, extended?)express.urlencoded({ extended })Parse form bodies (flat or nested)
    express_text(limit?)express.text(limit?)Parse text/* bodies
    express_raw(limit?)express.raw(limit?)Parse raw bodies into bytes
    express_static(root, options?)express.static(root, options)Serve static files (ETag, Range, ...)
    cors()cors()Permissive CORS with preflight
    logger()morgan('dev')Minimal request logger

    #WebSockets

    µWebSockets' websocket support is exposed directly — no node:http upgrade workaround, the frames never leave the native engine:

    app.ws("/chat/:room", @express.ws_behavior(
    open=fn(conn) { conn.send("welcome") },
    message=fn(conn, message) {
    match message {
    @express.WsMessage::Text(text) => conn.send("echo:" + text)
    @express.WsMessage::Binary(data) => conn.send_bytes(data)
    }
    },
    close=fn(_conn, _code, _reason) { () },
    ))

    WsConn offers id(), send(text), send_bytes(bytes), send_frame(bytes, opcode, compress), and close(code?, reason?). All three behavior callbacks are optional.

    #TLS / HTTPS

    Call app.enable_tls (or one of the listen_tls* helpers) with PEM-encoded certificate and key files. Routes are registered exactly as for plain HTTP, and req.secure() / req.protocol() report the TLS state.

    ///|
    fn main {
    let app = @express.express()

    app.get("/", fn(req, res, _next) { res.send("hello over " + req.protocol()) })

    // Switches the app to TLS, then blocks in the µWebSockets event loop.
    app.listen_tls("cert.pem", "key.pem", 8443, fn(ok) {
    println("TLS listen: " + ok.to_string())
    })
    }

    Non-blocking TLS (call app.run() yourself):

    ///|
    fn main {
    let app = @express.express()
    app.get("/", fn(_req, res, _next) { res.send("hello over TLS") })

    app.listen_tls_async("cert.pem", "key.pem", "0.0.0.0", 8443, fn(ok) {
    println("TLS listen: " + ok.to_string())
    })
    app.run() // blocks
    }

    Advanced options use enable_tls_with, then start the app with the regular listen* calls:

    ///|
    fn main {
    let app = @express.express()
    app.enable_tls_with(@express.UwsSslOptions::{
    cert_file: "cert.pem",
    key_file: "key.pem",
    passphrase: Some("secret"),
    dh_params_file: None,
    ca_file: None,
    ssl_ciphers: Some("ECDHE+AESGCM"),
    })
    app.get("/", fn(_req, res, _next) { res.send("hello over TLS") })
    app.listen(8443, fn(ok) { println("TLS listen: " + ok.to_string()) })
    }

    For local testing, generate a self-signed pair:

    openssl req -x509 -newkey rsa:2048 -nodes -days 30 \ -keyout key.pem -out cert.pem -subj "/CN=localhost" moon run example/tls # HTTPS demo on :8443

    #Helpers exported for tests and custom routers

    make_pattern, PathPattern::match_path, split_path, parse_query, parse_urlencoded, parse_urlencoded_nested, parse_range_header, percent_decode, percent_decode_path, percent_encode, parse_param_names, status_text, mime_by_extension, mime_type, format_http_date, parse_http_date, sha256_hex, sign_cookie, unsign_cookie, negotiate_media, negotiate_charsets, negotiate_encodings, negotiate_languages.

    #How it works

    µWebSockets matches a single catch-all route (/*); everything Express-shaped happens in MoonBit (express.mbt, path.mbt, middleware.mbt). µWebSockets invalidates the request object when the handler returns, so the shim snapshots method/url/query/headers up front, and request bodies are delivered asynchronously by request id (uwb_res_on_data).

    src/uws_shim.cpp is the C++/C boundary; uws.mbt declares the extern "C" calls with FuncRef callbacks. Strings cross the boundary as UTF-8 Bytes.

    third_party/ vendors µWebSockets (App.h and friends, header-only) and the parts of µSockets needed for the epoll/kqueue event loop, including the OpenSSL TLS backend (crypto/openssl.c, crypto/sni_tree.cpp). Local patches, each marked with a comment:

    1. libusockets.h defines LIBUS_NO_SSL unless MOONBIT_UWS_WITH_TLS is set.
    2. internal/ and eventing/ includes use paths relative to their own files, so the stubs compile without -I flags.
    3. crypto/openssl.c includes ../internal/internal.h for the same reason.
    4. µWebSockets headers include the vendored libusockets.h by relative path.

    The shim dispatches every response write on the app's TLS mode, because µWebSockets compiles HttpResponse<true> and HttpResponse<false> separately and their socket-write paths differ.

    #Differences from Express

    • Handlers and middleware must name the extra parameter (fn(req, res, _next)) because MoonBit has no arity-based overloading.
    • next is a struct: next.proceed() / next.fail(msg) / next.resume_with(err?).
    • Errors are strings, not Error objects.
    • Route patterns support :name, * and *name; there is no regexp route.
    • Body parsing is opt-in per middleware, exactly like body-parser.
    • res.set() replaces a header; use res.append() to add multi-value headers.
    • req.params is a Map[String, String]; use req.has_param(name) to check existence.
    • X-Powered-By is never sent.
    • compression, session and multipart handling are out of scope (they are separate packages in the Express ecosystem); bring your own middleware.
    • trust proxy accepts "true", a hop count, a comma-separated IP list or the loopback / linklocal / uniquelocal presets. CIDR ranges are not parsed.
    • express.static does not support the setHeaders callback.
    • Router does not host WebSocket routes (app.ws is application-level only).
    • RouteInfo.method is exposed as http_method() (MoonBit reserves method).

    #Tests

    moon test covers route matching (static, params, wildcard, prefixes, trailing slashes, case-insensitivity), query parsing/percent decoding, status codes, SHA-256 and HMAC known-answer vectors, cookie signing, content negotiation, HTTP dates, Range parsing, nested URL-encoded bodies and MIME resolution. The demo in example/main is also a manual integration test:

    moon run example/main curl 'http://localhost:8080/users/42?tab=info' curl -X POST -H 'Content-Type: application/json' -d '{"a":1}' http://localhost:8080/echo

    #License

    Apache-2.0. Vendored µWebSockets and µSockets sources keep their own Apache-2.0 licenses in third_party/*/LICENSE.

    ErrorMiddleware

    type ErrorMiddleware = (String, Req, Res, Next) -> Unit

    Handler

    type Handler = (Req, Res, Next) -> Unit

    Middleware

    type Middleware = (Req, Res, Next) -> Unit

    ViewEngine

    type ViewEngine = (String, String, Json) -> String

    A view engine: receives the resolved template path, the template source and the locals, and returns the rendered string.

    App

    pub struct App {
    // private fields
    }

    App::add_route

    fn App::add_route(self : App, http_method : String, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    Registers a handler for one HTTP method (the low-level form of app.get/post/..., which build routes on top of it).

    App::all

    fn App::all(self : App, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    App::close

    fn App::close(self : App) -> Unit

    Closes all sockets, which also stops the event loop.

    App::delete

    fn App::delete(self : App, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    App::disable

    fn App::disable(self : App, key : String) -> Unit

    Turns a boolean setting off, like app.disable(name).

    App::disabled

    fn App::disabled(self : App, key : String) -> Bool

    Whether a boolean setting is disabled, like app.disabled(name).

    App::enable

    fn App::enable(self : App, key : String) -> Unit

    Turns a boolean setting on, like app.enable(name).

    App::enable_tls

    fn App::enable_tls(self : App, cert_file : String, key_file : String) -> Unit

    Switches the app to TLS before it starts listening. cert_file and key_file must point to PEM-encoded certificate and key. For extra options (CA bundle, ciphers, ...) use enable_tls_with.

    App::enable_tls_with

    fn App::enable_tls_with(self : App, options : UwsSslOptions) -> Unit

    Switches the app to TLS with full UwsSslOptions. Must run before listen* (or before prepare()); a TLS app is started with the same listen calls as a plain one.

    App::enabled

    fn App::enabled(self : App, key : String) -> Bool

    Whether a boolean setting is enabled, like app.enabled(name).

    App::engine

    fn App::engine(self : App, extension : String, render : (String, String, Json) -> String) -> Unit

    Registers a view engine for an extension, like app.engine("html", fn).

    App::get

    fn App::get(self : App, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    App::get_setting

    fn App::get_setting(self : App, key : String) -> String

    Returns a setting, "" when unset.

    App::head

    fn App::head(self : App, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    App::listen

    fn App::listen(self : App, port : Int, cb : (Bool) -> Unit) -> Unit

    Starts listening on all interfaces and runs the event loop. This call blocks until the server is closed.

    App::listen_async

    fn App::listen_async(self : App, host : String, port : Int, cb : (Bool) -> Unit) -> Unit

    Starts listening without blocking. Call run() yourself afterwards, for example when the process has other work to do.

    App::listen_host

    fn App::listen_host(self : App, host : String, port : Int, cb : (Bool) -> Unit) -> Unit

    Starts listening on a specific host and runs the event loop. This call blocks until the server is closed.

    App::listen_tls

    fn App::listen_tls(self : App, cert_file : String, key_file : String, port : Int, cb : (Bool) -> Unit) -> Unit

    Switches to TLS and starts listening on all interfaces, blocking in the µWebSockets event loop until close() is called.

    App::listen_tls_async

    fn App::listen_tls_async(self : App, cert_file : String, key_file : String, host : String, port : Int, cb : (Bool) -> Unit) -> Unit

    Switches to TLS and starts listening without blocking. Call run() yourself afterwards.

    App::listen_tls_host

    fn App::listen_tls_host(self : App, cert_file : String, key_file : String, host : String, port : Int, cb : (Bool) -> Unit) -> Unit

    Switches to TLS and starts listening on a specific host. This call blocks.

    App::locals

    fn App::locals(self : App) -> Map[String, String]

    Returns the app-scoped local variables map.

    App::options

    fn App::options(self : App, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    App::param

    fn App::param(self : App, name : String, callback : (Req, Next) -> Unit) -> Unit

    Adds a param callback. Called when a route param with name is matched. The callback receives (req, next) and should call next.proceed() to continue.

    App::patch

    fn App::patch(self : App, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    App::post

    fn App::post(self : App, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    App::prepare

    fn App::prepare(self : App) -> Unit

    Registers the catch-all route and publishes the app to the shim. Must run before listen_async.

    App::put

    fn App::put(self : App, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    App::render

    fn App::render(self : App, view : String, locals? : Json) -> String raise

    Renders a view through the registered engine, resolving it below the views setting (default ./views). Uses the view engine setting when the view has no engine extension.

    App::route

    fn App::route(self : App, path : String) -> RouteBuilder

    Creates a route builder for chaining handlers on one path, like app.route("/x").get(show).post(create).

    App::run

    fn App::run(self : App) -> Unit

    Runs the µWebSockets event loop. Blocks until close() is called.

    App::set

    fn App::set(self : App, key : String, value : String) -> Unit

    App::use_at

    fn App::use_at(self : App, path : String, middleware : (Req, Res, Next) -> Unit) -> Unit

    Registers middleware that only runs for paths starting with path, for example app.use_at("/api", auth).

    App::use_error

    fn App::use_error(self : App, middleware : (String, Req, Res, Next) -> Unit) -> Unit

    Registers error-handling middleware. It runs when a previous middleware called next.fail(...).

    App::use_error_at

    fn App::use_error_at(self : App, path : String, middleware : (String, Req, Res, Next) -> Unit) -> Unit

    App::use_middleware

    #alias(use)
    fn App::use_middleware(self : App, middleware : (Req, Res, Next) -> Unit) -> Unit

    Registers middleware for every request, app.use(mw) in Express.

    use is a reserved word in MoonBit, so the function is declared as use_middleware and exposed under the Express name with #alias.

    App::use_router

    fn App::use_router(self : App, path : String, router : Router) -> Unit

    Mounts a router under path, like app.use("/api", router). Requests see the mount prefix stripped in req.url and accumulated in req.base_url.

    App::ws

    fn App::ws(self : App, path : String, behavior : WsBehavior) -> Unit

    Registers a websocket route. Must be called before the app listens.

    FileOptions

    pub struct FileOptions {
    dotfiles : String
    index : String
    etag : Bool
    last_modified : Bool
    max_age : Int
    immutable : Bool
    fallthrough : Bool
    extensions : Array[String]
    redirect : Bool
    }

    Options shared by res.send_file and express_static, modeled on the send package Express delegates to.

    FileOptions::default

    fn FileOptions::default() -> FileOptions

    pub struct Next {
    // private fields
    }

    Continues the middleware chain. Call proceed() to keep going or fail(message) to hand control to the error middleware.

    Next::fail

    fn Next::fail(self : Next, message : String) -> Unit

    Next::fail_status

    fn Next::fail_status(self : Next, status : Int, message : String) -> Unit

    Fails with an HTTP status, the equivalent of Express' next(createError(status, message)).

    Next::proceed

    fn Next::proceed(self : Next) -> Unit

    Next::resume_with

    fn Next::resume_with(self : Next, error : String?) -> Unit

    PathPattern

    pub struct PathPattern {
    // private fields
    }

    A route pattern. Patterns are compiled once at registration time and matched in MoonBit, while the actual HTTP transport stays in µWebSockets.

    PathPattern::is_any

    fn PathPattern::is_any(self : PathPattern) -> Bool

    PathPattern::match_path

    fn PathPattern::match_path(self : PathPattern, path : String, prefix : Bool) -> Array[(String, String)]?

    Matches path against this pattern and returns captured parameters. When prefix is true the pattern only has to match the beginning of the path, which is how app.use("/api", ...) behaves.

    PathPattern::source

    fn PathPattern::source(self : PathPattern) -> String

    The original pattern string, as used by req.route.path.

    RangeResult

    pub enum RangeResult {
    NoRange
    Malformed
    Unsatisfiable
    Ranges(Array[(Int, Int)])
    }

    The outcome of parsing a Range header, mirroring range-parser.

    Req

    pub struct Req {
    // private fields
    }

    Req::accepts

    fn Req::accepts(self : Req, types : Array[String]) -> String

    Content negotiation: the best match from types for the Accept header, or "" when none is acceptable. Extensions such as html are supported.

    Req::accepts_charsets

    fn Req::accepts_charsets(self : Req, charsets : Array[String]) -> String

    req.acceptsCharsets, returning the client's preferred charset out of charsets or "".

    Req::accepts_encodings

    fn Req::accepts_encodings(self : Req, encodings : Array[String]) -> String

    req.acceptsEncodings, returning the client's preferred encoding out of encodings or "".

    Req::accepts_languages

    fn Req::accepts_languages(self : Req, languages : Array[String]) -> String

    req.acceptsLanguages, returning the client's preferred language out of languages or "".

    Req::app

    fn Req::app(self : Req) -> App

    The application that owns this request, like req.app.

    Req::base_url

    fn Req::base_url(self : Req) -> String

    The mount path of the current router, "" at the app level.

    Req::body

    fn Req::body(self : Req) -> String

    Req::body_bytes

    fn Req::body_bytes(self : Req) -> Bytes

    The raw request body bytes. Always binary safe; express_raw fills it.

    Req::content_type_is

    fn Req::content_type_is(self : Req, content_type : String) -> String

    Checks whether the request Content-Type matches content_type. Returns the matched type or "".

    Req::cookie

    fn Req::cookie(self : Req, name : String) -> String

    Returns a request cookie value, or "" when it is not present.

    Req::cookies

    fn Req::cookies(self : Req) -> Map[String, String]

    Req::form

    fn Req::form(self : Req, name : String) -> String

    Req::form_json

    fn Req::form_json(self : Req) -> Json?

    Form data parsed by express_urlencoded(extended=true), with nested objects and arrays, or None for the flat parser.

    Req::form_map

    fn Req::form_map(self : Req) -> Map[String, String]

    Req::fresh

    fn Req::fresh(self : Req) -> Bool

    req.fresh: whether the conditional request headers match the current response, so the response can be a 304 Not Modified.

    Req::get

    fn Req::get(self : Req, name : String) -> String

    Req::has_param

    fn Req::has_param(self : Req, name : String) -> Bool

    Req::header

    fn Req::header(self : Req, name : String) -> String

    Req::host

    fn Req::host(self : Req) -> String

    The Host header (or X-Forwarded-Host for trusted clients), req.host.

    Req::hostname

    fn Req::hostname(self : Req) -> String

    The host name without the port, like req.hostname.

    Req::http_method

    #alias(method)
    fn Req::http_method(self : Req) -> String

    The HTTP method of the request in uppercase ("GET", "POST", ...).

    This is req.method in Express. method is a reserved word in MoonBit, so the function is declared as http_method and exposed under the Express name with #alias.

    Req::ip

    fn Req::ip(self : Req) -> String

    The client address. Follows X-Forwarded-For when trust proxy is set, mirroring req.ip.

    Req::ips

    fn Req::ips(self : Req) -> Array[String]

    The X-Forwarded-For chain, farthest first, like req.ips. Empty when no proxy is trusted.

    Req::is_aborted

    fn Req::is_aborted(self : Req) -> Bool

    Req::json_body

    fn Req::json_body(self : Req) -> Json?

    Req::locals

    fn Req::locals(self : Req) -> Map[String, String]

    Returns the request-scoped local variables map.

    Req::original_url

    fn Req::original_url(self : Req) -> String

    The full URL of the original request, req.originalUrl.

    Req::param

    fn Req::param(self : Req, name : String) -> String

    Returns a route parameter, or "" when it is not present.

    Req::params

    fn Req::params(self : Req) -> Map[String, String]

    Req::path

    fn Req::path(self : Req) -> String

    Req::protocol

    fn Req::protocol(self : Req) -> String

    Returns the request protocol: "http" or "https", honoring X-Forwarded-Proto when the client is trusted.

    Req::query

    fn Req::query(self : Req) -> String

    The raw query string, without the leading ?.

    Req::query_map

    fn Req::query_map(self : Req) -> Map[String, String]

    Req::query_param

    fn Req::query_param(self : Req, name : String) -> String

    Returns a parsed query parameter, or "" when it is not present.

    Req::range

    fn Req::range(self : Req, size : Int) -> RangeResult

    Parses the Range header for an entity of size bytes, like req.range(size).

    Req::raw_query

    fn Req::raw_query(self : Req) -> String

    Req::remote_port

    fn Req::remote_port(self : Req) -> Int

    The remote TCP port of the request, like req.socket.remotePort.

    Req::route

    fn Req::route(self : Req) -> RouteInfo?

    The route currently handling the request, like req.route.

    Req::secure

    fn Req::secure(self : Req) -> Bool

    Returns true when the request was made over TLS.

    Req::set_body

    fn Req::set_body(self : Req, body : String) -> Unit

    Req::set_body_bytes

    fn Req::set_body_bytes(self : Req, body : Bytes) -> Unit

    Req::set_form

    fn Req::set_form(self : Req, value : Map[String, String]) -> Unit

    Req::set_form_json

    fn Req::set_form_json(self : Req, value : Json?) -> Unit

    Req::set_json_body

    fn Req::set_json_body(self : Req, value : Json?) -> Unit

    fn Req::signed_cookie(self : Req, name : String) -> String

    Returns a signed cookie, verified against the cookie secret setting. "" when the cookie is missing or its signature does not match.

    Req::signed_cookies

    fn Req::signed_cookies(self : Req) -> Map[String, String]

    All cookies with verified signatures, without the s: prefix.

    Req::stale

    fn Req::stale(self : Req) -> Bool

    req.stale, the negation of req.fresh.

    Req::subdomains

    fn Req::subdomains(self : Req) -> Array[String]

    The subdomain parts of req.hostname, most specific first, with the subdomain offset setting (default 2) segments removed. IP hosts yield an empty array, like Express.

    Req::subdomains_with

    fn Req::subdomains_with(self : Req, offset : String) -> Array[String]

    Same as subdomains, with an explicit offset. A negative offset is treated as 0.

    Req::url

    fn Req::url(self : Req) -> String

    The URL of the request relative to the current mount point, path plus query string, like req.url inside an Express router.

    Req::xhr

    fn Req::xhr(self : Req) -> Bool

    req.xhr: whether the request was issued by an XMLHttpRequest-style client (X-Requested-With: XMLHttpRequest).

    Res

    pub struct Res {
    // private fields
    }

    Res::append

    fn Res::append(self : Res, name : String, value : String) -> Res

    Appends a response header (allows multiple values for the same header).

    Res::attachment

    fn Res::attachment(self : Res, filename? : String) -> Res

    Sets Content-Disposition: attachment to trigger a download. If filename is provided, includes it in the header.
    fn Res::clear_cookie(self : Res, name : String, path? : String) -> Res

    Expires a cookie, mirroring Express res.clearCookie.

    Res::cookie

    fn Res::cookie(self : Res, name : String, value : String) -> Res

    Appends a Set-Cookie header with Path=/.

    Res::cookie_with

    fn Res::cookie_with(self : Res, name : String, value : String, path? : String, max_age? : Int, http_only? : Bool, domain? : String, expires? : String, secure? : Bool, same_site? : String, signed? : Bool) -> Res

    Appends a Set-Cookie header. A negative max_age omits Max-Age and expires is a full HTTP date string. With signed the value is prefixed with s: and signed with the cookie secret setting, like res.cookie(name, value, { signed: true }).

    Res::download

    fn Res::download(self : Res, path : String, filename? : String, options? : FileOptions) -> Unit

    Transfers the file at path as an attachment. Calls send_file after setting Content-Disposition.

    Res::end_stream

    fn Res::end_stream(self : Res) -> Unit

    Ends a streamed response.

    Res::flush_headers

    fn Res::flush_headers(self : Res) -> Unit

    Writes the status line and headers without ending the response, for streaming responses (Server-Sent Events, chunked downloads).

    Res::format

    fn Res::format(self : Res, handlers : Array[(String, (Req, Res, Next) -> Unit)]) -> Unit

    Content negotiation for responses, like res.format. handlers maps a media type or extension (or "default") to a handler; when nothing matches and no default is given the request fails with 406.

    Res::get

    fn Res::get(self : Res, name : String) -> String

    Alias of header, matching res.get in Express.

    Res::header

    fn Res::header(self : Res, name : String) -> String

    Res::headers_sent

    fn Res::headers_sent(self : Res) -> Bool

    Whether the status line and headers have been written to the socket already (after write, flush_headers or a send).

    Res::is_aborted

    fn Res::is_aborted(self : Res) -> Bool

    Res::is_ended

    fn Res::is_ended(self : Res) -> Bool

    Res::json

    fn Res::json(self : Res, value : Json) -> Unit

    Serializes value as JSON and sends it.

    Res::json_string

    fn Res::json_string(self : Res, raw : String) -> Unit

    Sends an already serialized JSON string.

    Res::jsonp

    fn Res::jsonp(self : Res, value : Json) -> Unit

    Sends JSONP. When the callback query parameter (named by the jsonp callback name setting, callback by default) is present, the body is a script invoking it; otherwise the response is plain JSON.
    fn Res::links(self : Res, links : Array[String]) -> Res

    Sets the Link header from an array of link objects. Each link is a string like </path>; rel="stylesheet".

    Res::locals

    fn Res::locals(self : Res) -> Map[String, String]

    Returns the response-scoped local variables map.

    Res::location

    fn Res::location(self : Res, url : String) -> Res

    Sets the Location header.

    Res::redirect

    fn Res::redirect(self : Res, url : String, status? : Int) -> Unit

    Redirects to url with 302 Found (or the given status), negotiating a text or HTML body from the Accept header like Express.

    Res::render

    fn Res::render(self : Res, view : String, locals? : Json) -> Unit

    Renders view through the app's view engine and sends the result, like res.render. locals and res.locals are merged with app.locals.

    Res::send

    fn Res::send(self : Res, body : String) -> Unit

    Sends a text response. Defaults Content-Type to text/html, like Express. HEAD requests are answered by µWebSockets with headers only.

    Res::send_bytes

    fn Res::send_bytes(self : Res, body : Bytes) -> Unit

    Sends raw bytes, useful for static files and binary payloads. Defaults Content-Type to application/octet-stream, like res.send(Buffer).

    Res::send_empty

    fn Res::send_empty(self : Res) -> Unit

    Ends the response without a body.

    Res::send_file

    fn Res::send_file(self : Res, path : String, options? : FileOptions) -> Unit

    Transfers the file at path as the response, like res.sendFile: sets Content-Type, Content-Length, Last-Modified, a weak ETag, and handles conditional requests and byte ranges.

    Res::send_status

    fn Res::send_status(self : Res, code : Int) -> Unit

    Sets the status from code and sends the matching reason phrase (or the number) as the body, like res.sendStatus.

    Res::set

    fn Res::set(self : Res, name : String, value : String) -> Res

    Res::set_header

    fn Res::set_header(self : Res, name : String, value : String) -> Unit

    Res::status

    fn Res::status(self : Res, code : Int) -> Res

    Res::status_code

    fn Res::status_code(self : Res) -> Int

    Res::type_

    fn Res::type_(self : Res, content_type : String) -> Res

    Sets Content-Type, accepting either a media type or an extension like html, and adding the default charset like Express does.

    Res::vary

    fn Res::vary(self : Res, field : String) -> Res

    Adds a field to the Vary header.

    Res::write

    fn Res::write(self : Res, chunk : String) -> Bool

    Streams a chunk of the response body. Writes the status line and buffered headers on first use. Returns false on socket backpressure.

    Res::write_bytes

    fn Res::write_bytes(self : Res, chunk : Bytes) -> Bool

    Streams a binary chunk of the response body.

    RouteBuilder

    pub struct RouteBuilder {
    // private fields
    }

    A route builder for chaining handlers on one path: app.route("/x").get(show).post(create).

    RouteBuilder::all

    fn RouteBuilder::all(self : RouteBuilder, handler : (Req, Res, Next) -> Unit) -> RouteBuilder

    RouteBuilder::delete

    fn RouteBuilder::delete(self : RouteBuilder, handler : (Req, Res, Next) -> Unit) -> RouteBuilder

    RouteBuilder::get

    fn RouteBuilder::get(self : RouteBuilder, handler : (Req, Res, Next) -> Unit) -> RouteBuilder

    RouteBuilder::head

    fn RouteBuilder::head(self : RouteBuilder, handler : (Req, Res, Next) -> Unit) -> RouteBuilder

    RouteBuilder::options

    fn RouteBuilder::options(self : RouteBuilder, handler : (Req, Res, Next) -> Unit) -> RouteBuilder

    RouteBuilder::patch

    fn RouteBuilder::patch(self : RouteBuilder, handler : (Req, Res, Next) -> Unit) -> RouteBuilder

    RouteBuilder::post

    fn RouteBuilder::post(self : RouteBuilder, handler : (Req, Res, Next) -> Unit) -> RouteBuilder

    RouteBuilder::put

    fn RouteBuilder::put(self : RouteBuilder, handler : (Req, Res, Next) -> Unit) -> RouteBuilder

    RouteInfo

    pub struct RouteInfo {
    http_method : String
    path : String
    }

    Identifies the route currently handling the request, mirroring req.route in Express.

    RouteInfo::http_method

    #alias(method)
    fn RouteInfo::http_method(self : RouteInfo) -> String

    The method of the matched route. method is a reserved word in MoonBit, so the accessor is declared as http_method and exposed under the Express name with #alias.

    Router

    pub struct Router {
    // private fields
    }

    A standalone router, like express.Router(). Mount it with app.use_router(path, router); merge_params controls whether the parent router's params stay visible inside (Express' mergeParams).

    Router::add_route

    fn Router::add_route(self : Router, http_method : String, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    Registers a handler on a router for one HTTP method.

    Router::all

    fn Router::all(self : Router, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    Router::delete

    fn Router::delete(self : Router, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    Router::get

    fn Router::get(self : Router, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    Router::head

    fn Router::head(self : Router, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    Router::new

    fn Router::new(merge_params? : Bool) -> Router

    Router::options

    fn Router::options(self : Router, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    Router::param

    fn Router::param(self : Router, name : String, callback : (Req, Next) -> Unit) -> Unit

    Adds a param callback to a router, like router.param(name, fn).

    Router::patch

    fn Router::patch(self : Router, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    Router::post

    fn Router::post(self : Router, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    Router::put

    fn Router::put(self : Router, path : String, handler : (Req, Res, Next) -> Unit) -> Unit

    Router::route

    fn Router::route(self : Router, path : String) -> RouteBuilder

    Creates a route builder on a router, like router.route("/x").

    Router::use_at

    fn Router::use_at(self : Router, path : String, middleware : (Req, Res, Next) -> Unit) -> Unit

    Registers middleware on a router for paths starting with path.

    Router::use_error

    fn Router::use_error(self : Router, middleware : (String, Req, Res, Next) -> Unit) -> Unit

    Router::use_error_at

    fn Router::use_error_at(self : Router, path : String, middleware : (String, Req, Res, Next) -> Unit) -> Unit

    Router::use_middleware

    #alias(use)
    fn Router::use_middleware(self : Router, middleware : (Req, Res, Next) -> Unit) -> Unit

    Registers middleware on a router for every request, router.use(mw) in Express. Declared as use_middleware and exposed as use with #alias because use is reserved in MoonBit.

    Router::use_router

    fn Router::use_router(self : Router, path : String, router : Router) -> Unit

    Mounts a nested router, like router.use("/sub", subRouter).

    UwsApp

    #external
    pub type UwsApp

    UwsReq

    #external
    pub type UwsReq

    UwsRes

    #external
    pub type UwsRes

    UwsSocket

    #external
    pub type UwsSocket

    UwsSslOptions

    pub struct UwsSslOptions {
    cert_file : String
    key_file : String
    passphrase : String?
    dh_params_file : String?
    ca_file : String?
    ssl_ciphers : String?
    }

    TLS options for creating an SSL-enabled app.

    WsBehavior

    pub struct WsBehavior {
    open : (WsConn) -> Unit?
    message : (WsConn, WsMessage) -> Unit?
    close : (WsConn, Int, String) -> Unit?
    }

    Callbacks for one websocket route. All of them are optional.

    WsConn

    pub struct WsConn {
    // private fields
    }

    One open websocket connection.

    WsConn::close

    fn WsConn::close(self : WsConn, code? : Int, reason? : String) -> Unit

    Closes the connection. Defaults to a normal closure (1000).

    WsConn::id

    fn WsConn::id(self : WsConn) -> UInt64

    Connection id, unique for the lifetime of the process.

    WsConn::send

    fn WsConn::send(self : WsConn, text : String) -> Unit

    Sends a text frame.

    WsConn::send_bytes

    fn WsConn::send_bytes(self : WsConn, data : Bytes) -> Unit

    Sends a binary frame.

    WsConn::send_frame

    fn WsConn::send_frame(self : WsConn, data : Bytes, opcode : Int, compress : Bool) -> Unit

    Sends an arbitrary frame, e.g. a ping, optionally compressed.

    WsMessage

    pub enum WsMessage {
    Text(String)
    Binary(Bytes)
    }

    A message delivered on a websocket.

    base64_encode

    fn base64_encode(data : Bytes) -> String

    Standard base64 with padding, as used by cookie-signature.

    bytes_to_hex

    fn bytes_to_hex(data : Bytes) -> String

    Lower-case hex encoding.

    constant_time_equal

    fn constant_time_equal(a : Bytes, b : Bytes) -> Bool

    Time-independent byte comparison, for signatures and MACs.

    cors

    fn cors() -> ((Req, Res, Next) -> Unit)

    Permissive CORS middleware, including preflight responses.

    dispatch_abort

    fn dispatch_abort(request_id : UInt64) -> Unit

    Dispatches an abort notification to the pending request.

    dispatch_body

    fn dispatch_body(request_id : UInt64, body : Bytes, _len : Int, status : Int) -> Unit

    Dispatches request body data to the pending request. The raw bytes stay on the Req so that binary-safe parsers can use them.

    dispatch_request

    fn dispatch_request(app_id : UInt64, uws_res : UwsRes, _uws_req : UwsReq) -> Unit

    Dispatches an incoming HTTP request to the registered app.

    escape_html

    fn escape_html(value : String) -> String

    Escapes HTML special characters, like escape-html.

    express

    fn express() -> App

    Creates a new application.

    express_json

    fn express_json(limit? : Int) -> ((Req, Res, Next) -> Unit)

    Parses application/json request bodies into req.json_body() and also keeps the raw text in req.body().

    express_raw

    fn express_raw(limit? : Int) -> ((Req, Res, Next) -> Unit)

    Reads a binary body into req.body_bytes(), and its lossy text form into req.body(), like express.raw(). The raw bytes are always binary safe.

    express_router

    fn express_router() -> Router

    Creates a new router, like express.Router().

    express_static

    fn express_static(root : String, options? : FileOptions) -> ((Req, Res, Next) -> Unit)

    Serves files below root, like express.static: Content-Type, ETag, Last-Modified, byte ranges and Cache-Control come from res.send_file. Missing files fall through to the next middleware unless fallthrough is disabled.

    express_text

    fn express_text(limit? : Int) -> ((Req, Res, Next) -> Unit)

    Reads a text/* body into req.body().

    express_urlencoded

    fn express_urlencoded(limit? : Int, extended? : Bool) -> ((Req, Res, Next) -> Unit)

    Parses application/x-www-form-urlencoded bodies into req.form_map(). With extended the nested form (a[b]=1) is also parsed into req.form_json(), like express.urlencoded({ extended: true }).

    format_http_date

    fn format_http_date(epoch_seconds : Int64) -> String

    Formats seconds since the Unix epoch as an IMF-fixdate, e.g. Sun, 06 Nov 1994 08:49:37 GMT.

    hmac_sha256

    fn hmac_sha256(key : Bytes, message : Bytes) -> Bytes

    HMAC-SHA256 (RFC 2104) with a 64-byte block size.

    logger

    fn logger() -> ((Req, Res, Next) -> Unit)

    Minimal request logger.

    make_pattern

    fn make_pattern(path : String) -> PathPattern

    mime_by_extension

    fn mime_by_extension(extension : String) -> String

    MIME type for a file extension, without parameters. "" when unknown.

    mime_type

    fn mime_type(path : String) -> String

    MIME type for a file path, with the charset used by express.static.

    negotiate_charsets

    fn negotiate_charsets(header : String, provided : Array[String]) -> String

    req.acceptsCharsets. With no header every charset is acceptable, so the first provided one wins.

    negotiate_encodings

    fn negotiate_encodings(header : String, provided : Array[String]) -> String

    req.acceptsEncodings. With no header identity is the only acceptable encoding, like negotiator's implicit identity entry.

    negotiate_languages

    fn negotiate_languages(header : String, provided : Array[String]) -> String

    req.acceptsLanguages. With no header every language is acceptable.

    negotiate_media

    fn negotiate_media(accept : String, provided : Array[String]) -> String

    req.accepts for media types. Extensions such as html are mapped to their media type before matching, and the caller's original string is returned. An empty accept means "no header".

    parse_cookies

    fn parse_cookies(raw : String) -> Map[String, String]

    Parses a Cookie request header (a=1; b=hello%20world) into a map.

    parse_http_date

    fn parse_http_date(value : String) -> Int64?

    Parses an IMF-fixdate into seconds since the epoch. None for any other format, matching how Express falls back to ignoring the header.

    parse_param_names

    fn parse_param_names(path : String) -> Array[String]

    Returns the parameter names of a route pattern, in order.

    parse_query

    fn parse_query(raw : String) -> Map[String, String]

    Parses a raw query string (a=1&b=hello+world) into a map.

    parse_range_header

    fn parse_range_header(header : String, size : Int) -> RangeResult

    Parses a Range header for an entity of size bytes, the same grammar as the range-parser package: bytes=0-4, bytes=5-, bytes=-3 and multiple comma separated ranges.

    parse_urlencoded

    fn parse_urlencoded(raw : String) -> Map[String, String]

    Parses an application/x-www-form-urlencoded body. This is the same grammar as a query string.

    parse_urlencoded_nested

    fn parse_urlencoded_nested(raw : String) -> Json

    Parses an application/x-www-form-urlencoded body with bracketed names (a[b]=1&a[]=2&a[0]=3) into nested JSON, mirroring the extended mode of express.urlencoded.

    percent_decode

    fn percent_decode(input : String) -> String

    Decodes percent-encoded bytes in a URL query component. + is treated as a space, matching application/x-www-form-urlencoded.

    percent_decode_path

    fn percent_decode_path(input : String) -> String

    Same as percent_decode, but leaves + untouched. Use this for path segments, where + is a literal plus sign.

    percent_encode

    fn percent_encode(input : String) -> String

    Percent-encodes a value for use in a query string or cookie, keeping the unreserved characters from RFC 3986 untouched.

    sha256_bytes

    fn sha256_bytes(data : Bytes) -> Bytes

    SHA-256 (FIPS 180-4) over data, returning the 32 raw digest bytes.

    sha256_hex

    fn sha256_hex(data : String) -> String

    Hex digest of sha256_bytes, lower case.
    fn sign_cookie(secret : String, value : String) -> String

    Signs a cookie value the way the cookie-signature package does: value + "." + base64(hmac_sha256(secret, value)) with the = padding stripped.

    split_path

    fn split_path(path : String) -> Array[String]

    Splits a URL path into non-empty segments. Repeated slashes and a trailing slash are ignored, so /users/ and /users match the same route.

    status_text

    fn status_text(code : Int) -> String

    HTTP reason phrase for the status codes used by the framework.
    fn unsign_cookie(secret : String, signed : String) -> String?

    Verifies a value produced by sign_cookie and returns the original value, or None when the signature does not match.

    ws_behavior

    fn ws_behavior(open? : (WsConn) -> Unit, message? : (WsConn, WsMessage) -> Unit, close? : (WsConn, Int, String) -> Unit) -> WsBehavior

    Builds a websocket behavior, mirroring app.ws("/path", { open, message, close }).