Sign in

    This version of the module is deprecated: Renamed to gaato/requesto

    req

    A human-facing async HTTP client for MoonBit: verb functions, a client with defaults, body encoders, responses as values, and a step chain around a swappable transport.

    http
    client
    requests
    async
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    20 hours ago
    Downloads
    1

    #gaato/req

    An async HTTP client for MoonBit: verb functions, a client with defaults, body encoders, and responses as values. Native, JS and moonrun Wasm.

    moon add gaato/req

    ///|
    struct User {
    id : Int
    name : String
    } derive(FromJson)

    ///|
    async fn example() -> Unit raise {
    let client = @req.Client::new(
    base_url="https://api.example.com/v1",
    headers=[("Accept", "application/json")],
    auth=Bearer("token"),
    )
    defer client.close()
    let user : User = client
    .get("users/42", params=[("expand", "teams")])
    .raise_for_status()
    .json_as()
    client.post("users", json={ "name": user.name }) |> ignore
    println(@req.get("https://example.com/").text())
    }

    #What it does

    • get, post, put, patch, delete, head, options and request, on a Client or as top-level functions.
    • Client defaults: base_url (always appended to), headers, params (merged into any query already in the URL), auth (Basic, Bearer, Digest), timeout, retry.
    • Retries by default: idempotent methods on 408, 429, 500, 502, 503, 504 and transport failures, any method when nothing was sent; Retry-After up to 60 s, else backoff with jitter. retry=Retry::none() turns them off.
    • One body per call: content (bytes), text, data (form), files (multipart, @req.Part), json or body_stream.
    • A response is a value whatever its status; raise_for_status() turns non-2xx into ReqError::Status.
    • Redirects followed (at most 10, credentials dropped across origins, no https to http), with history; follow_redirects=false per client or call.
    • Cookies kept per client in an RFC 6265 jar, client.cookies(); cookies=Jar(jar) shares one, cookies=Off keeps none.
    • Streaming both ways: stream hands over the response while its body is still arriving (read_some, each_chunk, each_line); body_stream and Part::file_stream send a body as it is read.
    • Server-sent events: client.sse("GET", url, event => ...), or each_event on a streamed response.
    • URLs checked and normalised before sending; @req.Url takes response URLs apart and resolves links.
    • Timeouts per call and per phase (connect_timeout, write_timeout, read_timeout, pool_timeout), proxy (or HTTP_PROXY, HTTPS_PROXY, NO_PROXY), and connection limits (max_connections, max_connections_per_origin).
    • Bounded by default: 30 s per exchange, 64 MiB per buffered body, 16 MiB per event or line.
    • steps, links of the chain around the transport, with on_request and on_response hooks.
    • Tests swap the transport for MockTransport::new(request => response) and the clock for VirtualClock from gaato/http-async/mock.

    Not yet: HTTP/2, response caching. On JS, fetch follows redirects before the client sees them, and in browsers handles cookies itself.

    #Coming from

    requests / httpxaxios / kyReqgaato/req
    Callhttpx.get(url, params=...)ky.get(url, {searchParams})Req.get!(url, params: ...)@req.get(url, params=[...])
    Clienthttpx.Client(base_url=...)axios.create({baseURL})Req.new(base_url: ...)@req.Client::new(base_url=...)
    JSON bodyjson=...json: ... (ky)json: ...json=...
    Non-2xxvalue, raise_for_status()throwsvaluevalue, raise_for_status()
    Read JSON.json().json() / data.body.json(), .json_as()
    Redirectsfollow_redirects=, .historymaxRedirects / redirectredirect:, max_redirects:follow_redirects=, max_redirects=, .history
    Cookiesclient.cookiesthe browser's—client.cookies(), cookies=Jar(jar)
    Streamingclient.stream(...), iter_lines()responseType: 'stream'into: funclient.stream(...), each_line
    SSEhttpx_sse.connect_sseEventSource—client.sse(...), each_event
    Timeoutshttpx.Timeout(connect=...)timeoutreceive_timeout:timeout=, connect_timeout=, ...
    Proxyproxy=proxy (axios)connect_options:proxy=, trust_env
    Hooksevent_hooksinterceptors / hooksstepssteps=[on_request(...), ...]
    RetriesHTTPAdapter(max_retries=Retry(...)) (off)retry (ky, on)retry: :safe_transient (on)retry=Retry::new(...) (on)
    DigestHTTPDigestAuth / DigestAuth——auth=Digest(username=u, password=p)
    Mockhttpx.MockTransport—Req.Test@req.MockTransport

    Unofficial and experimental. Apache-2.0.

    using @gaato/http/cookie { type Cookie }

    A stored cookie: @cookie.Cookie.

    CookieJar

    An RFC 6265 cookie store: @cookie.CookieJar of gaato/http.
    type Next = async (
    Request
    ) -> Response raise ReqError

    The rest of the chain after a step: the later steps and the transport.

    Part

    A multipart part: Part::text(name, value), Part::file(name, filename, content_type, bytes) or Part::json(name, value).

    Phase

    using @gaato/http { type Phase }

    The phase of an exchange a transport failure happened in: @http.Phase.

    Request

    using @gaato/http { type Request }

    A request as the transport receives it: @http.Request.

    SameSite

    A cookie's SameSite attribute: @cookie.SameSite.

    SseEvent

    using @gaato/http/sse { type SseEvent }

    One server-sent event: @sse.SseEvent of gaato/http, with its event type ("message" unless the stream names one), its data lines joined with \n, the last id the stream set, and the retry delay it asked for, in milliseconds and capped at one hour.

    Step

    A link of the chain around the transport. It may change the request, call next (or not, or more than once) and change or replace the result. A client's steps run in array order, the first outermost, the same shape as @http.Middleware.

    Url

    using @gaato/http { type Url }

    An RFC 3986 URI reference: @http.Url, for taking apart, resolving or comparing the URLs of responses.

    test {
    let page = @req.Url::parse("https://api.example.com/v1/items?page=1")
    let next = page.resolve(@req.Url::parse("items?page=2"))
    assert_eq(next.to_string(), "https://api.example.com/v1/items?page=2")
    assert_eq(next.query_params(), [("page", "2")])
    assert_true(page.same_origin(next))
    }

    ReqError

    pub(all) suberror ReqError {
    Timeout(request~ :
    Request
    , phase~ :
    Phase
    , timeout~ : Int)
    Network(request~ :
    Request
    , phase~ :
    Phase
    , message~ : String)
    Protocol(request~ :
    Request
    , message~ : String)
    Proxy(request~ :
    Request
    , status~ : Int, message~ : String)
    TooManyRedirects(request~ :
    Request
    , max~ : Int)
    InvalidRedirect(request~ :
    Request
    , location~ : String, message~ : String)
    ResponseNotRead(request~ :
    Request
    )
    StreamConsumed(request~ :
    Request
    )
    UnsupportedProtocol(url~ : String)
    InvalidURL(url~ : String, message~ : String)
    InvalidRequest(message~ : String)
    Decoding(message~ : String)
    Status(request~ :
    Request
    , response~ : Response)
    }

    Why a call failed. Transport failures carry the request being sent; Status carries the response that raise_for_status refused.
    impl Show for ReqError

    ReqError::is_timeout

    fn ReqError::is_timeout(self : ReqError) -> Bool

    Whether a time limit ran out.

    ReqError::is_transport_error

    fn ReqError::is_transport_error(self : ReqError) -> Bool

    Whether sending failed below HTTP: Timeout, Network, Protocol, Proxy or UnsupportedProtocol.

    ReqError::is_unsent

    fn ReqError::is_unsent(self : ReqError) -> Bool

    Whether the request certainly never reached the server, so sending it again cannot repeat its effect: the transport failed while waiting for a connection or making one, or the proxy refused the tunnel. A request refused before the transport, such as InvalidURL, is not counted: it would fail the same way again.

    ReqError::output

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

    The error's one-line message.

    ReqError::to_repr

    Debug output names the request by method and URL and the response by status, so headers such as Authorization never appear in it, and credentials in a URL are redacted.

    ReqError::to_string

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

    The error's one-line message.

    Auth

    pub(all) enum Auth {
    Basic(username~ : String, password~ : String)
    Bearer(String)
    Digest(username~ : String, password~ : String)
    }

    Credentials sent in the Authorization header. A call that sets that header itself keeps its own.

    Auth::to_repr

    Debug output redacts the password and the token.

    Client

    pub struct Client {
    // private fields
    }

    Defaults shared by the calls made through it, and the transport and clock they use. Call close when done, to drop the default transport's idle connections.

    Client::close

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

    Drops the default transport's idle connections. A transport passed to Client::new is left to its owner.

    Client::cookies

    The client's cookie jar, or None with cookies=Off.

    test {
    let client = @req.Client::new()
    assert_eq(client.cookies().map(jar => jar.length()), Some(0))
    client.close()
    }

    Client::delete

    async fn Client::delete(self : Client, url : String, params? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a DELETE request: request("DELETE", url, ...).

    Client::get

    async fn Client::get(self : Client, url : String, params? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a GET request: request("GET", url, ...).

    Client::head

    async fn Client::head(self : Client, url : String, params? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a HEAD request: request("HEAD", url, ...).

    Client::new

    fn Client::new(base_url? : String, headers? : Array[(String, String)], params? : Array[(String, String)], auth? : Auth, timeout? : Int, max_body_bytes? : Int?, steps? : Array[async (
    Request
    , async (
    Request
    ) -> Response raise ReqError) -> Response raise ReqError], follow_redirects? : Bool, max_redirects? : Int, cookies? : Cookies, retry? : Retry, transport? : &
    Transport
    , clock? : &
    Clock
    , random? : () -> Double, connect_timeout? : Int, write_timeout? : Int, read_timeout? : Int, pool_timeout? : Int, max_connections? : Int, max_connections_per_origin? : Int, max_idle_per_origin? : Int, proxy? : String, trust_env? : Bool) -> Client

    Creates a client.

    • base_url: prefixed to relative URLs; a call's URL is appended to it with one /, so "/users" and "users" both land under its path.
    • headers: sent with every call, after User-Agent: gaato-req/<version>, which a User-Agent here replaces.
    • params: query parameters added to every call.
    • auth: sets Authorization on calls that do not set it themselves.
    • timeout: milliseconds one exchange may take, 30 s by default: up to the whole response, or for a streamed one up to its head and then each read of its body.
    • max_body_bytes: the largest body buffered, 64 MiB by default, counted after decoding; None lifts it. The default transport applies it to each call; Response::read applies it to a streamed body.
    • steps: links of the chain around the transport, the first outermost; they see the call's first request and its final response, before redirects, retries and auth.
    • retry: when a call is sent again, Retry::new() by default: up to 3 retries of idempotent methods on 408, 429, 500, 502, 503, 504 and transport failures, of any method when nothing was sent; Retry::none() turns it off.
    • follow_redirects: follows 301, 302, 303, 307 and 308 responses, at most max_redirects (10) in a row, as gaato/http's follow_redirects does: 303, and 301 or 302 after POST, turn into GET without the body; Authorization, Cookie and Proxy-Authorization are dropped when the origin changes; https to http is refused. On by default; each call can turn it off or on. On JS, fetch follows redirects before the client sees them.
    • cookies: by default the client keeps cookies in a jar of its own on its clock (Client::cookies), at most 50 per domain and 3000 in all, applied on every redirect hop. Jar(jar) uses jar instead, Off keeps none.
    • transport: replaces the default AsyncTransport of gaato/http-async.
    • clock: measures timeout and elapsed, and waits between retries.
    • random: values in [0, 1) for retry jitter, Digest client nonces and multipart boundaries; host randomness by default.

    These configure the default transport and are ignored with transport:

    • connect_timeout, write_timeout, read_timeout, pool_timeout: milliseconds for making a connection, sending the request, waiting for each read of the response, and waiting for a free connection. Unset, only timeout applies.
    • max_connections, max_connections_per_origin: open connections in all and to one origin; a call beyond them waits for one to be free. Unset, gaato/http-async's defaults apply.
    • max_idle_per_origin: connections kept open between calls, per origin.
    • proxy: an http:// or https:// proxy URL, credentials optional, for every call. Without it, trust_env (on by default) reads HTTP_PROXY, HTTPS_PROXY, ALL_PROXY and NO_PROXY.

    test {
    let client = @req.Client::new(
    base_url="https://api.example.com/v1",
    headers=[("Accept", "application/json")],
    auth=Bearer("token"),
    )
    client.close()
    }

    Client::options

    async fn Client::options(self : Client, url : String, params? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a OPTIONS request: request("OPTIONS", url, ...).

    Client::patch

    async fn Client::patch(self : Client, url : String, params? : Array[(String, String)], headers? : Array[(String, String)], content? : Bytes, text? : String, data? : Array[(String, String)], files? : Array[
    Part
    ], json? : Json, body_stream? : &
    BodyStream
    , body_length? : Int64, timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a PATCH request: request("PATCH", url, ...).

    Client::post

    async fn Client::post(self : Client, url : String, params? : Array[(String, String)], headers? : Array[(String, String)], content? : Bytes, text? : String, data? : Array[(String, String)], files? : Array[
    Part
    ], json? : Json, body_stream? : &
    BodyStream
    , body_length? : Int64, timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a POST request: request("POST", url, ...).

    Client::put

    async fn Client::put(self : Client, url : String, params? : Array[(String, String)], headers? : Array[(String, String)], content? : Bytes, text? : String, data? : Array[(String, String)], files? : Array[
    Part
    ], json? : Json, body_stream? : &
    BodyStream
    , body_length? : Int64, timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a PUT request: request("PUT", url, ...).

    Client::request

    async fn Client::request(self : Client, http_method : String, url : String, params? : Array[(String, String)], headers? : Array[(String, String)], content? : Bytes, text? : String, data? : Array[(String, String)], files? : Array[
    Part
    ], json? : Json, body_stream? : &
    BodyStream
    , body_length? : Int64, timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a request and returns its response, whatever its status.

    • url is joined to base_url, then parsed and normalised as Url does; one that is not an absolute http or https URL is refused before anything is sent. Credentials in it (user:pass@) are taken out of the URL and sent as Basic Authorization, in place of the client's auth, unless the call sets Authorization itself.
    • params are added to the URL's query, after the client's; a name given here replaces the client's parameters of that name.
    • headers replace the client's headers of the same names.
    • At most one body: content (bytes, no Content-Type), text (text/plain; charset=utf-8), data (a URL-encoded form), files (multipart/form-data), json (compact application/json) or body_stream (sent as it is read, with Content-Length when body_length gives it and chunked otherwise). A Content-Type in the headers wins over the implied one. A body that streams, including files made with Part::file_stream, is sent at most once: retries and redirects that would send it again stop instead.
    • timeout and retry replace the client's for this call.
    • follow_redirects replaces the client's for this call. The response carries the redirects followed in history and the final URL in url.

    Client::sse

    async fn Client::sse(self : Client, http_method : String, url : String, params? : Array[(String, String)], headers? : Array[(String, String)], content? : Bytes, text? : String, data? : Array[(String, String)], files? : Array[
    Part
    ], json? : Json, body_stream? : &
    BodyStream
    , body_length? : Int64, timeout? : Int, max_event_bytes? : Int, f : async (
    SseEvent
    ) -> Unit) -> Unit

    Sends a request for a server-sent event stream and calls f with each event, until the stream ends. The arguments are those of Client::stream; Accept: text/event-stream and Cache-Control:no-store are sent unless headers set them, so that no cache in between holds the stream back.

    A status outside 200 through 299 raises Status, with as much of the body as max_body_bytes lets it read; 204 No Content, a server's way to say there are no events, returns at once. Otherwise the body is read as Response::each_event reads it.

    There is no reconnection: when the stream ends, so does the call. To resume, call again with a Last-Event-ID header holding the last event's id, after waiting its retry. For the response itself, its headers or stopping early, use stream and each_event.

    async test {
    let client = @req.Client::new(
    transport=@req.MockTransport::new(_ => {
    status: 200,
    headers: @http.Headers::from_array([("Content-Type", "text/event-stream")]),
    body: b"id: 7\ndata: hello\n\n",
    }),
    )
    let seen = []
    client.sse("GET", "https://example.com/events", event => seen.push(event))
    assert_eq(seen.map(event => (event.data, event.id)), [("hello", Some("7"))])
    }

    Client::stream

    async fn[T] Client::stream(self : Client, http_method : String, url : String, params? : Array[(String, String)], headers? : Array[(String, String)], content? : Bytes, text? : String, data? : Array[(String, String)], files? : Array[
    Part
    ], json? : Json, body_stream? : &
    BodyStream
    , body_length? : Int64, timeout? : Int, follow_redirects? : Bool, retry? : Retry, f : async (Response) -> T) -> T

    Sends a request like request and calls f with the response while its body is still on the connection, then closes it, whether f read it or not. Steps see the response before f; every response the call opened, including ones a step dropped or raised over, is closed when it ends. The redirect responses in history are closed unread before the next request goes out: they keep their status, headers and URL, not their bodies.

    async test {
    let client = @req.Client::new(
    transport=@req.MockTransport::new(_ => {
    status: 200,
    headers: @http.Headers::new(),
    body: b"line one",
    }),
    )
    let size = client.stream("GET", "https://example.com/export", response => {
    let mut size = 0
    response.each_chunk(chunk => size += chunk.length())
    size
    })
    assert_eq(size, 8)
    }

    Cookies

    pub(all) enum Cookies {
    Jar(
    CookieJar
    )
    Off
    }

    Where a client keeps cookies instead of a jar of its own.

    test {
    let clock = @simclock.VirtualClock()
    let jar = @req.CookieJar::new(clock)
    let first = @req.Client::new(clock~, cookies=Jar(jar))
    let second = @req.Client::new(clock~, cookies=Jar(jar))
    let shared = second.cookies().unwrap()
    assert_true(physical_equal(shared, jar))
    let none = @req.Client::new(cookies=Off)
    assert_true(none.cookies() is None)
    [first, second, none].each(client => client.close())
    }

    MockTransport

    pub struct MockTransport {
    // private fields
    }

    A transport that answers every request by calling a function, for tests: the function can check the request, record it, wait on a clock, return any response or raise any transport failure.

    async test {
    let client = @req.Client::new(
    transport=@req.MockTransport::new(request => {
    status: 200,
    headers: @http.Headers::new(),
    body: b"hi \{request.url}",
    }),
    )
    let response = client.get("https://example.com/")
    assert_eq(response.text(), "hi https://example.com/")
    }

    MockTransport::new

    Creates a transport that answers with handler.

    Response

    pub struct Response {
    status : Int
    headers :
    Headers

    url : String
    request :
    Request

    history : Array[Response]
    elapsed : Int
    // private fields
    }

    A received response. Every status is an ordinary response; call raise_for_status to turn the ones outside 200 through 299 into an error.

    A response from Client::stream holds its body unread: read buffers it, read_some and each_chunk take it chunk by chunk, each_line and each_event line by line and event by event, and close drops the rest. Any other call's response arrives read.

    Response::close

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

    Drops the rest of a streamed body and releases its connection, and ends any each_* loop it is called from. A body in memory, or one read to its end, stays readable.

    Response::content

    fn Response::content(self : Response) -> Bytes raise ReqError

    The whole body. A streamed response's body has to be read first.

    Response::each_chunk

    async fn Response::each_chunk(self : Response, f : async (Bytes) -> Unit) -> Unit

    Calls f with each chunk of the body, in order, until its end. Calling close in f stops there.

    Response::each_event

    async fn Response::each_event(self : Response, f : async (
    SseEvent
    ) -> Unit, max_event_bytes? : Int) -> Unit

    Calls f with each server-sent event of the body, in order, until its end. Calling close in f stops there.

    Raises Decoding without reading when the response's Content-Type is not text/event-stream, such as an error page. An event larger than max_event_bytes (16 MiB by default) closes the body and raises Protocol, after the events before it. Each read waits at most the call's timeout, so a stream that stays silent longer needs a longer one.

    async test {
    let client = @req.Client::new(
    transport=@req.MockTransport::new(_ => {
    status: 200,
    headers: @http.Headers::from_array([("Content-Type", "text/event-stream")]),
    body: b"data: one\n\nevent: done\ndata: two\n\n",
    }),
    )
    let seen = []
    client.stream("GET", "https://example.com/events", response => {
    response.each_event(event => seen.push("\{event.event} \{event.data}"))
    })
    assert_eq(seen, ["message one", "done two"])
    }

    Response::each_line

    async fn Response::each_line(self : Response, f : async (String) -> Unit, max_line_bytes? : Int) -> Unit

    Calls f with each line of the body, in order, until its end: lines end with \n, \r\n or \r, which f does not see, and are decoded as UTF-8 (malformed bytes become U+FFFD). A last line without a terminator counts. Calling close in f stops there.

    A line longer than max_line_bytes (16 MiB by default) closes the body and raises Protocol, after the lines before it.

    async test {
    let client = @req.Client::new(
    transport=@req.MockTransport::new(_ => {
    status: 200,
    headers: @http.Headers::new(),
    body: b"{\"n\":1}\n{\"n\":2}\n",
    }),
    )
    let lines = []
    client.stream("GET", "https://example.com/feed.ndjson", response => {
    response.each_line(line => lines.push(line))
    })
    assert_eq(lines, ["{\"n\":1}", "{\"n\":2}"])
    }

    Response::is_client_error

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

    Whether the status is in 400 through 499.

    Response::is_error

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

    Whether the status is in 400 through 599.

    Response::is_informational

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

    Whether the status is in 100 through 199.

    Response::is_redirect

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

    Whether the status is in 300 through 399.

    Response::is_server_error

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

    Whether the status is in 500 through 599.

    Response::is_success

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

    Whether the status is in 200 through 299.

    Response::json

    fn Response::json(self : Response) -> Json raise ReqError

    The body parsed as JSON.

    Response::json_as

    The body parsed as JSON and converted to T, which the call site names by its expected type: let user : User = response.json_as().

    Response::new

    fn Response::new(status : Int, request :
    Request
    , headers? :
    Headers
    , content? : Bytes, url? : String, history? : Array[Response], elapsed? : Int) -> Response

    A response for a step or a test to return, its body already read.

    test {
    let request = @http.Request::get("https://example.com/")
    let response = @req.Response::new(200, request, content=b"cached")
    assert_eq(response.text(), "cached")
    assert_eq(response.url, "https://example.com/")
    }

    Response::raise_for_status

    fn Response::raise_for_status(self : Response) -> Response raise ReqError

    The response itself when its status is in 200 through 299, otherwise a Status error carrying it, informational and redirect statuses included.

    Response::read

    async fn Response::read(self : Response) -> Bytes raise ReqError

    Reads the rest of a streamed body into memory, up to the client's max_body_bytes, and returns it; content, text and json work afterwards. A body already read is returned as it is. A body larger than the bound is closed and raises Protocol.

    Response::read_some

    async fn Response::read_some(self : Response) -> Bytes? raise ReqError

    The next chunk of the body, or None at its end. A body in memory comes as one chunk. Each read from the connection is bounded by the call's timeout. After close, raises StreamConsumed.

    Response::text

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

    The body decoded as UTF-8; malformed bytes become U+FFFD.

    Retry

    pub struct Retry {
    // private fields
    }

    When a call is sent again, and how long it waits first.

    A call is sent again, up to max_retries times, when its method is one of methods and the response's status is one of statuses or sending failed with Timeout or Network; and whatever its method when nothing reached the server (ReqError::is_unsent; a proxy refusal only with one of statuses). A body that streams is never sent twice. The wait is a Retry-After the response gives, in seconds or as an HTTP date on the client's clock, unless it asks for more than max_retry_after, in which case the response is returned as it is; otherwise it is drawn between 0 and base_delay * 2 ^ retry, capped at max_delay ("full jitter").

    test {
    let client = @req.Client::new(retry=@req.Retry::new(max_retries=5))
    client.close()
    let client = @req.Client::new(retry=@req.Retry::none())
    client.close()
    }

    Retry::new

    fn Retry::new(max_retries? : Int, statuses? : Array[Int], methods? : Array[String], max_retry_after? : Int, base_delay? : Int, max_delay? : Int) -> Retry

    A retry policy; the defaults are those of Client::new.

    • max_retries: sends after the first, 3 by default.
    • statuses: response statuses worth another try, by default 408, 429, 500, 502, 503 and 504.
    • methods: methods sent again after such a status or a Timeout or Network failure, by default GET, HEAD, OPTIONS, PUT, DELETE and TRACE.
    • max_retry_after: the longest Retry-After honoured, in milliseconds, 60 s by default; a server asking for more gets no retry.
    • base_delay, max_delay: milliseconds of the backoff, 1 s and 30 s by default.

    Retry::none

    fn Retry::none() -> Retry

    A policy that never sends a call again.

    delete

    async fn delete(url : String, params? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a DELETE request with a throw-away client, closed afterwards.

    get

    async fn get(url : String, params? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a GET request with a throw-away client, closed afterwards.
    async fn head(url : String, params? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a HEAD request with a throw-away client, closed afterwards.

    on_request

    fn on_request(hook : async (
    Request
    ) -> Unit raise ReqError) -> (async (
    Request
    , async (
    Request
    ) -> Response raise ReqError) -> Response raise ReqError)

    A step that calls hook with each request before passing it on.

    on_response

    fn on_response(hook : async (Response) -> Unit raise ReqError) -> (async (
    Request
    , async (
    Request
    ) -> Response raise ReqError) -> Response raise ReqError)

    A step that calls hook with each response before returning it.

    options

    async fn options(url : String, params? : Array[(String, String)], headers? : Array[(String, String)], timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a OPTIONS request with a throw-away client, closed afterwards.

    patch

    async fn patch(url : String, params? : Array[(String, String)], headers? : Array[(String, String)], content? : Bytes, text? : String, data? : Array[(String, String)], files? : Array[
    Part
    ], json? : Json, body_stream? : &
    BodyStream
    , body_length? : Int64, timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a PATCH request with a throw-away client, closed afterwards.

    post

    async fn post(url : String, params? : Array[(String, String)], headers? : Array[(String, String)], content? : Bytes, text? : String, data? : Array[(String, String)], files? : Array[
    Part
    ], json? : Json, body_stream? : &
    BodyStream
    , body_length? : Int64, timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a POST request with a throw-away client, closed afterwards.

    put

    async fn put(url : String, params? : Array[(String, String)], headers? : Array[(String, String)], content? : Bytes, text? : String, data? : Array[(String, String)], files? : Array[
    Part
    ], json? : Json, body_stream? : &
    BodyStream
    , body_length? : Int64, timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a PUT request with a throw-away client, closed afterwards.

    request

    async fn request(http_method : String, url : String, params? : Array[(String, String)], headers? : Array[(String, String)], content? : Bytes, text? : String, data? : Array[(String, String)], files? : Array[
    Part
    ], json? : Json, body_stream? : &
    BodyStream
    , body_length? : Int64, timeout? : Int, follow_redirects? : Bool, retry? : Retry) -> Response raise ReqError

    Sends a request with a throw-away client, closed afterwards. The arguments are those of Client::request.

    async test {
    let response = try @req.request("GET", "ftp://example.com/") catch {
    UnsupportedProtocol(..) => None
    error => raise error
    } noraise {
    response => Some(response)
    }
    assert_true(response is None)
    }

    sse

    async fn sse(http_method : String, url : String, params? : Array[(String, String)], headers? : Array[(String, String)], content? : Bytes, text? : String, data? : Array[(String, String)], files? : Array[
    Part
    ], json? : Json, body_stream? : &
    BodyStream
    , body_length? : Int64, timeout? : Int, max_event_bytes? : Int, f : async (
    SseEvent
    ) -> Unit) -> Unit

    Reads a server-sent event stream with a throw-away client, closed afterwards. The arguments are those of Client::sse.

    stream

    async fn[T] stream(http_method : String, url : String, params? : Array[(String, String)], headers? : Array[(String, String)], content? : Bytes, text? : String, data? : Array[(String, String)], files? : Array[
    Part
    ], json? : Json, body_stream? : &
    BodyStream
    , body_length? : Int64, timeout? : Int, follow_redirects? : Bool, retry? : Retry, f : async (Response) -> T) -> T

    Streams a response with a throw-away client, closed afterwards. The arguments are those of Client::stream.