mio

Best async http library for Moonbit

network
async
http
moon add oboard/mio@0.5.2
Download zip
Author
Version
0.5.2
License
Apache-2.0
Last updated
3 months ago
Downloads
458
README

#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:

{ "deps": { "oboard/mio": "0.5.0" } }

#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
}
async fn Client::Client(uri : String, headers? : Map[String, String], proxy? : Client, verify? : Bool) -> Client

impl Reader for Client
impl Writer for 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::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::new

#
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
)
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::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::new

#
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.