src/packed.bend
emerging-ezhttp@0.21.0.0 · 211 lines · 23 definitions
1
2
3
4
5
6
7
8
9import Base
10import bend-kit-bytes@0.3.2.0/bytes.bend as Bytes
11import emerging-ezhttp@0.21.0.0/src/http.bend as Http
12
13
14
15type Got is Type:
16 Torn{why: String}
17 Got{status: U32, headers: List<&2, Http.Header>, reason: String,
18 body: Bytes.Bytes}
19
20
21type Parts is Type:
22 Bad{why: String}
23 Parts{parts: List<&1, Bytes.Bytes>}
24
25
26def packed.text.of(r: Bytes.Bytes & Bytes.Bytes) -> Bytes.Bytes & String:
27 (b, piece) = r
28 (b, Bytes.to_string(piece))
29
30
31def packed.text(b: Bytes.Bytes, +at: U32, +n: U32) -> Bytes.Bytes & String:
32 packed.text.of(Bytes.slice(b, at, n))
33
34
35def packed.copy(b: Bytes.Bytes, +at: U32, +n: U32) -> Bytes.Bytes:
36 Bytes.snd(Bytes.slice(b, at, n))
37
38
39
40
41def packed.chunk.take(r: Bytes.Bytes & Bytes.Bytes, +next: U32,
42 acc: List<&1, Bytes.Bytes>,
43 go: Bytes.Bytes -> U32 -> List<&1, Bytes.Bytes> -> Parts) -> Parts:
44 (b, part) = r
45 go(b)(next)(part <> acc)
46
47
48def packed.chunk.ready.is(b: Bytes.Bytes, +d: U32, +k: U32,
49 acc: List<&1, Bytes.Bytes>, ready: Bool,
50 go: Bytes.Bytes -> U32 -> List<&1, Bytes.Bytes> -> Parts) -> Parts:
51 match ready:
52 case False{}:
53 Bad{Http.chunk.short()}
54 case True{}:
55 packed.chunk.take(Bytes.slice(b, d, k), (d + k + 2 : U32), acc, go)
56
57
58def packed.chunk.ready(r: Bytes.Bytes & String, +d: U32, +k: U32,
59 acc: List<&1, Bytes.Bytes>,
60 go: Bytes.Bytes -> U32 -> List<&1, Bytes.Bytes> -> Parts) -> Parts:
61 (b, crlf) = r
62 packed.chunk.ready.is(b, d, k, acc, String.eq(crlf, "\r\n"), go)
63
64
65
66def packed.chunk.data(b: Bytes.Bytes, +d: U32, +n: Nat,
67 acc: List<&1, Bytes.Bytes>, fits: Bool,
68 go: Bytes.Bytes -> U32 -> List<&1, Bytes.Bytes> -> Parts) -> Parts:
69 match fits:
70 case False{}:
71 Bad{Http.chunk.short()}
72 case True{}:
73 +k = U32.from_nat(n)
74 packed.chunk.ready(packed.text(b, (d + k : U32), 2), d, k, acc, go)
75
76
77def packed.chunk.size(b: Bytes.Bytes, +d: U32, +len: U32,
78 acc: List<&1, Bytes.Bytes>, z: Http.Size,
79 go: Bytes.Bytes -> U32 -> List<&1, Bytes.Bytes> -> Parts) -> Parts:
80 match z:
81 case Http.SizeBad{why}:
82 Bad{why}
83 case Http.SizeLast{}:
84 Parts{acc}
85 case Http.SizeData{+n}:
86 packed.chunk.data(b, d, n, acc,
87 Bool.not(Nat.is_lt(U32.to_nat((len - d : U32)), Nat.add(n, 2n))), go)
88
89
90def packed.chunk.line(r: Bytes.Bytes & String, +d: U32, +len: U32,
91 acc: List<&1, Bytes.Bytes>,
92 go: Bytes.Bytes -> U32 -> List<&1, Bytes.Bytes> -> Parts) -> Parts:
93 (b, line) = r
94 packed.chunk.size(b, d, len, acc, Http.chunk.size(line), go)
95
96
97def packed.chunk.at(r: Bytes.Bytes & Maybe<&2, U32>, +p: U32,
98 +len: U32, acc: List<&1, Bytes.Bytes>,
99 go: Bytes.Bytes -> U32 -> List<&1, Bytes.Bytes> -> Parts) -> Parts:
100 (b, at) = r
101 match at:
102 case None{}:
103 Parts{acc}
104 case Some{+i}:
105 packed.chunk.line(packed.text(b, p, (i - p : U32)), (i + 2 : U32),
106 len, acc, go)
107
108
109def packed.chunks(fuel: Nat, b: Bytes.Bytes, +p: U32, +len: U32,
110 acc: List<&1, Bytes.Bytes>) -> Parts:
111 match fuel:
112 case 0n:
113 Bad{"the chunked body is truncated"}
114 case 1n+f:
115 packed.chunk.at(Bytes.find.from(b, "\r\n", p), p, len, acc,
116 b2 => q => a2 => packed.chunks(f, b2, q, len, a2))
117
118
119def packed.chunked(st: U32, hs: List<&2, Http.Header>, reason: String,
120 r: Parts) -> Got:
121 match r:
122 case Bad{why}:
123 Torn{why}
124 case Parts{ps}:
125 Got{st, hs, reason, Bytes.concat(List.reverse(&1, Bytes.Bytes, ps))}
126
127
128
129
130def packed.sized(st: U32, hs: List<&2, Http.Header>, reason: String,
131 b: Bytes.Bytes, +q: U32, +n: Nat, short: Bool) -> Got:
132 match short:
133 case True{}:
134 Torn{"the response body stopped before its Content-Length"}
135 case False{}:
136 Got{st, hs, reason, packed.copy(b, q, U32.from_nat(n))}
137
138
139def packed.body(st: U32, hs: List<&2, Http.Header>, reason: String,
140 f: Http.Frame, b: Bytes.Bytes, +q: U32, +len: U32) -> Got:
141 match f:
142 case Http.Bare{}:
143 Got{st, hs, reason, Bytes.new(0)}
144 case Http.Sized{+n}:
145 packed.sized(st, hs, reason, b, q, n,
146 Nat.is_lt(U32.to_nat((len - q : U32)), n))
147 case Http.Chunked{}:
148 packed.chunked(st, hs, reason,
149 packed.chunks(U32.to_nat((len - q : U32)), b, q, len, []))
150 case Http.Closed{}:
151 Got{st, hs, reason, packed.copy(b, q, (len - q : U32))}
152
153
154def packed.final(p: Http.Plan, reason: String, b: Bytes.Bytes, +q: U32,
155 +len: U32) -> Got:
156 match p:
157 case Http.Unplanned{why}:
158 Torn{why}
159 case Http.Planned{st, hs, f}:
160 packed.body(st, hs, reason, f, b, q, len)
161
162
163
164
165def packed.step(+asked: Bool, b: Bytes.Bytes, +q: U32, +len: U32,
166 +ls: List<&2, String>, skip: Bool, rest: Bytes.Bytes -> U32 -> Got) -> Got:
167 match skip:
168 case True{}:
169 rest(b)(q)
170 case False{}:
171 packed.final(Http.head.final(asked, ls), Http.phrase.line(Http.first(ls)),
172 b, q, len)
173
174
175def packed.head.text(+asked: Bool, r: Bytes.Bytes & String, +q: U32,
176 +len: U32, rest: Bytes.Bytes -> U32 -> Got) -> Got:
177 (b, before) = r
178 +ls = String.lines(before)
179 packed.step(asked, b, q, len, ls,
180 Http.interim.skip.of(Http.status.of(ls), U32.is_lt(q, len)), rest)
181
182
183def packed.head(+asked: Bool, r: Bytes.Bytes & Maybe<&2, U32>, +p: U32,
184 +len: U32, rest: Bytes.Bytes -> U32 -> Got) -> Got:
185 (b, at) = r
186 match at:
187 case None{}:
188 Torn{"the response head has no blank line after it"}
189 case Some{+i}:
190 packed.head.text(asked, packed.text(b, p, (i - p : U32)), (i + 4 : U32),
191 len, rest)
192
193
194def packed.heads(fuel: Nat, +asked: Bool, b: Bytes.Bytes, +p: U32, +len: U32)
195 -> Got:
196 match fuel:
197 case 0n:
198 Torn{"the response head has no blank line after it"}
199 case 1n+f:
200 packed.head(asked, Bytes.find.from(b, "\r\n\r\n", p), p, len,
201 b2 => q => packed.heads(f, asked, b2, q, len))
202
203
204def packed.reply.of(+method: String, r: Bytes.Bytes & U32) -> Got:
205 (b, +len) = r
206 packed.heads(U32.to_nat(len), String.eq(method, "HEAD"), b, 0, len)
207
208
209
210def packed.reply(+method: String, b: Bytes.Bytes) -> Got:
211 packed.reply.of(method, Bytes.length(b))