bend-kit-hairpin Releases (16): 0.13.0.1 0xcebeb1685c5a25d66eb140be2a7d5270 indexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.13.0.1 0.13.0.0 0x49b8c9f7f7bde2d97c8c02d279f5382d unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.13.0.0 0.12.0.0 0x13c5100779ac0d3f227664502e7ad677 unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.12.0.0 0.11.0.0 0x4090998f71ed7b363f9b8db8f108e9b6 unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.11.0.0 0.10.0.0 0xd5b558a6096e70cd462e05105c961432 unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.10.0.0 0.9.0.0 0x359317aa687d47a340aaa7ca8075cc64 unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.9.0.0 0.8.0.0 0x5bc8e39f58adb35dfce98b6c5429f90b unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.8.0.0 0.7.0.0 0x7a4f6479e786b51d0ee5c2f915fe0a00 unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.7.0.0 0.6.0.0 0x0aad4ded7abdbd241a3265b4d79e114f unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.6.0.0 0.5.1.0 0xeaba39b9b1c1f604da8359497a708ca8 unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.5.1.0 0.5.0.0 0xfdf17458cd8c271788453219c49905ac unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.5.0.0 0.4.0.0 0xceb432c443dec61cf786e2dbdebe5202 unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.4.0.0 0.3.0.0 0x5f997a9351e7133e43ba9611e734f8a6 unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.3.0.0 0.2.1.0 0x32bd867a107b63c50365cf1c4612ace5 unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.2.1.0 0.2.0.0 0xe5b3966bf84836ae6b7c8d505cebeeb6 unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.2.0.0 0.1.0.0 0xb0c36a879a0010d8b4a7cdf045abacc0 unindexed https://jend.leeeet.dev/files.txt?path=bend-kit-hairpin@0.1.0.0 Definitions (72): Hairpin.Opts (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=Opts) # base: "" for none. headers: lowercase names. ms: the step timeout. policy: attempts, backoff, and deadline of each request. # ca: a PEM CA file to trust; "" trusts the default store. type Opts is Data: Opts{base: String, headers: Map<&2, List<&2, String>>, ms: U32, mode: Http.Mode, policy: Retry.Policy, cert: Maybe<&2, Http.Cert>, ca: String} Hairpin.Client (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=Client) # breaker: the circuit breaker of every request on this client. type Client is Type: Client{opts: Opts, pool: Http.Pool, jar: Maybe<&2, Http.Jar>, breaker: Retry.Breaker} Hairpin.Err (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=Err) # ErrHttp: the last attempt failed. ErrDenied: no attempt started, for why. type Err is Data: ErrHttp{error: Http.Err} ErrDenied{why: Retry.Deny} Hairpin.policy (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=policy) # One attempt, the first included; backoff from 500 ms, doubled to at most 30 s; no deadline. def policy() -> Retry.Policy: Hairpin.new.with (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=new.with) # A pool of up to cap idle sockets per origin, no base, no default headers, 30 s steps, follow redirects, # no retries, and no circuit breaker. def new.with(+cap: U32) -> Client: Hairpin.new (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=new) def new() -> Client: Hairpin.base (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=base) def base(c: Client, url: String) -> Client: Hairpin.header (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=header) # A default header. It replaces an earlier default of the same name; a request header of that name replaces it. def header(c: Client, k: String, v: String) -> Client: Hairpin.timeout (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=timeout) def timeout(c: Client, ms: U32) -> Client: Hairpin.redirect (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=redirect) def redirect(c: Client, mode: Http.Mode) -> Client: Hairpin.retry (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=retry) # Up to n retries after the first attempt: n + 1 attempts in all. def retry(c: Client, n: U32) -> Client: Hairpin.deadline (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=deadline) # No attempt of a request starts ms or more after the request starts. def deadline(c: Client, ms: U32) -> Client: Hairpin.circuit (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=circuit) # A circuit breaker in front of every request, starting closed. def circuit(c: Client, circ: Retry.Circuit) -> Client: Hairpin.breaker.of (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=breaker.of) # The breaker, for inspection. def breaker.of(c: Client) -> Client & Retry.Breaker: Hairpin.cert (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=cert) # Paths to a PEM client chain and its private key, presented to every HTTPS origin. def cert(c: Client, chain: String, key: String) -> Client: Hairpin.ca (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=ca) # Path to a PEM CA file, trusted in place of the default store. Like the certificate, it follows same-origin redirects only. def ca(c: Client, path: String) -> Client: Hairpin.jar (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=jar) # Every request stores and sends cookies through jar. Its clock moves to Time.now() before each try. def jar(c: Client, jar: Http.Jar) -> Client: Hairpin.jar.of (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=jar.of) # The jar, if the client has one. def jar.of(c: Client) -> Client & Maybe<&2, Http.Jar>: Hairpin.close (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=close) def close(c: Client) -> IO(Unit): Hairpin.stream.close (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=stream.close) # Close a stream from open.as. A body read to its end gives its connection to the client's pool. def stream.close(c: Client, st: Http.Stream) -> IO(Client): Hairpin.target.pick (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=target.pick) def target.pick(none: Bool, b: String, ref: String) -> Maybe<&2, String>: Hairpin.target (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=target) # ref against the base (RFC 3986 ยง5.2); with no base, ref as given. None when the base is not absolute. def target(+b: String, ref: String) -> Maybe<&2, String>: Hairpin.target.abs (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=target.abs) def target.abs(m: Maybe<&2, String>) -> Maybe<&2, Url.Abs>: Hairpin.merge (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=merge) # The defaults under the request headers, names lowercased; a request header replaces a default of its name. # Host, Connection, Content-Length, and Transfer-Encoding are the client's own, so both sides drop them. def merge(defaults: Map<&2, List<&2, String>>, headers: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>: Hairpin.jar.now (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=jar.now) def jar.now(j: Maybe<&2, Http.Jar>) -> IO(Maybe<&2, Http.Jar>): Hairpin.once.final (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=once.final) def once.final(+enc: String, x: Http.Pool & Result<&1, &1, Http.Err, Http.Res> & Maybe<&2, Http.Jar>) -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, Http.Res>): Hairpin.once (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=once) # One try: the redirect loop on the pool, with the jar, the certificate, and the CA file, then the body decoded. def once(+ms: U32, +mode: Http.Mode, +cert: Maybe<&2, Http.Cert>, +ca: String, +enc: String, p: Http.Pool, j: Maybe<&2, Http.Jar>, +method: String, +u: Maybe<&2, Url.Abs>, +headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, Http.Res>): Hairpin.hint.ms (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=hint.ms) def hint.ms(m: Maybe<&2, U32>) -> Maybe<&2, Nat>: Hairpin.Verdict (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=Verdict) # A judge's verdict on one attempt. Ok: a success. Caller: the request is at fault (a 4xx), so it stops # and the breaker does not count it. Target: the target failed in a way another try would repeat, so it # stops and the breaker counts it. Again: another try may help; after is the Retry-After value, "" for none. type Verdict is Data: VerdictOk{} VerdictCaller{} VerdictTarget{} VerdictAgain{after: String} Hairpin.counts (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=counts) # Ok and Caller do not count against the breaker; Target and Again do. def counts(v: Verdict) -> Bool: Hairpin.outcome (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=outcome) # Ok is Settled; Caller and Target are Failed. Again is Transient for an idempotent request, else Unsafe. def outcome(+idem: Bool, v: Verdict, now: Time.Instant) -> Retry.Outcome: Hairpin.lift (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=lift) def lift(-R: Type, r: Result<&1, &1, Http.Err, R>) -> Result<&1, &1, Err, R>: Hairpin.Step (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=Step) # The live state of one request between attempts. R: a response or a stream. last: the result of the last # attempt, None before the first. type Step<-R: Type> is Type: Step{pool: Http.Pool, jar: Maybe<&2, Http.Jar>, breaker: Retry.Breaker, budget: Retry.Budget, body: Bytes.Bytes, last: Maybe<&1, Result<&1, &1, Err, R>>, plan: Retry.Plan} Hairpin.clock (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=clock) # The clock, read only when a decision depends on it; 0 otherwise, which no such decision reads. def clock(need: Bool) -> IO(Nat): Hairpin.breaker.on (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=breaker.on) def breaker.on(br: Retry.Breaker) -> Bool: Hairpin.transient (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=transient) def transient(o: Retry.Outcome) -> Bool: Hairpin.some (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=some) def some(m: Maybe<&2, Nat>) -> Bool: Hairpin.deadline.some (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=deadline.some) def deadline.some(b: Retry.Budget) -> Bool: Hairpin.outcome.io (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=outcome.io) # The wall clock dates a Retry-After, so it is read only for Again. def outcome.io(+idem: Bool, v: Verdict) -> IO(Retry.Outcome): Hairpin.jitter.io (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=jitter.io) # Jitter for the backoff, drawn only when a retry may follow. def jitter.io(need: Bool) -> IO(Nat): Hairpin.settle.at (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=settle.at) # hit: the breaker counts this attempt as a failure. def settle.at(-R: Type, +now: Nat, +rnd: Nat, +p: Retry.Policy, +n: Nat, hit: Bool, +o: Retry.Outcome, br: Retry.Breaker, +b: Retry.Budget, body: Bytes.Bytes, pool: Http.Pool, jar: Maybe<&2, Http.Jar>, r: Result<&1, &1, Http.Err, R>) -> Step: Hairpin.settle.outcome (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=settle.outcome) # The attempt has ended: tell the breaker, and plan the next step. def settle.outcome(-R: Type, +p: Retry.Policy, +n: Nat, hit: Bool, +o: Retry.Outcome, +br: Retry.Breaker, b: Retry.Budget, body: Bytes.Bytes, pool: Http.Pool, jar: Maybe<&2, Http.Jar>, r: Result<&1, &1, Http.Err, R>) -> IO(Step): Hairpin.settle.judged (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=settle.judged) def settle.judged(-R: Type, +p: Retry.Policy, +idem: Bool, +n: Nat, br: Retry.Breaker, b: Retry.Budget, body: Bytes.Bytes, pool: Http.Pool, jar: Maybe<&2, Http.Jar>, j: Result<&1, &1, Http.Err, R> & Verdict) -> IO(Step): Hairpin.judge.status (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=judge.status) # A status that Http.retry.judge does not retry: below 400 is Ok, a 4xx is Caller, and the rest is Target. def judge.status(+s: U32) -> Verdict: Hairpin.judge.settled (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=judge.settled) def judge.settled(r: Result<&1, &1, Http.Err, Http.Res>) -> Result<&1, &1, Http.Err, Http.Res> & Verdict: Hairpin.judge.of (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=judge.of) def judge.of(x: Result<&1, &1, Http.Err, Http.Res> & Maybe<&2, String>) -> Result<&1, &1, Http.Err, Http.Res> & Verdict: Hairpin.judge (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=judge) # The default judge: Http.retry.judge, as if the request were idempotent, picks Again; outcome then applies idem. def judge(r: Result<&1, &1, Http.Err, Http.Res>) -> Result<&1, &1, Http.Err, Http.Res> & Verdict: Hairpin.settle (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=settle) def settle(~R: Type, ~judge: Result<&1, &1, Http.Err, R> -> Result<&1, &1, Http.Err, R> & Verdict, +p: Retry.Policy, +idem: Bool, +n: Nat, br: Retry.Breaker, b: Retry.Budget, body: Bytes.Bytes, x: Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, R>) -> IO(Step): Hairpin.Call (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=Call) # The inputs of one attempt. u: the target, None when it is not absolute. headers: the merged headers. type Call is Data: Call{enc: String, method: String, u: Maybe<&2, Url.Abs>, headers: Map<&2, List<&2, String>>} Hairpin.try.res (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=try.res) # One attempt that reads a whole response: the redirect loop on the pool, with the jar and the certificate. def try.res(o: Opts, call: Call, pool: Http.Pool, jar: Maybe<&2, Http.Jar>, body: Bytes.Bytes) -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, Http.Res>): Hairpin.try.stream (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=try.stream) # One attempt that opens a stream on the pool: the redirect loop with the mode, the jar, and the certificate, # then the body decoded by enc. def try.stream(o: Opts, call: Call, pool: Http.Pool, jar: Maybe<&2, Http.Jar>, body: Bytes.Bytes) -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, Http.Stream>): Hairpin.close.res (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=close.res) def close.res(r: Http.Res) -> IO(Unit): Hairpin.close.last (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=close.last) def close.last(~R: Type, ~close: R -> IO(Unit), last: Maybe<&1, Result<&1, &1, Err, R>>) -> IO(Unit): Hairpin.attempt.run (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=attempt.run) def attempt.run(~R: Type, ~judge: Result<&1, &1, Http.Err, R> -> Result<&1, &1, Http.Err, R> & Verdict, ~try: Opts -> Call -> Http.Pool -> Maybe<&2, Http.Jar> -> Bytes.Bytes -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, R>), +o: Opts, +call: Call, +idem: Bool, +n: Nat, pool: Http.Pool, jar: Maybe<&2, Http.Jar>, br: Retry.Breaker, b: Retry.Budget, r: Bytes.Bytes & Bytes.Bytes) -> IO(Step): Hairpin.denied (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=denied) def denied(-R: Type, why: Retry.Deny, last: Maybe<&1, Result<&1, &1, Err, R>>) -> Maybe<&1, Result<&1, &1, Err, R>>: Hairpin.attempt.gate (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=attempt.gate) # A refused stream closes only when the next attempt is admitted, so a denied attempt still returns it. def attempt.gate(~R: Type, ~judge: Result<&1, &1, Http.Err, R> -> Result<&1, &1, Http.Err, R> & Verdict, ~try: Opts -> Call -> Http.Pool -> Maybe<&2, Http.Jar> -> Bytes.Bytes -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, R>), ~close: R -> IO(Unit), x: Retry.Breaker & Retry.Budget & Retry.Gate, +o: Opts, +call: Call, +idem: Bool, +n: Nat, pool: Http.Pool, jar: Maybe<&2, Http.Jar>, body: Bytes.Bytes, last: Maybe<&1, Result<&1, &1, Err, R>>) -> IO(Step): Hairpin.attempt (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=attempt) # Ask the gate; when it admits, make attempt n (0 first) of this layer. With no deadline and no breaker, # admission does not read the clock. def attempt(~R: Type, ~judge: Result<&1, &1, Http.Err, R> -> Result<&1, &1, Http.Err, R> & Verdict, ~try: Opts -> Call -> Http.Pool -> Maybe<&2, Http.Jar> -> Bytes.Bytes -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, R>), ~close: R -> IO(Unit), +o: Opts, +call: Call, +idem: Bool, +n: Nat, s: Step) -> IO(Step): Hairpin.pause (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=pause) def pause(ms: Nat) -> IO(Unit): Hairpin.go (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=go) # left: attempts this layer may still make. n: attempts made so far. Each turn follows the plan of the last step. def go(~R: Type, ~judge: Result<&1, &1, Http.Err, R> -> Result<&1, &1, Http.Err, R> & Verdict, ~try: Opts -> Call -> Http.Pool -> Maybe<&2, Http.Jar> -> Bytes.Bytes -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, R>), ~close: R -> IO(Unit), left: Nat, +n: Nat, +o: Opts, +call: Call, +idem: Bool, s: Step) -> IO(Step): Hairpin.result.of (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=result.of) def result.of(-R: Type, m: Maybe<&1, Result<&1, &1, Err, R>>) -> Result<&1, &1, Err, R>: Hairpin.request.done (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=request.done) def request.done(-R: Type, +o: Opts, s: Step) -> Client & Retry.Budget & Result<&1, &1, Err, R>: Hairpin.attempts (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=attempts) def attempts(p: Retry.Policy) -> Nat: Hairpin.budget (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=budget) # The budget of one plain request: the policy's attempts, and its deadline from now. The clock is read only for a deadline. def budget(c: Client) -> IO(Client & Retry.Budget): Hairpin.run (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=run) def run(~R: Type, ~judge: Result<&1, &1, Http.Err, R> -> Result<&1, &1, Http.Err, R> & Verdict, ~try: Opts -> Call -> Http.Pool -> Maybe<&2, Http.Jar> -> Bytes.Bytes -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, R>), ~close: R -> IO(Unit), c: Client, b: Retry.Budget, +idem: Bool, enc: String, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Client & Retry.Budget & Result<&1, &1, Err, R>): Hairpin.request.as (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=request.as) # One request inside a caller's retry layer, with every choice explicit. Every attempt takes from b, the # caller's shared budget, and obeys its deadline; the client's own policy caps the attempts of this call. # The budget left comes back beside the result: pass it to the next call, so nested layers never reset or # multiply it. The client's own deadline does not apply here; b's does. # judge: the Verdict on each attempt; Hairpin.judge is the default. idem: the request is safe to repeat, so # Again is retried; else the breaker counts it and it never repeats. enc: the Accept-Encoding codings. def request.as(~judge: Result<&1, &1, Http.Err, Http.Res> -> Result<&1, &1, Http.Err, Http.Res> & Verdict, c: Client, b: Retry.Budget, +idem: Bool, +enc: String, +method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Client & Retry.Budget & Result<&1, &1, Err, Http.Res>): Hairpin.open.as (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=open.as) # A streamed request under the same budget, deadline, backoff, and breaker as request.as. The stream opens on # the client's pool. It follows the client's redirect mode, presents its certificate, and sends and stores # cookies through its jar, as request.as does. judge reads the status and headers through Http.stream.res. # A refused stream closes before the next attempt; the last one comes back open, so the caller can read its # body. Close it with stream.close to give its connection back to the pool. def open.as(~judge: Result<&1, &1, Http.Err, Http.Stream> -> Result<&1, &1, Http.Err, Http.Stream> & Verdict, c: Client, b: Retry.Budget, +idem: Bool, +enc: String, +method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Client & Retry.Budget & Result<&1, &1, Err, Http.Stream>): Hairpin.request.in (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=request.in) # request.as with judge, the idempotency of method, and every coding Http decodes. def request.in(c: Client, b: Retry.Budget, +method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Client & Retry.Budget & Result<&1, &1, Err, Http.Res>): Hairpin.request.drop (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=request.drop) def request.drop(x: Client & Retry.Budget & Result<&1, &1, Err, Http.Res>) -> Client & Result<&1, &1, Err, Http.Res>: Hairpin.request.budget (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=request.budget) def request.budget(+method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, x: Client & Retry.Budget) -> IO(Client & Retry.Budget & Result<&1, &1, Err, Http.Res>): Hairpin.request (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=request) # url resolves against the base; headers go over the defaults. Redirects follow the client's mode, at most 20. # Each request gets a fresh budget from budget(c). Before each attempt the deadline, the # budget, and the breaker must admit it. GET, HEAD, OPTIONS, TRACE, PUT, and DELETE try again after a connect # error, a timeout, or 408, 429, 500, 502, 503, or 504, as Http.retry.judge says, on the same pool. # When the attempts run out, the last result comes back; when none started, ErrDenied says why. def request(c: Client, +method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Client & Result<&1, &1, Err, Http.Res>): Hairpin.get (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=get) def get(c: Client, url: String) -> IO(Client & Result<&1, &1, Err, Http.Res>): Hairpin.post.json (https://jend.leeeet.dev/definition.txt?path=bend-kit-hairpin@0.13.0.1/hairpin.bend&name=post.json) # POST v as compact JSON, with content-type: application/json. def post.json(c: Client, url: String, v: Json.Val) -> IO(Client & Result<&1, &1, Err, Http.Res>):