Sign in

    moonfetch

    moonfetch — an HTTP client for MoonBit (← httpx, reqwest): one call for a request, a Client that keeps connections, HTTP/1.1 and HTTP/2 chosen by ALPN, redirects, timeouts and JSON.

    http
    client
    fetch
    async
    requests
    moonbit
    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    2 hours ago
    Downloads
    2

    #moonfetch

    An HTTP client for MoonBit, after httpx and reqwest: one call for a request, a Client that keeps its connections, HTTP/1.1 and HTTP/2 chosen by ALPN.

    // One request.
    let r = @moonfetch.get("https://example.com/")
    r.status // 200
    r.version // "HTTP/2"
    r.text()

    // A client: connections kept per origin, defaults for every request.
    let api = @moonfetch.Client::new(
    base_url="https://api.example.com/v1",
    headers=[("authorization", "Bearer \{token}")],
    )
    defer api.close()
    let user = api.get("/users/7").checked().json()
    let made = api.post("/users", body=Json({ "name": "moon" }))
    let form = api.post("/login", body=Form([("user", "moon"), ("pass", secret)]))

    Run moon run src/examples/tour --target native to fetch a page over HTTP/2.

    #What it does

    VersionsHTTP/1.1 and HTTP/2 by ALPN over TLS 1.3 (moontls), HTTP/1.1 in the clear
    Connectionsidle HTTP/1.1 connections kept per origin; one HTTP/2 connection per origin carries every request to it
    BodiesBody is one value: Text, Bytes, Json, Form; the content type follows unless a header names one
    Redirectsup to 10 followed; a 303, and a 301 or 302 to a POST, become a GET; authorization and cookie stay with their origin
    Timeouts5 seconds for the whole exchange, redirects included; timeout=None on the client waits for ever
    Compressiongzip and deflate asked for and decoded; a request with its own accept-encoding gets the body as sent
    Retriesa kept connection the server closed while idle, or an HTTP/2 connection it went away from, is replaced and an idempotent request sent again; a stream the server refused is sent again whatever the method
    Certificateschecked against the Mozilla roots by mooncred; there is no switch that turns checking off: trust your own CA by giving tls roots that hold it
    ErrorsRefused for what moonfetch refuses (Status, Redirects, Reset, Timeout, Url); a socket, TLS or framing error passes through as it is

    #Settings

    Client::new takes the defaults; each request may replace timeout and redirects, and its headers replace the client's of the same name.

    SettingDefaultFrom
    timeout5000 mshttpx
    redirects10reqwest, requests
    idle10 connections per originurllib3
    http2onreqwest, curl
    compressiononhttpx, reqwest
    HTTP/2 receive window4 MiB per stream, 1 GiB per connectionGo

    #Beside the official client

    moonbitlang/async/http has a client too. What differs:

    async/httpmoonfetch
    HTTP/2noby ALPN
    TLSthe system's (OpenSSL, SChannel)moontls, the same on every platform
    Connection reuseone connection per Clienta pool per origin
    Redirects, compressionnoyes
    Turning certificate checks offtrust=NoVerificationnot possible; give the roots instead

    The sockets are moonbitlang/async's, so moonfetch runs on the native and wasm backends. A browser host, streamed responses, WebSocket and HTTP/3 come next.

    #Install

    moon add moonbitstack/moonfetch

    #Licence

    Apache-2.0. See LICENSE.

    Refused

    pub(all) suberror Refused {
    Status(status~ : Int, url~ : String)
    Redirects(limit~ : Int)
    Reset(code~ : Int)
    Timeout(limit~ : Int)
    Url(String)
    } derive(Eq,
    Debug
    )

    What moonfetch itself refuses. Errors from below pass through as they are: a socket that will not connect, a TLS alert, a response that does not parse.
    impl Show for Refused

    Refused::equal

    fn Refused::equal(Refused, Refused) -> Bool

    Refused::not_equal

    fn Refused::not_equal(x : Refused, y : Refused) -> Bool

    Refused::output

    fn Refused::output(self : Refused, logger : &Logger) -> Unit

    Refused::to_repr

    Refused::to_string

    fn Refused::to_string(self : Refused) -> String

    Body

    pub(all) enum Body {
    Empty
    Text(String)
    Bytes(Bytes)
    Json(Json)
    Form(Array[(String, String)])
    } derive(Eq,
    Debug
    )

    A request body. One value, so a request cannot carry two bodies for the library to choose between.

    Body::equal

    fn Body::equal(Body, Body) -> Bool

    Body::not_equal

    fn Body::not_equal(x : Body, y : Body) -> Bool

    Body::to_repr

    Client

    type Client

    An HTTP client that keeps its connections: idle HTTP/1.1 ones per origin, and one HTTP/2 connection per origin for every request to it. Close it when done with it.

    Client::close

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

    Close every connection the client keeps.

    Client::delete

    async fn Client::delete(self : Client, url : StringView, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    Client::get

    async fn Client::get(self : Client, url : StringView, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    Client::head

    async fn Client::head(self : Client, url : StringView, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    Client::new

    fn Client::new(base_url? : String, headers? : Array[(String, String)], timeout? : Int?, redirects? : Int, http2? : Bool, compression? : Bool, idle? : Int, tls? :
    Client
    ) -> Client raise Refused

    A client.

    base_url is the prefix relative URLs are joined to, as httpx joins them: its path is kept, so /users under https://a.example/api is https://a.example/api/users. headers go on every request. timeout bounds each request, redirects included; None waits for ever. redirects caps how many are followed; 0 follows none. http2 offers HTTP/2 by ALPN. compression asks for gzip or deflate and decodes what comes back, as httpx and reqwest do; a request that names its own accept-encoding gets the body as sent. tls judges servers' certificates, the Mozilla roots when not given; its ALPN list is set by http2.

    Client::options

    async fn Client::options(self : Client, url : StringView, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    Client::patch

    async fn Client::patch(self : Client, url : StringView, body? : Body, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    Client::post

    async fn Client::post(self : Client, url : StringView, body? : Body, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    Client::put

    async fn Client::put(self : Client, url : StringView, body? : Body, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    Client::request

    async fn Client::request(self : Client, verb :
    Method
    , url : StringView, query? : Array[(String, String)], headers? : Array[(String, String)], body? : Body, timeout? : Int, redirects? : Int) -> Response

    Send a request and read the response whole.

    query is added to the URL's own query; headers replace the client's of the same name; timeout and redirects replace the client's for this request.

    Response

    pub(all) struct Response {
    status : Int
    reason : String
    version : String
    headers : Array[(String, String)]
    url : String
    body : Bytes
    } derive(Eq,
    Debug
    )

    A response, read whole. Header names are lower case, as HTTP/2 writes them, and keep the order they arrived in.

    Response::checked

    fn Response::checked(self : Response) -> Response raise Refused

    The response itself, or Status for a 4xx or 5xx, as reqwest's error_for_status does.

    Response::equal

    fn Response::equal(Response, Response) -> Bool

    Response::header

    fn Response::header(self : Response, name : StringView) -> String?

    The first value of the header name, matched without regard to case.

    Response::is_success

    fn Response::is_success(self : Response) -> Bool

    Whether the status is 2xx.

    Response::json

    The body parsed as JSON.

    Response::not_equal

    fn Response::not_equal(x : Response, y : Response) -> Bool

    Response::text

    fn Response::text(self : Response) -> String

    The body as UTF-8 text; a malformed sequence reads as U+FFFD.

    Response::to_repr

    Response::values

    fn Response::values(self : Response, name : StringView) -> Array[String]

    Every value of the header name, in order: what set-cookie needs.

    delete

    async fn delete(url : StringView, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    get

    async fn get(url : StringView, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    async fn head(url : StringView, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    idle

    let idle : Int

    How many idle HTTP/1.1 connections are kept per origin: urllib3's 10.

    options

    async fn options(url : StringView, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    patch

    async fn patch(url : StringView, body? : Body, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    post

    async fn post(url : StringView, body? : Body, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    put

    async fn put(url : StringView, body? : Body, query? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, redirects? : Int) -> Response

    redirects

    let redirects : Int

    How many redirects are followed when nothing else is said: reqwest's and requests' 10.

    request

    async fn request(verb :
    Method
    , url : StringView, query? : Array[(String, String)], headers? : Array[(String, String)], body? : Body, timeout? : Int, redirects? : Int) -> Response

    One request on a client of its own, closed afterwards. A program that makes more than a few requests keeps a Client instead.

    timeout

    let timeout : Int

    How long a request may take when nothing else is said, in milliseconds: httpx's 5 seconds.