src/wire.bend
emerging-ezhttp@0.13.0.0 · 88 lines · 0 definitions
1# ezhttp/src/wire: one request out and one response back. TLS and DNS stay in
2# the effect (OpenSSL via dlopen); this module does not re-specify TLS.
3#
4# Spec: scheme, host, port each on their own line; everything after the third
5# newline is the request verbatim; the scheme line may carry a timeout
6# (wirespec.bend). Answer: status on the first line, then text
7# ("0" and the raw response, or a non-zero code and the reason).
8#
9# The effect opens a connection and sends the request, then answers one read
10# at a time; when the response is whole is wirespec.bend's decision
11# (wire.read.next), so a server that keeps the connection open after a
12# complete response is not waited on (SPEC.md HTTP-CLI-5, HTTP-CLI-6).
13import Base
14import emerging-ezhttp@0.13.0.0/src/wirespec.bend as Spec
15
16# connect, send the request a spec carries, and answer a connection number
17def ezwire.open(spec: String) -> IO(String):
18 import "./effs/wire.c"
19 import "./effs/wire.js"
20
21# the next octets a connection has received, "" once the server has closed
22def ezwire.read(conn: U32) -> IO(String):
23 import "./effs/wire.c"
24 import "./effs/wire.js"
25
26# close a connection
27def ezwire.close(conn: U32) -> IO(Unit):
28 import "./effs/wire.c"
29 import "./effs/wire.js"
30
31# the spec the effect reads (wirespec.bend)
32def wire.spec(secure: Bool, host: String, port: U32, req: String) -> String:
33 Spec.wire.spec(secure, host, port, req)
34
35# the most reads one exchange makes
36def wire.reads() -> Nat:
37 65536n
38
39# a read's outcome: read again, or the answer
40def wire.loop.on(r: Spec.Read, more: String -> IO(String)) -> IO(String):
41 match r:
42 case Spec.Again{acc}:
43 more(acc)
44 case Spec.Heard{answer}:
45 IO.pure(String, answer)
46
47# read until wirespec.bend says the answer is whole
48def wire.loop(fuel: Nat, +conn: U32, +method: String, acc: String)
49 -> IO(String):
50 match fuel:
51 case 0n:
52 IO.pure(String, Spec.wire.spent())
53 case 1n+p:
54 do IO<String>:
55 +got : String <- ezwire.read(conn)
56 wire.loop.on(Spec.wire.read.next(method, acc, got),
57 next => wire.loop(p, conn, method, next))
58
59# read the answer on an open connection, then close it
60def wire.hear(+conn: U32, method: String) -> IO(String):
61 do IO<String>:
62 +answer : String <- wire.loop(wire.reads(), conn, method, "")
63 _done : Unit <- ezwire.close(conn)
64 IO.pure(String, answer)
65
66# an open's outcome: read from the connection, or the answer
67def wire.after(o: Spec.Opened, method: String) -> IO(String):
68 match o:
69 case Spec.Refused{answer}:
70 IO.pure(String, answer)
71 case Spec.Open{conn}:
72 wire.hear(conn, method)
73
74# a request with this method, sent as a spec says, and its whole answer
75def wire.run(method: String, spec: String) -> IO(String):
76 do IO<String>:
77 +got : String <- ezwire.open(spec)
78 wire.after(Spec.wire.opened(got), method)
79
80# a request sent to a host and its whole answer read back
81def wire.talk(secure: Bool, host: String, port: U32, method: String,
82 req: String) -> IO(String):
83 wire.run(method, wire.spec(secure, host, port, req))
84
85# the same exchange, bounded by a timeout in milliseconds
86def wire.talk.timed(secure: Bool, ms: U32, host: String, port: U32,
87 method: String, req: String) -> IO(String):
88 wire.run(method, Spec.wire.spec.timed(secure, ms, host, port, req))