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