src/client.bend source
src/client.bend on the hub · documented module
# ezhttp/src/client: fetch a URL. URL and HTTP parsing joined to the wire effect.# Answers a structured Response (status + headers + body), not a hub-style# "0\n…" string contract.import Baseimport ./url.bend as Urlimport ./http.bend as Httpimport ./body.bend as Bodyimport ./wire.bend as Wireimport ./auth.bend as Auth# structured client result: transport/parse error, or an HTTP responsetype Response is Data: Err{why: String} Response{status: U32, headers: List<&2, Http.Header>, body: String}# transport status line: first line is a code, rest is the payloaddef wire.code(raw: String) -> Maybe<&2, U32>: U32.read(Http.first(String.split(raw, '\n')))# text after the first newlinedef wire.text.of(c: Http.Cut) -> String: match c: case Http.NoCut{}: "" case Http.Cut{_before, after}: afterdef wire.text(raw: String) -> String: wire.text.of(Http.divide(raw, "\n"))# whether the wire status is success (0)def wire.ok.of(m: Maybe<&2, U32>) -> Bool: match m: case None{}: False{} case Some{n}: U32.is_eq(n, 0)def wire.ok(raw: String) -> Bool: wire.ok.of(wire.code(raw))# a torn or successful HTTP parse as a client Responsedef from.reply(r: Http.Reply) -> Response: match r: case Http.Torn{why}: Err{why} case Http.Reply{status, headers, body}: Response{status, headers, body}# a finished exchangedef from.raw(raw: String, ok: Bool) -> Response: match ok: case False{}: Err{wire.text(raw)} case True{}: from.reply(Http.parse(wire.text(raw)))# HTTP(S) exchange with a pre-built requestdef http.talk(secure: Bool, host: String, port: U32, req: String) -> IO(Response): do IO<Response>: +raw : String <- Wire.wire.talk(secure, host, port, req) IO.pure(Response, from.raw(raw, wire.ok(raw)))# a URL that came apart into an HTTP exchangedef http.loc(method: String, l: Url.Loc, headers: List<&2, Http.Header>, body: String) -> IO(Response): match l: case Url.Bad{why}: IO.pure(Response, Err{why}) case Url.Loc{+scheme, +host, port, path}: http.talk(String.eq(scheme, "https"), host, port, Http.request(method, host, path, headers, body))# GETdef client.get(url: String, headers: List<&2, Http.Header>) -> IO(Response): http.loc("GET", Url.parse(url), headers, "")# HEAD (RFC 9110 §9.3.2)def client.head(url: String, headers: List<&2, Http.Header>) -> IO(Response): http.loc("HEAD", Url.parse(url), headers, "")# POSTdef client.post(url: String, headers: List<&2, Http.Header>, body: Body.Body) -> IO(Response): http.loc("POST", Url.parse(url), headers, Body.body.payload(body))# PUTdef client.put(url: String, headers: List<&2, Http.Header>, body: Body.Body) -> IO(Response): http.loc("PUT", Url.parse(url), headers, Body.body.payload(body))# DELETEdef client.delete(url: String, headers: List<&2, Http.Header>, body: Body.Body) -> IO(Response): http.loc("DELETE", Url.parse(url), headers, Body.body.payload(body))# OPTIONSdef client.options(url: String, headers: List<&2, Http.Header>) -> IO(Response): http.loc("OPTIONS", Url.parse(url), headers, "")# Authorization: Bearer … (auth.bend)def client.bearer(token: String) -> Http.Header: Auth.auth.bearer(token)# Authorization: Basic … (auth.bend; RFC 7617, RFC 4648 Base64)def client.basic(user: String, password: String) -> Http.Header: Auth.auth.basic(user, password)