mio

    Best async http library for Moonbit

    network
    async
    http
    Download zip
    Author
    Version
    0.5.4
    License
    Apache-2.0
    Last updated
    last month
    Downloads
    555

    #oboard/mio

    A MoonBit HTTP networking library with native HTTP/1.1, experimental HTTP/2 and HTTP/3 transports, and JavaScript Fetch support.

    Version License

    #Features

    • 🚀 Async support: Request APIs are async on native and JavaScript targets.
    • 🌐 HTTP methods: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD, CONNECT, and TRACE are represented by RequestMethod.
    • 📄 Response helpers: Read response bodies as binary, text, or JSON.
    • 🌊 Streaming HTTP/1.1: Native stream APIs expose request/response bodies through @io.Reader and @io.Writer.
    • 📦 Compression: Native responses can decode gzip, deflate, br, and zstd content encodings.
    • 🎯 Multi-backend: Native uses MoonBit transports; JavaScript uses the platform Fetch implementation.
    • 🔧 Builder options: Configure default headers, timeout, protocol mode, proxy, and certificate verification.

    #Installation

    Add to your moon.mod.json:

    moon add oboard/mio

    #Quick Start

    #Basic GET Request

    let response = @mio.get("https://api.github.com") catch {
    Err(e) => println("Error: " + e.to_string())
    }
    println("Response: " + response.text())

    #Request Builder

    ///|
    let client = @mio.RequestClient::builder()
    .default_header("User-Agent", "mio")
    .timeout(10000)
    .build()

    ///|
    let response = client
    .post("https://api.example.com/items")
    .json({ "name": "moonbit" })
    .send()

    #HTTP/2 Prior Knowledge

    ///|
    let client = @mio.RequestClient::builder()
    .http2_prior_knowledge()
    .timeout(10000)
    .build()

    ///|
    let response = client.get("http://localhost:3000/").send()
    println(response.text())

    #HTTP/3 Prior Knowledge

    ///|
    let client = @mio.RequestClient::builder()
    .http3_prior_knowledge()
    .timeout(10000)
    .build()

    ///|
    let response = client.get("https://example.com/").send()
    println(response.text())

    danger_accept_invalid_certs(true) disables certificate and hostname verification. It is intended for local testing only.

    #Protocol Support

    ModeNative statusNotes
    HTTP/1.1SupportedDirect TCP/TLS transport with streaming APIs.
    HTTP/2ExperimentalPrior-knowledge client, HPACK, SETTINGS/HEADERS/DATA, PING, WINDOW_UPDATE, and GOAWAY handling.
    HTTP/3ExperimentalUDP QUIC path with X25519, TLS 1.3 ServerHello, EncryptedExtensions, Finished, QUIC Retry, Handshake ACKs, H3 SETTINGS, request streams, and response decoding.
    JavaScriptRuntime-managedUses Fetch; protocol negotiation is handled by the host runtime.

    HTTP/2 and HTTP/3 are currently single-request transports. They do not yet provide a hyper-style connection task, connection pooling, multiplexed request scheduling, broad retransmission/loss recovery, or full certificate-chain validation.

    #Hyper Comparison

    hyper separates an established connection into a request sender and a connection future that continuously drives protocol state. mio currently keeps HTTP/2 and HTTP/3 as compact single-request loops. The implementation is moving toward the same separation of concerns:

    • request construction is handled by RequestClient and RequestBuilder;
    • protocol-specific transports live in HTTP/2 and HTTP/3 modules;
    • connection state such as QUIC packet number spaces, Retry handling, and TLS transcript state is kept below the request API.

    Unlike hyper, this package includes a MoonBit-native experimental QUIC/TLS 1.3/HTTP/3 path. That path is intentionally conservative and still needs more transport work before it should be treated as production-ready.

    #Contributing

    1. Fork the repository
    2. Create your feature branch (git checkout -b feature/amazing-feature)
    3. Commit your changes (git commit -m 'Add amazing feature')
    4. Push to the branch (git push origin feature/amazing-feature)
    5. Open a Pull Request

    #License

    This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

    HpackError

    pub suberror HpackError {
    HpackBadInteger
    HpackBadString
    HpackBadHuffmanPadding
    HpackHuffmanEOS
    HpackBadIndex(Int)
    HpackBadDynamicTableSize(Int)
    } derive(ToJson,
    Debug
    )

    Http2Error

    pub suberror Http2Error {
    Http2BadFrame
    Http2ConnectionError(String)
    Http2StreamReset(Int)
    Http2MissingStatus
    } derive(ToJson,
    Debug
    )

    HttpProtocolError

    pub suberror HttpProtocolError {
    BadRequest
    HttpVersionNotSupported(String)
    NotImplemented
    } derive(ToJson,
    Debug
    )

    IncorrectBodyLength

    pub suberror IncorrectBodyLength derive(ToJson,
    Debug
    )

    ProxyError

    pub suberror ProxyError {
    ProxyError(Response)
    } derive(ToJson,
    Debug
    )

    impl Show for ProxyError

    RequestError

    pub suberror RequestError {
    UnsupportedHttpVersion(HttpVersion)
    } derive(ToJson,
    Debug
    )

    URIParseError

    pub suberror URIParseError {
    InvalidFormat
    UnsupportedProtocol(String)
    } derive(ToJson,
    Debug
    )

    Client

    pub struct Client {
    // private fields
    }

    impl Reader for Client
    impl Writer for Client

    Client::Client

    async fn Client::Client(uri : String, headers? : Map[String, String], proxy? : Client, verify? : Bool) -> Client

    Client::close

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

    Client::end_request

    async fn Client::end_request(self : Client) -> Response

    Client::enter_passthrough_mode

    async fn Client::enter_passthrough_mode(self : Client) -> Unit

    Client::flush

    async fn Client::flush(self : Client) -> Unit

    Client::get

    async fn Client::get(self : Client, path : String, extra_headers? : Map[String, String], body? : &
    Data
    ) -> Response

    Client::new

    async fn Client::new(uri : String, headers? : Map[String, String], proxy? : Client, verify? : Bool) -> Client

    Client::post

    async fn Client::post(self : Client, path : String, body : &
    Data
    , extra_headers? : Map[String, String]) -> Response

    Client::put

    async fn Client::put(self : Client, path : String, body : &
    Data
    , extra_headers? : Map[String, String]) -> Response

    Client::request

    async fn Client::request(self : Client, meth : RequestMethod, path : String, extra_headers? : Map[String, String]) -> Unit

    Client::skip_response_body

    async fn Client::skip_response_body(self : Client) -> Unit

    ClientBuilder

    pub struct ClientBuilder {
    headers : Map[String, String]
    proxy : Client?
    timeout : Int?
    version : HttpVersion
    verify : Bool
    }

    ClientBuilder::ClientBuilder

    #alias(new)
    fn ClientBuilder::ClientBuilder() -> ClientBuilder

    ClientBuilder::build

    ClientBuilder::danger_accept_invalid_certs

    fn ClientBuilder::danger_accept_invalid_certs(self : ClientBuilder, enabled : Bool) -> ClientBuilder

    ClientBuilder::default_header

    fn ClientBuilder::default_header(self : ClientBuilder, key : String, value : String) -> ClientBuilder

    ClientBuilder::default_headers

    fn ClientBuilder::default_headers(self : ClientBuilder, headers : Map[String, String]) -> ClientBuilder

    ClientBuilder::http1_only

    fn ClientBuilder::http1_only(self : ClientBuilder) -> ClientBuilder

    ClientBuilder::http2_prior_knowledge

    fn ClientBuilder::http2_prior_knowledge(self : ClientBuilder) -> ClientBuilder

    ClientBuilder::http3_prior_knowledge

    fn ClientBuilder::http3_prior_knowledge(self : ClientBuilder) -> ClientBuilder

    ClientBuilder::proxy

    fn ClientBuilder::proxy(self : ClientBuilder, proxy : Client) -> ClientBuilder

    ClientBuilder::timeout

    fn ClientBuilder::timeout(self : ClientBuilder, millis : Int) -> ClientBuilder

    pub struct Cookie {
    name : String
    value : String
    path : String?
    expires_raw : String?
    max_age : Int64?
    domain : String?
    secure : Bool
    http_only : Bool
    extensions : Array[String]
    } derive(ToJson,
    Debug
    )

    Cookie::Cookie

    fn Cookie::Cookie(name : String, value : String, path? : String, expires_raw? : String, max_age? : Int64, domain? : String, secure? : Bool, http_only? : Bool, extensions? : Array[String]) -> Cookie

    Cookie::new

    fn Cookie::new(name : String, value : String, path? : String, expires_raw? : String, max_age? : Int64, domain? : String, secure? : Bool, http_only? : Bool, extensions? : Array[String]) -> Cookie

    HttpVersion

    pub(all) enum HttpVersion {
    Http1
    Http2
    Http3
    } derive(Compare, Eq, Hash, ToJson,
    Debug
    )

    Protocol

    pub(all) enum Protocol {
    Http
    Https
    } derive(Compare, Eq, Hash, ToJson,
    Debug
    )

    impl Show for Protocol

    Protocol::default_port

    fn Protocol::default_port(p : Protocol) -> Int

    RequestBuilder

    pub struct RequestBuilder {
    client : RequestClient
    meth : RequestMethod
    uri : String
    headers : Map[String, String]
    body : &
    Data

    }

    RequestBuilder::body

    RequestBuilder::header

    fn RequestBuilder::header(self : RequestBuilder, key : String, value : String) -> RequestBuilder

    RequestBuilder::headers

    fn RequestBuilder::headers(self : RequestBuilder, headers : Map[String, String]) -> RequestBuilder

    RequestBuilder::json

    fn RequestBuilder::json(self : RequestBuilder, value : Json) -> RequestBuilder

    RequestBuilder::send

    async fn RequestBuilder::send(self : RequestBuilder) -> ResponseBody

    RequestClient

    pub struct RequestClient {
    headers : Map[String, String]
    proxy : Client?
    timeout : Int?
    version : HttpVersion
    verify : Bool
    }

    RequestClient::RequestClient

    #alias(new)
    fn RequestClient::RequestClient() -> RequestClient

    RequestClient::builder

    fn RequestClient::builder() -> ClientBuilder

    RequestClient::delete

    fn RequestClient::delete(self : RequestClient, uri : String) -> RequestBuilder

    RequestClient::get

    fn RequestClient::get(self : RequestClient, uri : String) -> RequestBuilder

    RequestClient::head

    fn RequestClient::head(self : RequestClient, uri : String) -> RequestBuilder

    RequestClient::options

    fn RequestClient::options(self : RequestClient, uri : String) -> RequestBuilder

    RequestClient::patch

    fn RequestClient::patch(self : RequestClient, uri : String) -> RequestBuilder

    RequestClient::post

    fn RequestClient::post(self : RequestClient, uri : String) -> RequestBuilder

    RequestClient::put

    fn RequestClient::put(self : RequestClient, uri : String) -> RequestBuilder

    RequestClient::request

    fn RequestClient::request(self : RequestClient, meth : RequestMethod, uri : String) -> RequestBuilder

    RequestMethod

    pub(all) enum RequestMethod {
    Get
    Head
    Post
    Put
    Delete
    Connect
    Options
    Trace
    Patch
    } derive(Compare, Eq, Hash, ToJson,
    Debug
    )

    Response

    pub(all) struct Response {
    code : Int
    reason : String
    headers : Map[String, String]
    cookies : Array[Cookie]
    } derive(ToJson,
    Debug
    )

    impl Show for Response

    ResponseBody

    pub(all) struct ResponseBody {
    response : Response
    body : &
    Data

    }

    ResponseBody::binary

    fn ResponseBody::binary(self : ResponseBody) -> Bytes

    ResponseBody::json

    fn ResponseBody::json(self : ResponseBody) -> Json raise

    ResponseBody::text

    fn ResponseBody::text(self : ResponseBody) -> String raise

    delete

    async fn delete(uri : String, headers? : Map[String, String], body? : &
    Data
    , proxy? : Client) -> ResponseBody

    Similar to get, but performs a DELETE request instead.

    get

    async fn get(uri : String, headers? : Map[String, String], body? : &
    Data
    , proxy? : Client) -> ResponseBody

    Perform a HTTP GET request to uri. Supported protocols are http:// and https://. The HTTP response message and the whole response body will be returned.

    proxy, if present, specifies the proxy to use for this request. See @http.Client::new for more details. proxy is not supported on JavaScript backend.

    See Client::request for more details.

    get_stream

    async fn get_stream(uri : String, headers? : Map[String, String], body? : &
    Data
    , proxy? : Client) -> (Response, Client)

    Similar to @http.get, but allow reading response body streamingly. A pair (response, client) will be returned, where response is the response header from the server, and client is the HTTP client that performs the request. client can be used to read the content of response body via @io.Reader, see @http.Client for more details.

    Note that the returned client must be manually closed via .close() to close the underlying connection used for the request.
    async fn head(uri : String, headers? : Map[String, String], proxy? : Client) -> ResponseBody

    Similar to get, but performs a HEAD request instead.

    options

    async fn options(uri : String, headers? : Map[String, String], body? : &
    Data
    , proxy? : Client) -> ResponseBody

    Similar to get, but performs an OPTIONS request instead.

    patch

    async fn patch(uri : String, content : &
    Data
    , headers? : Map[String, String], proxy? : Client) -> ResponseBody

    Similar to get, but performs a PATCH request instead.

    post

    async fn post(uri : String, content : &
    Data
    , headers? : Map[String, String], proxy? : Client) -> ResponseBody

    Similar to get, but performs a POST request instead.

    post_stream

    async fn post_stream(uri : String, headers? : Map[String, String], proxy? : Client) -> Client

    Similar to @http.post, but allow writing request body streamingly. The return value client is the HTTP client that performs the request, it can be used to write the content of request body via @io.Writer. Notice that writing to @http.Client is buffered, so if you need to send data to the server immediately, .flush() must be called. After writing all the content, .end_request() must be called to complete the request and obtain response from the server. After that, the response body from the server can be obtained by using client as a @io.Reader. See @http.Client for more details.

    Note that the returned client must be manually closed via .close() to close the underlying connection used for the request.

    put

    async fn put(uri : String, content : &
    Data
    , headers? : Map[String, String], proxy? : Client) -> ResponseBody

    Similar to get, but performs a PUT request instead.

    put_stream

    async fn put_stream(uri : String, headers? : Map[String, String], proxy? : Client) -> Client

    Similar to @http.put, but allow writing request body streamingly. The return value client is the HTTP client that performs the request, it can be used to write the content of request body via @io.Writer. Notice that writing to @http.Client is buffered, so if you need to send data to the server immediately, .flush() must be called. After writing all the content, .end_request() must be called to complete the request and obtain response from the server. After that, the response body from the server can be obtained by using client as a @io.Reader. See @http.Client for more details.

    Note that the returned client must be manually closed via .close() to close the underlying connection used for the request.