hairpin.bend
bend-kit-hairpin@0.13.0.1 · 512 lines · 72 definitions
1
2import Base
3import bend-kit-http@0.38.1.1/http.bend as Http
4import bend-kit-time@0.3.0.0/time.bend as Time
5import bend-kit-url@0.4.1.0/url.bend as Url
6import bend-kit-json@0.7.0.0/json.bend as Json
7import bend-kit-bytes@0.3.3.0/bytes.bend as Bytes
8import bend-kit-retry@0.2.0.0/retry.bend as Retry
9
10
11
12type Opts is Data:
13 Opts{base: String, headers: Map<&2, List<&2, String>>, ms: U32, mode: Http.Mode, policy: Retry.Policy, cert: Maybe<&2, Http.Cert>, ca: String}
14
15
16type Client is Type:
17 Client{opts: Opts, pool: Http.Pool, jar: Maybe<&2, Http.Jar>, breaker: Retry.Breaker}
18
19
20type Err is Data:
21 ErrHttp{error: Http.Err}
22 ErrDenied{why: Retry.Deny}
23
24
25def policy() -> Retry.Policy:
26 Retry.Policy{1n, 500n, 30000, None{}}
27
28
29
30def new.with(+cap: U32) -> Client:
31 Client{Opts{"", Http.empty(), 30000, Http.ModeFollow{}, policy(), None{}, ""}, Http.pool.new.with(cap), None{}, Retry.BreakerOff{}}
32
33def new() -> Client:
34 new.with(8)
35
36def base(c: Client, url: String) -> Client:
37 Client{Opts{b, h, ms, mode, p, cert, ca}, pool, j, br} = c
38 Client{Opts{url, h, ms, mode, p, cert, ca}, pool, j, br}
39
40
41def header(c: Client, k: String, v: String) -> Client:
42 Client{Opts{b, h, ms, mode, p, cert, ca}, pool, j, br} = c
43 Client{Opts{b, Http.set(h, String.to_lower(k), v), ms, mode, p, cert, ca}, pool, j, br}
44
45def timeout(c: Client, ms: U32) -> Client:
46 Client{Opts{b, h, old, mode, p, cert, ca}, pool, j, br} = c
47 Client{Opts{b, h, ms, mode, p, cert, ca}, pool, j, br}
48
49def redirect(c: Client, mode: Http.Mode) -> Client:
50 Client{Opts{b, h, ms, old, p, cert, ca}, pool, j, br} = c
51 Client{Opts{b, h, ms, mode, p, cert, ca}, pool, j, br}
52
53
54def retry(c: Client, n: U32) -> Client:
55 Client{Opts{b, h, ms, mode, Retry.Policy{old, base, cap, d}, cert, ca}, pool, j, br} = c
56 Client{Opts{b, h, ms, mode, Retry.Policy{1n+U32.to_nat(n), base, cap, d}, cert, ca}, pool, j, br}
57
58
59def deadline(c: Client, ms: U32) -> Client:
60 Client{Opts{b, h, t, mode, Retry.Policy{a, base, cap, old}, cert, ca}, pool, j, br} = c
61 Client{Opts{b, h, t, mode, Retry.Policy{a, base, cap, Some{U32.to_nat(ms)}}, cert, ca}, pool, j, br}
62
63
64def circuit(c: Client, circ: Retry.Circuit) -> Client:
65 Client{o, pool, j, old} = c
66 Client{o, pool, j, Retry.Breaker.new(circ)}
67
68
69def breaker.of(c: Client) -> Client & Retry.Breaker:
70 Client{o, pool, j, +br} = c
71 (Client{o, pool, j, br}, br)
72
73
74def cert(c: Client, chain: String, key: String) -> Client:
75 Client{Opts{b, h, ms, mode, p, old, ca}, pool, j, br} = c
76 Client{Opts{b, h, ms, mode, p, Some{Http.Cert{chain, key}}, ca}, pool, j, br}
77
78
79def ca(c: Client, path: String) -> Client:
80 Client{Opts{b, h, ms, mode, p, cert, old}, pool, j, br} = c
81 Client{Opts{b, h, ms, mode, p, cert, path}, pool, j, br}
82
83
84def jar(c: Client, jar: Http.Jar) -> Client:
85 Client{o, pool, old, br} = c
86 Client{o, pool, Some{jar}, br}
87
88
89def jar.of(c: Client) -> Client & Maybe<&2, Http.Jar>:
90 Client{o, pool, +j, br} = c
91 (Client{o, pool, j, br}, j)
92
93def close(c: Client) -> IO(Unit):
94 Client{o, pool, j, br} = c
95 Http.pool.close(pool)
96
97
98def stream.close(c: Client, st: Http.Stream) -> IO(Client):
99 Client{o, pool, j, br} = c
100 do IO<Client>:
101 p : Http.Pool <- Http.pool.stream.close(pool, st)
102 return Client{o, p, j, br}
103
104def target.pick(none: Bool, b: String, ref: String) -> Maybe<&2, String>:
105 match none:
106 case True{}:
107 Some{ref}
108 case False{}:
109 Http.resolve(b, ref)
110
111
112def target(+b: String, ref: String) -> Maybe<&2, String>:
113 target.pick(String.is_empty(b), b, ref)
114
115def target.abs(m: Maybe<&2, String>) -> Maybe<&2, Url.Abs>:
116 match m:
117 case None{}:
118 None{}
119 case Some{s}:
120 Url.absolute(s)
121
122
123
124def merge(defaults: Map<&2, List<&2, String>>, headers: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>:
125 Http.req.put(Map.to_list(&2, List<&2, String>, headers), Http.req.put(Map.to_list(&2, List<&2, String>, defaults), Http.empty()))
126
127def jar.now(j: Maybe<&2, Http.Jar>) -> IO(Maybe<&2, Http.Jar>):
128 match j:
129 case None{}:
130 IO.pure(Maybe<&2, Http.Jar>, None{})
131 case Some{jar}:
132 do IO<Maybe<&2, Http.Jar>>:
133 now : Time.Instant <- Time.now()
134 return Some{Http.jar.at(jar, now)}
135
136def once.final(+enc: String, x: Http.Pool & Result<&1, &1, Http.Err, Http.Res> & Maybe<&2, Http.Jar>) ->
137 IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, Http.Res>):
138 (p, r, j) = x
139 do IO<Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, Http.Res>>:
140 d : Result<&1, &1, Http.Err, Http.Res> <- Http.fetch.final(enc, r)
141 return (p, j, d)
142
143
144def once(+ms: U32, +mode: Http.Mode, +cert: Maybe<&2, Http.Cert>, +ca: String, +enc: String, p: Http.Pool, j: Maybe<&2, Http.Jar>,
145 +method: String, +u: Maybe<&2, Url.Abs>, +headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) ->
146 IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, Http.Res>):
147 do IO<Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, Http.Res>>:
148 now : Maybe<&2, Http.Jar> <- jar.now(j)
149 x : Http.Pool & Result<&1, &1, Http.Err, Http.Res> & Maybe<&2, Http.Jar> <-
150 Http.pool.hops(mode, ms, Http.Hops{p, Http.fetch.start(method, headers, body, u, enc), cert, now, ca})
151 once.final(enc, x)
152
153def hint.ms(m: Maybe<&2, U32>) -> Maybe<&2, Nat>:
154 match m:
155 case None{}:
156 None{}
157 case Some{ms}:
158 Some{U32.to_nat(ms)}
159
160
161
162
163type Verdict is Data:
164 VerdictOk{}
165 VerdictCaller{}
166 VerdictTarget{}
167 VerdictAgain{after: String}
168
169
170def counts(v: Verdict) -> Bool:
171 match v:
172 case VerdictOk{}:
173 False{}
174 case VerdictCaller{}:
175 False{}
176 case VerdictTarget{}:
177 True{}
178 case VerdictAgain{after}:
179 True{}
180
181
182def outcome(+idem: Bool, v: Verdict, now: Time.Instant) -> Retry.Outcome:
183 match v:
184 case VerdictOk{}:
185 Retry.Settled{}
186 case VerdictCaller{}:
187 Retry.Failed{}
188 case VerdictTarget{}:
189 Retry.Failed{}
190 case VerdictAgain{+after}:
191 Bool.pick(Retry.Outcome, idem, Retry.Transient{hint.ms(Http.retry.after(after, now))}, Retry.Unsafe{})
192
193def lift(-R: Type, r: Result<&1, &1, Http.Err, R>) -> Result<&1, &1, Err, R>:
194 match r:
195 case Fail{e}:
196 Fail{ErrHttp{e}}
197 case Done{res}:
198 Done{res}
199
200
201
202type Step<-R: Type> is Type:
203 Step{pool: Http.Pool, jar: Maybe<&2, Http.Jar>, breaker: Retry.Breaker, budget: Retry.Budget, body: Bytes.Bytes,
204 last: Maybe<&1, Result<&1, &1, Err, R>>, plan: Retry.Plan}
205
206
207def clock(need: Bool) -> IO(Nat):
208 match need:
209 case True{}:
210 Time.mono.ms()
211 case False{}:
212 IO.pure(Nat, 0n)
213
214def breaker.on(br: Retry.Breaker) -> Bool:
215 match br:
216 case Retry.BreakerOff{}:
217 False{}
218 case Retry.BreakerOn{c, s}:
219 True{}
220
221def transient(o: Retry.Outcome) -> Bool:
222 match o:
223 case Retry.Transient{after}:
224 True{}
225 case Retry.Settled{}:
226 False{}
227 case Retry.Unsafe{}:
228 False{}
229 case Retry.Failed{}:
230 False{}
231
232def some(m: Maybe<&2, Nat>) -> Bool:
233 match m:
234 case None{}:
235 False{}
236 case Some{t}:
237 True{}
238
239def deadline.some(b: Retry.Budget) -> Bool:
240 Retry.Budget{l, d} = b
241 some(d)
242
243
244def outcome.io(+idem: Bool, v: Verdict) -> IO(Retry.Outcome):
245 match v:
246 case VerdictAgain{after}:
247 do IO<Retry.Outcome>:
248 wall : Time.Instant <- Time.now()
249 return outcome(idem, VerdictAgain{after}, wall)
250 case VerdictOk{}:
251 IO.pure(Retry.Outcome, Retry.Settled{})
252 case VerdictCaller{}:
253 IO.pure(Retry.Outcome, Retry.Failed{})
254 case VerdictTarget{}:
255 IO.pure(Retry.Outcome, Retry.Failed{})
256
257
258def jitter.io(need: Bool) -> IO(Nat):
259 match need:
260 case True{}:
261 do IO<Nat>:
262 rnd : Result<&1, &1, U32 & String, U32> <- IO.random_u32()
263 return U32.to_nat(Http.retry.jitter(rnd))
264 case False{}:
265 IO.pure(Nat, 0n)
266
267
268def settle.at(-R: Type, +now: Nat, +rnd: Nat, +p: Retry.Policy, +n: Nat, hit: Bool, +o: Retry.Outcome, br: Retry.Breaker, +b: Retry.Budget,
269 body: Bytes.Bytes, pool: Http.Pool, jar: Maybe<&2, Http.Jar>, r: Result<&1, &1, Http.Err, R>) -> Step<R>:
270 Step{pool, jar, Retry.Breaker.done(br, now, hit), b, body, Some{lift(R, r)}, Retry.plan(o, p, b, n, now, rnd)}
271
272
273def settle.outcome(-R: Type, +p: Retry.Policy, +n: Nat, hit: Bool, +o: Retry.Outcome, +br: Retry.Breaker, b: Retry.Budget, body: Bytes.Bytes,
274 pool: Http.Pool, jar: Maybe<&2, Http.Jar>, r: Result<&1, &1, Http.Err, R>) -> IO(Step<R>):
275 do IO<Step<R>>:
276 now : Nat <- clock(Bool.or(breaker.on(br), transient(o)))
277 rnd : Nat <- jitter.io(transient(o))
278 return settle.at(R, now, rnd, p, n, hit, o, br, b, body, pool, jar, r)
279
280def settle.judged(-R: Type, +p: Retry.Policy, +idem: Bool, +n: Nat, br: Retry.Breaker, b: Retry.Budget, body: Bytes.Bytes,
281 pool: Http.Pool, jar: Maybe<&2, Http.Jar>, j: Result<&1, &1, Http.Err, R> & Verdict) -> IO(Step<R>):
282 (r, +v) = j
283 do IO<Step<R>>:
284 o : Retry.Outcome <- outcome.io(idem, v)
285 settle.outcome(R, p, n, counts(v), o, br, b, body, pool, jar, r)
286
287
288def judge.status(+s: U32) -> Verdict:
289 Bool.pick(Verdict, U32.is_lt(s, 400), VerdictOk{}, Bool.pick(Verdict, U32.is_lt(s, 500), VerdictCaller{}, VerdictTarget{}))
290
291def judge.settled(r: Result<&1, &1, Http.Err, Http.Res>) -> Result<&1, &1, Http.Err, Http.Res> & Verdict:
292 match r:
293 case Fail{e}:
294 (Fail{e}, VerdictTarget{})
295 case Done{res}:
296 Http.Res{+s, h, b} = res
297 (Done{Http.Res{s, h, b}}, judge.status(s))
298
299def judge.of(x: Result<&1, &1, Http.Err, Http.Res> & Maybe<&2, String>) -> Result<&1, &1, Http.Err, Http.Res> & Verdict:
300 (r, m) = x
301 match m:
302 case Some{after}:
303 (r, VerdictAgain{after})
304 case None{}:
305 judge.settled(r)
306
307
308def judge(r: Result<&1, &1, Http.Err, Http.Res>) -> Result<&1, &1, Http.Err, Http.Res> & Verdict:
309 judge.of(Http.retry.judge(True{}, r))
310
311def settle(~R: Type, ~judge: Result<&1, &1, Http.Err, R> -> Result<&1, &1, Http.Err, R> & Verdict,
312 +p: Retry.Policy, +idem: Bool, +n: Nat, br: Retry.Breaker, b: Retry.Budget, body: Bytes.Bytes,
313 x: Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, R>) -> IO(Step<R>):
314 (pool, jar, r) = x
315 settle.judged(R, p, idem, n, br, b, body, pool, jar, judge(r))
316
317
318type Call is Data:
319 Call{enc: String, method: String, u: Maybe<&2, Url.Abs>, headers: Map<&2, List<&2, String>>}
320
321
322def try.res(o: Opts, call: Call, pool: Http.Pool, jar: Maybe<&2, Http.Jar>, body: Bytes.Bytes) ->
323 IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, Http.Res>):
324 Opts{base, h, +ms, +mode, p, +cert, +ca} = o
325 Call{+enc, +method, +u, +headers} = call
326 once(ms, mode, cert, ca, enc, pool, jar, method, u, headers, body)
327
328
329
330def try.stream(o: Opts, call: Call, pool: Http.Pool, jar: Maybe<&2, Http.Jar>, body: Bytes.Bytes) ->
331 IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, Http.Stream>):
332 Opts{base, h, +ms, mode, p, cert, ca} = o
333 Call{+enc, method, u, headers} = call
334 do IO<Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, Http.Stream>>:
335 now : Maybe<&2, Http.Jar> <- jar.now(jar)
336 r : Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, Http.Stream> <-
337 Http.stream.hops(mode, ms, (pool, Http.StreamHops{Http.stream.start(method, Http.accept.enc(headers, enc), body, u), cert, now, ca}))
338 Http.stream.init.pool(enc, r)
339
340def close.res(r: Http.Res) -> IO(Unit):
341 IO.pure(Unit, Unit{})
342
343def close.last(~R: Type, ~close: R -> IO(Unit), last: Maybe<&1, Result<&1, &1, Err, R>>) -> IO(Unit):
344 match last:
345 case None{}:
346 IO.pure(Unit, Unit{})
347 case Some{r}:
348 match r:
349 case Fail{e}:
350 IO.pure(Unit, Unit{})
351 case Done{x}:
352 close(x)
353
354def attempt.run(~R: Type, ~judge: Result<&1, &1, Http.Err, R> -> Result<&1, &1, Http.Err, R> & Verdict,
355 ~try: Opts -> Call -> Http.Pool -> Maybe<&2, Http.Jar> -> Bytes.Bytes -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, R>),
356 +o: Opts, +call: Call, +idem: Bool, +n: Nat, pool: Http.Pool, jar: Maybe<&2, Http.Jar>, br: Retry.Breaker,
357 b: Retry.Budget, r: Bytes.Bytes & Bytes.Bytes) -> IO(Step<R>):
358 Opts{base, h, ms, mode, +p, cert, ca} = o
359 (body, spare) = r
360 do IO<Step<R>>:
361 x : Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, R> <- try(o, call, pool, jar, spare)
362 settle(~R, ~judge, p, idem, n, br, b, body, x)
363
364def denied(-R: Type, why: Retry.Deny, last: Maybe<&1, Result<&1, &1, Err, R>>) -> Maybe<&1, Result<&1, &1, Err, R>>:
365 match last:
366 case None{}:
367 Some{Fail{ErrDenied{why}}}
368 case Some{r}:
369 Some{r}
370
371
372def attempt.gate(~R: Type, ~judge: Result<&1, &1, Http.Err, R> -> Result<&1, &1, Http.Err, R> & Verdict,
373 ~try: Opts -> Call -> Http.Pool -> Maybe<&2, Http.Jar> -> Bytes.Bytes -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, R>),
374 ~close: R -> IO(Unit),
375 x: Retry.Breaker & Retry.Budget & Retry.Gate, +o: Opts, +call: Call, +idem: Bool, +n: Nat, pool: Http.Pool,
376 jar: Maybe<&2, Http.Jar>, body: Bytes.Bytes, last: Maybe<&1, Result<&1, &1, Err, R>>) -> IO(Step<R>):
377 (br, b, g) = x
378 match g:
379 case Retry.GateNo{why}:
380 IO.pure(Step<R>, Step{pool, jar, br, b, body, denied(R, why, last), Retry.PlanStop{}})
381 case Retry.GateGo{}:
382 do IO<Step<R>>:
383 close.last(~R, ~close, last)
384 attempt.run(~R, ~judge, ~try, o, call, idem, n, pool, jar, br, b, Bytes.slice(body, 0, 4294967295))
385
386
387
388def attempt(~R: Type, ~judge: Result<&1, &1, Http.Err, R> -> Result<&1, &1, Http.Err, R> & Verdict,
389 ~try: Opts -> Call -> Http.Pool -> Maybe<&2, Http.Jar> -> Bytes.Bytes -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, R>),
390 ~close: R -> IO(Unit),
391 +o: Opts, +call: Call, +idem: Bool, +n: Nat, s: Step<R>) -> IO(Step<R>):
392 Step{pool, jar, +br, +b, body, last, plan} = s
393 do IO<Step<R>>:
394 now : Nat <- clock(Bool.or(breaker.on(br), deadline.some(b)))
395 attempt.gate(~R, ~judge, ~try, ~close, Retry.admit(br, b, now), o, call, idem, n, pool, jar, body, last)
396
397def pause(ms: Nat) -> IO(Unit):
398 match ms:
399 case 0n:
400 IO.pure(Unit, Unit{})
401 case 1n+k:
402 IO.sleep(U32.from_nat(1n+k))
403
404
405def go(~R: Type, ~judge: Result<&1, &1, Http.Err, R> -> Result<&1, &1, Http.Err, R> & Verdict,
406 ~try: Opts -> Call -> Http.Pool -> Maybe<&2, Http.Jar> -> Bytes.Bytes -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, R>),
407 ~close: R -> IO(Unit),
408 left: Nat, +n: Nat, +o: Opts, +call: Call, +idem: Bool, s: Step<R>) -> IO(Step<R>):
409 match left:
410 case 0n:
411 IO.pure(Step<R>, s)
412 case 1n+rest:
413 Step{pool, jar, br, b, body, last, plan} = s
414 match plan:
415 case Retry.PlanStop{}:
416 IO.pure(Step<R>, Step{pool, jar, br, b, body, last, Retry.PlanStop{}})
417 case Retry.PlanWait{ms}:
418 do IO<Step<R>>:
419 pause(ms)
420 next : Step<R> <- attempt(~R, ~judge, ~try, ~close, o, call, idem, n, Step{pool, jar, br, b, body, last, Retry.PlanStop{}})
421 go(~R, ~judge, ~try, ~close, rest, 1n+n, o, call, idem, next)
422
423def result.of(-R: Type, m: Maybe<&1, Result<&1, &1, Err, R>>) -> Result<&1, &1, Err, R>:
424 match m:
425 case None{}:
426 Fail{ErrDenied{Retry.DenySpent{}}}
427 case Some{r}:
428 r
429
430def request.done(-R: Type, +o: Opts, s: Step<R>) -> Client & Retry.Budget & Result<&1, &1, Err, R>:
431 Step{pool, jar, br, b, body, last, plan} = s
432 (Client{o, pool, jar, br}, b, result.of(R, last))
433
434def attempts(p: Retry.Policy) -> Nat:
435 Retry.Policy{a, base, cap, d} = p
436 a
437
438
439def budget(c: Client) -> IO(Client & Retry.Budget):
440 Client{+o, pool, jar, br} = c
441 Opts{base, h, ms, mode, Retry.Policy{+a, bs, cap, +d}, cert, ca} = o
442 do IO<Client & Retry.Budget>:
443 now : Nat <- clock(some(d))
444 return (Client{o, pool, jar, br}, Retry.Budget.new(a, now, d))
445
446def run(~R: Type, ~judge: Result<&1, &1, Http.Err, R> -> Result<&1, &1, Http.Err, R> & Verdict,
447 ~try: Opts -> Call -> Http.Pool -> Maybe<&2, Http.Jar> -> Bytes.Bytes -> IO(Http.Pool & Maybe<&2, Http.Jar> & Result<&1, &1, Http.Err, R>),
448 ~close: R -> IO(Unit),
449 c: Client, b: Retry.Budget, +idem: Bool, enc: String, method: String, url: String,
450 headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Client & Retry.Budget & Result<&1, &1, Err, R>):
451 Client{+o, pool, jar, br} = c
452 Opts{+base, +h, ms, mode, +p, cert, ca} = o
453 +call = {Call{enc, method, target.abs(target(base, url)), merge(h, headers)} : Call}
454 do IO<Client & Retry.Budget & Result<&1, &1, Err, R>>:
455 s : Step<R> <- go(~R, ~judge, ~try, ~close, attempts(p), 0n, o, call, idem, Step{pool, jar, br, b, body, None{}, Retry.PlanWait{0n}})
456 return request.done(R, o, s)
457
458
459
460
461
462
463
464def request.as(~judge: Result<&1, &1, Http.Err, Http.Res> -> Result<&1, &1, Http.Err, Http.Res> & Verdict,
465 c: Client, b: Retry.Budget, +idem: Bool, +enc: String, +method: String, url: String,
466 headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Client & Retry.Budget & Result<&1, &1, Err, Http.Res>):
467 run(~Http.Res, ~judge, ~try.res, ~close.res, c, b, idem, enc, method, url, headers, body)
468
469
470
471
472
473
474def open.as(~judge: Result<&1, &1, Http.Err, Http.Stream> -> Result<&1, &1, Http.Err, Http.Stream> & Verdict,
475 c: Client, b: Retry.Budget, +idem: Bool, +enc: String, +method: String, url: String,
476 headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Client & Retry.Budget & Result<&1, &1, Err, Http.Stream>):
477 run(~Http.Stream, ~judge, ~try.stream, ~Http.stream.close, c, b, idem, enc, method, url, headers, body)
478
479
480def request.in(c: Client, b: Retry.Budget, +method: String, url: String, headers: Map<&2, List<&2, String>>,
481 body: Bytes.Bytes) -> IO(Client & Retry.Budget & Result<&1, &1, Err, Http.Res>):
482 do IO<Client & Retry.Budget & Result<&1, &1, Err, Http.Res>>:
483 enc : String <- Http.codings()
484 request.as(~judge, c, b, Http.pool.idempotent(method), enc, method, url, headers, body)
485
486def request.drop(x: Client & Retry.Budget & Result<&1, &1, Err, Http.Res>) -> Client & Result<&1, &1, Err, Http.Res>:
487 (c, b, r) = x
488 (c, r)
489
490def request.budget(+method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes,
491 x: Client & Retry.Budget) -> IO(Client & Retry.Budget & Result<&1, &1, Err, Http.Res>):
492 (c, b) = x
493 request.in(c, b, method, url, headers, body)
494
495
496
497
498
499
500def request(c: Client, +method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) ->
501 IO(Client & Result<&1, &1, Err, Http.Res>):
502 do IO<Client & Result<&1, &1, Err, Http.Res>>:
503 y : Client & Retry.Budget <- budget(c)
504 x : Client & Retry.Budget & Result<&1, &1, Err, Http.Res> <- request.budget(method, url, headers, body, y)
505 return request.drop(x)
506
507def get(c: Client, url: String) -> IO(Client & Result<&1, &1, Err, Http.Res>):
508 request(c, "GET", url, Http.empty(), Bytes.new(0))
509
510
511def post.json(c: Client, url: String, v: Json.Val) -> IO(Client & Result<&1, &1, Err, Http.Res>):
512 request(c, "POST", url, Http.set(Http.empty(), "content-type", "application/json"), Json.encode.bytes(v))