///|
/// Raw native binding to the C ABI exposed by `src/uws_shim.cpp`.
///
/// µWebSockets is driven from C++ (`third_party/uwebsockets`), so MoonBit only
/// ever holds opaque pointers plus UTF-8 `Bytes` copies. Nothing in this file
/// should be used directly by applications; `express.mbt` wraps it.
///|
#external
pub type UwsApp
///|
#external
pub type UwsRes
///|
#external
pub type UwsReq
///|
#external
pub type UwsSocket
///|
/// TLS options for creating an SSL-enabled app.
pub struct UwsSslOptions {
cert_file : String
key_file : String
passphrase : String?
dh_params_file : String?
ca_file : String?
ssl_ciphers : String?
}
///|
/// Converts a MoonBit string to the UTF-8 bytes used across the FFI.
fn utf8_bytes(text : String) -> Bytes {
@utf8.encode(text)
}
///|
/// Converts UTF-8 bytes coming from C back into a MoonBit string.
fn utf8_string(bytes : Bytes) -> String {
@utf8.decode_lossy(bytes)
}
// ---------- callbacks into MoonBit ----------
///|
/// Registers the three C callbacks. Runs once at startup.
/// Uses FuncRef so MoonBit passes fat pointers {env, fn}.
extern "C" fn uwb_set_callbacks(
on_request : FuncRef[(UInt64, UwsRes, UwsReq) -> Unit],
on_body : FuncRef[(UInt64, Bytes, Int32, Int32) -> Unit],
on_abort : FuncRef[(UInt64) -> Unit],
) -> Unit = "uwb_set_callbacks"
// ---------- application ----------
///|
extern "C" fn uwb_app_new() -> UwsApp = "uwb_app_new"
///|
/// Upgrades the app to TLS with the given PEM options. Empty option strings
/// mean "not set", except `cert_file`/`key_file` which are required.
/// `options` fields are extracted here so the C ABI only ever sees byte slices.
#borrow(cert_file, key_file, passphrase, dh_params_file, ca_file, ssl_ciphers)
extern "C" fn uwb_app_enable_ssl(
app : UwsApp,
cert_file : Bytes,
cert_len : Int32,
key_file : Bytes,
key_len : Int32,
passphrase : Bytes,
passphrase_len : Int32,
dh_params_file : Bytes,
dh_params_len : Int32,
ca_file : Bytes,
ca_len : Int32,
ssl_ciphers : Bytes,
ciphers_len : Int32,
) -> Unit = "uwb_app_enable_ssl"
///|
/// FFI helper: enables TLS from `UwsSslOptions`, flattening optional strings.
fn enable_ssl(app : UwsApp, options : UwsSslOptions) -> Unit {
let cert = utf8_bytes(options.cert_file)
let key = utf8_bytes(options.key_file)
let passphrase = match options.passphrase {
Some(value) => utf8_bytes(value)
None => utf8_bytes("")
}
let dh_params = match options.dh_params_file {
Some(value) => utf8_bytes(value)
None => utf8_bytes("")
}
let ca = match options.ca_file {
Some(value) => utf8_bytes(value)
None => utf8_bytes("")
}
let ciphers = match options.ssl_ciphers {
Some(value) => utf8_bytes(value)
None => utf8_bytes("")
}
uwb_app_enable_ssl(
app,
cert,
cert.length(),
key,
key.length(),
passphrase,
passphrase.length(),
dh_params,
dh_params.length(),
ca,
ca.length(),
ciphers,
ciphers.length(),
)
}
///|
#borrow(path)
extern "C" fn uwb_app_any(
app : UwsApp,
app_id : UInt64,
path : Bytes,
path_len : Int32,
) -> Unit = "uwb_app_any"
///|
#borrow(host)
extern "C" fn uwb_app_listen(
app : UwsApp,
app_id : UInt64,
host : Bytes,
host_len : Int32,
port : Int32,
) -> Int32 = "uwb_app_listen"
///|
extern "C" fn uwb_app_run(app : UwsApp) -> Unit = "uwb_app_run"
///|
extern "C" fn uwb_app_close(app : UwsApp) -> Unit = "uwb_app_close"
// ---------- request snapshot ----------
///|
extern "C" fn uwb_req_method() -> Bytes = "uwb_req_method"
///|
extern "C" fn uwb_req_url() -> Bytes = "uwb_req_url"
///|
extern "C" fn uwb_req_query() -> Bytes = "uwb_req_query"
///|
extern "C" fn uwb_req_header_count() -> Int32 = "uwb_req_header_count"
///|
extern "C" fn uwb_req_header_name(index : Int32) -> Bytes = "uwb_req_header_name"
///|
extern "C" fn uwb_req_header_value(index : Int32) -> Bytes = "uwb_req_header_value"
// ---------- response ----------
///|
#borrow(status)
extern "C" fn uwb_res_write_status(
res : UwsRes,
ssl : Int32,
status : Bytes,
len : Int32,
) -> Unit = "uwb_res_write_status"
///|
#borrow(name, value)
extern "C" fn uwb_res_write_header(
res : UwsRes,
ssl : Int32,
name : Bytes,
name_len : Int32,
value : Bytes,
value_len : Int32,
) -> Unit = "uwb_res_write_header"
///|
#borrow(body)
extern "C" fn uwb_res_end(
res : UwsRes,
ssl : Int32,
body : Bytes,
len : Int32,
) -> Unit = "uwb_res_end"
///|
extern "C" fn uwb_res_end_empty(res : UwsRes, ssl : Int32) -> Unit = "uwb_res_end_empty"
///|
#borrow(data)
extern "C" fn uwb_res_write(
res : UwsRes,
ssl : Int32,
data : Bytes,
len : Int32,
) -> Int32 = "uwb_res_write"
///|
extern "C" fn uwb_res_on_aborted(
res : UwsRes,
ssl : Int32,
request_id : UInt64,
) -> Unit = "uwb_res_on_aborted"
///|
extern "C" fn uwb_res_on_data(
res : UwsRes,
ssl : Int32,
request_id : UInt64,
limit : Int32,
) -> Unit = "uwb_res_on_data"
///|
extern "C" fn uwb_res_remote_address(res : UwsRes, ssl : Int32) -> Bytes = "uwb_res_remote_address"
///|
extern "C" fn uwb_res_remote_port(res : UwsRes, ssl : Int32) -> Int32 = "uwb_res_remote_port"
// ---------- websockets ----------
///|
extern "C" fn uwb_set_ws_callbacks(
on_open : FuncRef[(UInt64, Int32, UwsSocket) -> Unit],
on_message : FuncRef[(UInt64, Bytes, Int32, Int32) -> Unit],
on_close : FuncRef[(UInt64, Int32, Bytes, Int32) -> Unit],
) -> Unit = "uwb_set_ws_callbacks"
///|
#borrow(path)
extern "C" fn uwb_app_ws(
app : UwsApp,
route_id : Int32,
path : Bytes,
path_len : Int32,
) -> Unit = "uwb_app_ws"
///|
#borrow(data)
extern "C" fn uwb_ws_send(
ws : UwsSocket,
data : Bytes,
len : Int32,
opcode : Int32,
compress : Int32,
) -> Unit = "uwb_ws_send"
///|
#borrow(message)
extern "C" fn uwb_ws_close(
ws : UwsSocket,
code : Int32,
message : Bytes,
msg_len : Int32,
) -> Unit = "uwb_ws_close"
///|
#borrow(data)
extern "C" fn uwb_ws_send_ssl(
ws : UwsSocket,
data : Bytes,
len : Int32,
opcode : Int32,
compress : Int32,
) -> Unit = "uwb_ws_send_ssl"
///|
#borrow(message)
extern "C" fn uwb_ws_close_ssl(
ws : UwsSocket,
code : Int32,
message : Bytes,
msg_len : Int32,
) -> Unit = "uwb_ws_close_ssl"
// ---------- filesystem helpers ----------
///|
#borrow(path)
extern "C" fn uwb_fs_kind(path : Bytes, path_len : Int32) -> Int32 = "uwb_fs_kind"
///|
#borrow(path)
extern "C" fn uwb_fs_read_file(path : Bytes, path_len : Int32) -> Bytes = "uwb_fs_read_file"
///|
#borrow(path)
extern "C" fn uwb_fs_mtime(path : Bytes, path_len : Int32) -> Int64 = "uwb_fs_mtime"
///|
/// `0` missing, `1` regular file, `2` directory.
fn fs_kind(path : String) -> Int32 {
let bytes = utf8_bytes(path)
uwb_fs_kind(bytes, bytes.length())
}
///|
fn fs_read_file(path : String) -> Bytes {
let bytes = utf8_bytes(path)
uwb_fs_read_file(bytes, bytes.length())
}
///|
/// Last modification time in seconds since the epoch, `-1` when missing.
fn fs_mtime(path : String) -> Int64 {
let bytes = utf8_bytes(path)
uwb_fs_mtime(bytes, bytes.length())
}
///|
/// Joins a directory and a URL path, collapsing the boundary slashes.
fn join_path(base : String, rel : String) -> String {
let mut left = base
while left.has_suffix("/") {
left = left[:left.length() - 1].to_owned()
}
let mut right = rel
if !right.has_prefix("/") {
right = "/" + right
}
left + right
}