Sign in

    http-async

    gaato/http Transport over moonbitlang/async. Native, JS, and moonrun Wasm targets.

    http
    transport
    async
    client
    Download zip
    Author
    Version
    0.2.0
    License
    Apache-2.0
    Last updated
    5 days ago
    Downloads
    1K

    #gaato/http-async

    AsyncTransport and AsyncClock: the gaato/http Transport and Clock over moonbitlang/async, for native, JS, and linear-memory Wasm on moonrun.

    gaato/http-async/mock has VirtualClock, a Clock for async tests whose time moves only when the test advances it: sleeps fire in deadline order, and settle lets the tasks under test run until they go idle between timers, so timing-dependent code runs the same way on every run and target.

    with_timeout_opt(clock, milliseconds, f) is @async.with_timeout_opt with the deadline taken from a Clock, so code that accepts a clock can have its timeouts driven by VirtualClock too.

    The Wasm backend uses MoonBit host APIs for sockets and TLS; it is not a browser or generic WASI transport. When using a moonrun --policy file, allow the destination host and port in net.connect. Loopback tests also need local socket bind permission. Native and Wasm share the HTTP implementation and connection/protocol error classification; JS uses fetch.

    AsyncTransport keeps a small keep-alive pool per origin (max_idle_per_origin, idle_max_ms), never queues concurrent requests, and replays a request once on a fresh connection when a reused one fails before the response head, but only for idempotent methods or requests carrying an Idempotency-Key. Call close() to drop parked connections. max_body_bytes caps a buffered response body (unbounded by default); a longer body closes the connection and fails with Protocol, while send_stream leaves the limit to its reader.

    In 0.1.2, a request's User-Agent is set when its connection is opened. Connections are reused only for requests with the same User-Agent; requests without one retain moonbitlang/async's default.

    Unofficial and experimental. Source, issues and design notes: https://github.com/gaato/mbt-sdk

    AsyncClock

    pub struct AsyncClock {
    }

    A production clock backed by moonbitlang/async timers and wall time.
    impl Clock for AsyncClock

    AsyncClock::new

    fn AsyncClock::new() -> AsyncClock

    Creates an async clock.

    test {
    let _ = @adapter.AsyncClock::new()
    }

    AsyncClock::now_unix_ms

    fn AsyncClock::now_unix_ms(_self : AsyncClock) -> Int64

    Explicit clock methods backed by moonbitlang/async.

    AsyncClock::sleep

    async fn AsyncClock::sleep(_self : AsyncClock, milliseconds : Int) -> Unit noraise

    Explicit clock methods backed by moonbitlang/async.

    AsyncTransport

    pub struct AsyncTransport {
    // private fields
    }

    An HTTP transport with bounded IO operations and a small keep-alive pool.

    A connection returns to the pool only after its response body has been consumed in full and the server did not answer Connection: close. At most max_idle_per_origin connections are parked per origin, each reusable for at most idle_max_ms; concurrent requests beyond that dial new connections and are never queued. No timer runs: every request, to any origin, closes the parked connections that have expired, so a transport left unused holds them until its next request or close(). When a reused connection fails before a response head, the request is sent once more on a fresh connection if it is idempotent or carries an Idempotency-Key; other requests surface the failure.

    AsyncTransport::close

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

    Closes every idle connection and refuses further requests with Connect("transport closed"). In-flight requests finish and their connections are closed instead of parked.

    test {
    let transport = @adapter.AsyncTransport::new()
    transport.close()
    assert_eq(transport.idle_connections(), 0)
    }

    AsyncTransport::idle_connections

    fn AsyncTransport::idle_connections(self : AsyncTransport) -> Int

    The number of parked keep-alive connections across all origins.

    AsyncTransport::new

    fn AsyncTransport::new(timeout_ms? : Int, max_idle_per_origin? : Int, idle_max_ms? : Int, max_body_bytes? : Int) -> AsyncTransport

    Creates a transport. Streaming reads each receive a fresh idle timeout. max_idle_per_origin = 0 or idle_max_ms = 0 disables connection reuse. max_body_bytes bounds a buffered response body: a longer one closes the connection and fails with Protocol. Without it the body is unbounded. Streams are never bounded, since their reader decides what to keep.

    test {
    let _ = @adapter.AsyncTransport::new(timeout_ms=1000)
    let _ = @adapter.AsyncTransport::new(max_idle_per_origin=0)
    let _ = @adapter.AsyncTransport::new(max_body_bytes=1_048_576)
    }

    AsyncTransport::send

    Explicit transport methods for buffered and streaming requests.

    AsyncTransport::send_stream

    Explicit transport methods for buffered and streaming requests.

    DeadlineTransport

    pub struct DeadlineTransport {
    // private fields
    }

    A per-exchange deadline: the whole buffered exchange, or only the response head for a streaming exchange. Expiry raises HttpError::Timeout(timeout_ms).

    DeadlineTransport::new

    Wraps a transport with a deadline; nonpositive deadlines expire immediately.

    DeadlineTransport::send

    Explicit transport methods with per-exchange deadlines.

    DeadlineTransport::send_stream

    Explicit transport methods with per-exchange deadlines.

    SemaphoreGate

    pub struct SemaphoreGate {
    // private fields
    }

    An admission gate backed by a cancellable async semaphore.

    SemaphoreGate::acquire

    async fn SemaphoreGate::acquire(self : SemaphoreGate) -> Unit noraise

    Explicit gate methods backed by the semaphore.

    SemaphoreGate::new

    fn SemaphoreGate::new(permits? : Int) -> SemaphoreGate

    Creates a gate with a positive number of permits (one by default).

    SemaphoreGate::release

    fn SemaphoreGate::release(self : SemaphoreGate) -> Unit

    Explicit gate methods backed by the semaphore.

    with_timeout_opt

    async fn[X] with_timeout_opt(clock : &
    Clock
    , milliseconds : Int, f : async () -> X) -> X?

    Runs f, giving up after milliseconds on clock: None when the time runs out first, in which case f is cancelled. Errors from f propagate. The clock-driven form of @async.with_timeout_opt, so a test can expire the deadline by advancing a virtual clock instead of waiting for it.

    A nonpositive milliseconds has already expired: f still starts, so a result it produces without waiting is returned, but anything it has to wait for gives up.

    async test {
    let clock = @adapter.AsyncClock::new()
    assert_eq(@adapter.with_timeout_opt(clock, 1000, () => 7), Some(7))
    }

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io