@amritk/nish-x86_64-linux 0.27.2 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +16 -8
- package/bin/nish +0 -0
- package/docs/AI.md +118 -26
- package/package.json +1 -1
- package/runtime/core/host.d.ts +2 -1
- package/runtime/core/host.mjs +4 -0
- package/runtime/core.d.ts +47 -0
- package/runtime/core.mjs +67 -0
- package/runtime/nish.d.ts +176 -0
- package/runtime/nish.h +120 -6
- package/runtime/nish.mjs +24 -4
- package/runtime/runtime-checksum.c +270 -0
- package/runtime/runtime-net.c +386 -5
- package/runtime/runtime-os.c +59 -8
- package/runtime/runtime-parallel.c +81 -5
- package/runtime/runtime-sandbox.c +479 -0
- package/runtime/runtime-sha256.c +239 -0
- package/runtime/runtime-shm.c +541 -0
- package/runtime/runtime.c +827 -791
- package/scripts/build.sh +79 -28
- package/std/README.md +25 -13
- package/std/collections.ts +5 -5
- package/std/compress/checksum.ts +88 -75
- package/std/compress/deflate.ts +224 -0
- package/std/compress/gzip.ts +52 -0
- package/std/crypto/bcrypt.ts +816 -280
- package/std/crypto/hmac.ts +305 -2
- package/std/crypto/jwt.ts +1661 -0
- package/std/json-equal.ts +335 -0
- package/std/json-format.ts +1070 -0
- package/std/json-number.ts +428 -0
- package/std/json-pointer.ts +245 -0
- package/std/json-typed.ts +34 -0
- package/std/json-value.ts +510 -17
- package/std/json.ts +35 -6
- package/std/net/handler.ts +1348 -0
- package/std/net/http-client.ts +97 -16
- package/std/net/http-compress.ts +301 -16
- package/std/net/http1-client.ts +104 -19
- package/std/net/http1-server.ts +127 -24
- package/std/net/http1.ts +24 -4
- package/std/net/outbound.ts +409 -0
- package/std/net/postgres.ts +405 -0
- package/std/net/prefork.ts +344 -20
- package/std/net/websocket.ts +295 -14
- package/std/regex.ts +613 -52
- package/std/secret.ts +1 -1
- package/std/sync/ring.ts +587 -0
- package/std/sync/shm.ts +284 -0
- package/std/term-io.ts +35 -8
- package/std/term-rate.ts +13 -1
- package/std/threads.ts +35 -3
- package/std/yaml-json.ts +841 -0
- /package/runtime/{LICENSE-ryu → LICENSE-zmij} +0 -0
|
@@ -0,0 +1,1348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/handler` — complete requests and their responses over the
|
|
3
|
+
* HTTP/1.1 carrier, so that a server answers requests instead of driving
|
|
4
|
+
* `Http1Server`'s events by hand.
|
|
5
|
+
*
|
|
6
|
+
* const HOME: i32 = 0 // module constants, in the order the routes are added
|
|
7
|
+
* const USER: i32 = 1
|
|
8
|
+
* const router = new Router(new RouterLimits())
|
|
9
|
+
* router.add("GET", "/") // answers HOME
|
|
10
|
+
* router.add("GET", "/users/:id") // answers USER
|
|
11
|
+
* const h = new HttpHandler(new HandlerLimits(), tcpListen("0.0.0.0", 8080, 1024), router)
|
|
12
|
+
* while (true) {
|
|
13
|
+
* h.wait(1000)
|
|
14
|
+
* while (h.ready()) {
|
|
15
|
+
* const req = h.take()
|
|
16
|
+
* switch (req.route) {
|
|
17
|
+
* case HOME: h.send(req, 200, TEXT, hello, 0, hello.length); break
|
|
18
|
+
* case USER: { const w = h.json(); ...; h.sendJson(req, 200); break }
|
|
19
|
+
* default: h.refuse(req) // 404, or 405 with its Allow
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* The handler owns the readiness loop, the carrier and everything between a
|
|
25
|
+
* request's first byte and its last response byte: it accepts, routes each
|
|
26
|
+
* request at its head, gathers its body up to the cap, and hands the program
|
|
27
|
+
* the request once it is whole. Keep-alive and pipelining are the carrier's:
|
|
28
|
+
* a connection's next request is read only once the one before it is
|
|
29
|
+
* answered and handed over, so answers go out in request order.
|
|
30
|
+
*
|
|
31
|
+
* **There are no function values**, so the program pulls: `wait` runs one
|
|
32
|
+
* turn of the loop, `ready` and `take` give out the whole requests it made,
|
|
33
|
+
* and the program dispatches on `req.route` — the index `Router.add`
|
|
34
|
+
* answered, or a negative `ROUTER_*` code — with a `switch` on module
|
|
35
|
+
* constants, as `nish/net/router` describes.
|
|
36
|
+
*
|
|
37
|
+
* **A request may be answered later.** A request the program has taken stays
|
|
38
|
+
* its own until it answers it, in the same turn or in a later one (after a
|
|
39
|
+
* database answers, say). Every request taken is answered exactly once, by
|
|
40
|
+
* `send`, `sendFields`, `sendJson`, `problem`, `refuse` or `start` ... `end`,
|
|
41
|
+
* and once answered the request object is the handler's again: it goes back
|
|
42
|
+
* to a free list and serves a later request, so the program keeps no
|
|
43
|
+
* reference to it.
|
|
44
|
+
*
|
|
45
|
+
* **An answer that comes too late is dropped.** Each slot carries a
|
|
46
|
+
* generation, bumped whenever its connection closes, and a request remembers
|
|
47
|
+
* the generation it was read under. An answer for a request whose
|
|
48
|
+
* connection has closed in the meantime — the peer left, the idle timeout
|
|
49
|
+
* passed — answers `HANDLER_DROPPED` and sends nothing, even when a new
|
|
50
|
+
* connection has taken the slot since. The answer still hands the request
|
|
51
|
+
* back, so nothing leaks.
|
|
52
|
+
*
|
|
53
|
+
* **Nothing is allocated per request once warm.** A request's method, target
|
|
54
|
+
* and header section are copied out of the carrier's head into the request's
|
|
55
|
+
* own buffer at `H1_REQUEST` (the parser reuses its own for the next
|
|
56
|
+
* request), and everything the program reads is a span into that copy or
|
|
57
|
+
* into the body buffer. Those buffers, the free list and the queue grow to
|
|
58
|
+
* the largest request seen and are kept; JSON is written into one scratch
|
|
59
|
+
* buffer the handler owns. `tests/link/net_handler` pins `Arena.used()` flat
|
|
60
|
+
* over thousands of requests.
|
|
61
|
+
*
|
|
62
|
+
* **The body cap** is the carrier's `maxBody`, which `HandlerLimits` sets to
|
|
63
|
+
* one MiB: a declared length over it, or a chunked body that grows past it, is
|
|
64
|
+
* answered 413 by the carrier and the connection ends. A request's body
|
|
65
|
+
* buffer grows to the body it holds, so a handler holds at most its slots
|
|
66
|
+
* times the cap in bodies. `docs/security/handler.md` is the record.
|
|
67
|
+
*
|
|
68
|
+
* Misuse — a status outside 200 to 599, a reserved field (`Content-Length`,
|
|
69
|
+
* `Transfer-Encoding`, `Connection`, `Upgrade`, `Keep-Alive`), more body than
|
|
70
|
+
* the length said, a second `start` — panics: it is a bug in the program,
|
|
71
|
+
* never something a peer can cause. What a peer can cause answers a code the
|
|
72
|
+
* program may ignore, so no answer needs its result bound.
|
|
73
|
+
*
|
|
74
|
+
* Private helpers share the importing program's flat symbol namespace
|
|
75
|
+
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
76
|
+
*
|
|
77
|
+
* Written from RFC 9110, RFC 9112 and RFC 9457, not ported from another
|
|
78
|
+
* implementation.
|
|
79
|
+
*/
|
|
80
|
+
import { copyBytes } from "nish/bytes"
|
|
81
|
+
import { JsonWriter } from "nish/json"
|
|
82
|
+
import { httpFieldBytes } from "nish/net/http-fields"
|
|
83
|
+
import { HTTP1_CHUNKED, HTTP1_NO_BODY, http1HexValue } from "nish/net/http1"
|
|
84
|
+
import {
|
|
85
|
+
H1_AGAIN,
|
|
86
|
+
H1_BODY,
|
|
87
|
+
H1_DONE,
|
|
88
|
+
H1_END,
|
|
89
|
+
H1_ERROR,
|
|
90
|
+
H1_NEED_MORE,
|
|
91
|
+
H1_POOL_FULL,
|
|
92
|
+
H1_REQUEST,
|
|
93
|
+
H1_WANT_READ,
|
|
94
|
+
H1_WANT_WRITE,
|
|
95
|
+
H1_WRITE,
|
|
96
|
+
Http1Config,
|
|
97
|
+
Http1Connection,
|
|
98
|
+
Http1Server,
|
|
99
|
+
} from "nish/net/http1-server"
|
|
100
|
+
import {
|
|
101
|
+
ROUTER_BAD_ESCAPE,
|
|
102
|
+
ROUTER_METHOD_NOT_ALLOWED,
|
|
103
|
+
ROUTER_NO_ROOM,
|
|
104
|
+
Router,
|
|
105
|
+
routerDecode,
|
|
106
|
+
} from "nish/net/router"
|
|
107
|
+
import { URL_DECODE_ALLOW_NUL, URL_DECODE_ERR_SPACE, URL_DECODE_FORM, percentDecode } from "nish/net/url"
|
|
108
|
+
import { pollAdd, pollCreate, pollModify, pollRemove, pollWait } from "nish:net"
|
|
109
|
+
|
|
110
|
+
/** An answer sent, or queued to be sent. */
|
|
111
|
+
export const HANDLER_SENT: i32 = 0
|
|
112
|
+
/** An answer for a request whose connection closed first: nothing was sent. */
|
|
113
|
+
export const HANDLER_DROPPED: i32 = -21
|
|
114
|
+
/** `HttpRequest.query` and `header`: no such name. */
|
|
115
|
+
export const HANDLER_ABSENT: i32 = -20
|
|
116
|
+
|
|
117
|
+
/** The most descriptors `watch` takes beside the handler's own. */
|
|
118
|
+
export const HANDLER_MAX_WATCHED: i32 = 16
|
|
119
|
+
|
|
120
|
+
// A request's state, from the free list to the free list.
|
|
121
|
+
/** On the free list. */
|
|
122
|
+
const HANDLER_FREE: i32 = 0
|
|
123
|
+
/** Its head is routed and its body is arriving. */
|
|
124
|
+
const HANDLER_READING: i32 = 1
|
|
125
|
+
/** Whole, but its connection still holds more than half an output of the response before it unsent. */
|
|
126
|
+
const HANDLER_PARKED: i32 = 2
|
|
127
|
+
/** Whole and queued for `take`. */
|
|
128
|
+
const HANDLER_QUEUED: i32 = 3
|
|
129
|
+
/** Taken: the program owes it an answer. */
|
|
130
|
+
const HANDLER_HELD: i32 = 4
|
|
131
|
+
/** Its response is started and the program is still writing the body. */
|
|
132
|
+
const HANDLER_STREAMING: i32 = 5
|
|
133
|
+
/** The program has ended the response; what the carrier had no room for is still being handed over. */
|
|
134
|
+
const HANDLER_FLUSHING: i32 = 6
|
|
135
|
+
|
|
136
|
+
/** The listener's poll token; a slot's is the slot, and watched descriptor `id`'s is `-2 - id`. */
|
|
137
|
+
const HANDLER_LISTENER: i32 = -1
|
|
138
|
+
/** Read, error and hang-up: the readiness bits that mean the socket has something for `readable`. */
|
|
139
|
+
const HANDLER_READ_BITS: i32 = 5
|
|
140
|
+
const HANDLER_ZERO: i32 = 0
|
|
141
|
+
const HANDLER_ONE_MIB: i32 = 1048576
|
|
142
|
+
/** `tcpAccept`: none is waiting (EAGAIN), or the one that was left before it was taken (ECONNABORTED). */
|
|
143
|
+
const HANDLER_ACCEPT_NONE: i32 = -11
|
|
144
|
+
const HANDLER_ACCEPT_ABORTED: i32 = -103
|
|
145
|
+
/** How long the listener rests after an accept fails for want of descriptors or memory: 100 ms. */
|
|
146
|
+
const HANDLER_ACCEPT_REST: i64 = 100000000
|
|
147
|
+
/** How often, at most, `wait` looks for a body past `bodyTimeout`: a second, in nanoseconds. */
|
|
148
|
+
const HANDLER_BODY_SWEEP: i64 = 1000000000
|
|
149
|
+
const HANDLER_NANOS_PER_MS: i64 = 1000000
|
|
150
|
+
/** `handlerFormByte`'s count of bytes taken, in its bits above the byte. */
|
|
151
|
+
const HANDLER_TOOK_ONE: i32 = 256
|
|
152
|
+
const HANDLER_TOOK_THREE: i32 = 768
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* What an `HttpHandler` holds and refuses past. `http` is the carrier's own
|
|
156
|
+
* configuration, made here with its body cap lowered to one MiB because the
|
|
157
|
+
* handler holds a body whole; raise `http.maxBody` for a server that takes
|
|
158
|
+
* larger bodies, knowing each slot may keep a buffer that large.
|
|
159
|
+
*/
|
|
160
|
+
export class HandlerLimits {
|
|
161
|
+
/** The carrier's caps and timeouts; `http.maxBody` is the body cap, past which a request is answered 413. */
|
|
162
|
+
http: Http1Config
|
|
163
|
+
/** The most connections at once. */
|
|
164
|
+
slots: i32 = 256
|
|
165
|
+
/** The JSON scratch buffer's size: the largest body `json` and `problem` can write. */
|
|
166
|
+
jsonBytes: i32 = 65536
|
|
167
|
+
/** The most readiness events one `wait` takes. */
|
|
168
|
+
maxEvents: i32 = 256
|
|
169
|
+
/**
|
|
170
|
+
* Milliseconds from a request's head to the end of its body. A request
|
|
171
|
+
* still reading its body past it has its connection closed: the carrier's
|
|
172
|
+
* `idleTimeout` is refreshed by every byte, so a body trickled a byte at a
|
|
173
|
+
* time would otherwise hold its slot for good (HANDLER-5). It is checked
|
|
174
|
+
* each `wait`, at most once a second, so a connection may outlive it by up
|
|
175
|
+
* to a second, or until `wait` returns.
|
|
176
|
+
*/
|
|
177
|
+
bodyTimeout: i32 = 30000
|
|
178
|
+
|
|
179
|
+
constructor() {
|
|
180
|
+
this.http = new Http1Config()
|
|
181
|
+
this.http.maxBody = HANDLER_ONE_MIB
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* One request, whole: its head copied out of the carrier and its body. Every
|
|
187
|
+
* position is an offset into `head` or `body`, which are the request's own
|
|
188
|
+
* and stay valid until it is answered.
|
|
189
|
+
*/
|
|
190
|
+
export class HttpRequest {
|
|
191
|
+
/** The method, the target and the header section, as the parser kept them (field names lowercased). */
|
|
192
|
+
head: u8[]
|
|
193
|
+
/** Four per field, into `head`: name start and end, value start and end. */
|
|
194
|
+
spans: i32[]
|
|
195
|
+
/** The route parameters as `[start, end)` pairs into `head`, in the pattern's order. */
|
|
196
|
+
params: i32[]
|
|
197
|
+
/** The body, `body[0 .. bodyLength)`. */
|
|
198
|
+
body: u8[]
|
|
199
|
+
/** What the carrier had no room for yet: `pending[pendingStart .. pendingEnd)`. */
|
|
200
|
+
pending: u8[]
|
|
201
|
+
/** When the head was read, as the handler's turn saw it (`monotonicNanos`); `bodyTimeout` runs from here. */
|
|
202
|
+
since: i64 = 0
|
|
203
|
+
/** The index `Router.add` answered for the route, or a negative `ROUTER_*` code. */
|
|
204
|
+
route: i32 = 0
|
|
205
|
+
/** How many parameter pairs `params` holds. */
|
|
206
|
+
paramCount: i32 = 0
|
|
207
|
+
methodStart: i32 = 0
|
|
208
|
+
methodEnd: i32 = 0
|
|
209
|
+
targetStart: i32 = 0
|
|
210
|
+
targetEnd: i32 = 0
|
|
211
|
+
/** Where the path ends: the target's first `?`, or its end. */
|
|
212
|
+
pathEnd: i32 = 0
|
|
213
|
+
headerCount: i32 = 0
|
|
214
|
+
bodyLength: i32 = 0
|
|
215
|
+
/** The connection's slot, and its generation when this request was read. */
|
|
216
|
+
slot: i32 = 0
|
|
217
|
+
gen: i32 = 0
|
|
218
|
+
/** Its index in the handler's pool. */
|
|
219
|
+
index: i32 = 0
|
|
220
|
+
state: i32 = 0
|
|
221
|
+
pendingStart: i32 = 0
|
|
222
|
+
pendingEnd: i32 = 0
|
|
223
|
+
|
|
224
|
+
constructor(index: i32) {
|
|
225
|
+
this.index = index
|
|
226
|
+
this.head = []
|
|
227
|
+
this.spans = []
|
|
228
|
+
this.params = []
|
|
229
|
+
this.body = []
|
|
230
|
+
this.pending = []
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Whether the method is exactly `method` (methods are case-sensitive, RFC 9110 §9.1). */
|
|
234
|
+
methodIs(method: string): boolean {
|
|
235
|
+
return handlerSpanIs(this.head, this.methodStart, this.methodEnd, method)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Whether the path, the target up to its `?`, is exactly `path`. */
|
|
239
|
+
pathIs(path: string): boolean {
|
|
240
|
+
return handlerSpanIs(this.head, this.targetStart, this.pathEnd, path)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Where parameter `k` starts in `head`. Panics on a parameter the route does not have. */
|
|
244
|
+
paramStart(k: i32): i32 {
|
|
245
|
+
this.checkParam(k)
|
|
246
|
+
return this.params[2 * k]
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Where parameter `k` ends in `head`. */
|
|
250
|
+
paramEnd(k: i32): i32 {
|
|
251
|
+
this.checkParam(k)
|
|
252
|
+
return this.params[2 * k + 1]
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Whether parameter `k`, raw, is exactly `text`. */
|
|
256
|
+
paramIs(k: i32, text: string): boolean {
|
|
257
|
+
this.checkParam(k)
|
|
258
|
+
return handlerSpanIs(this.head, this.params[2 * k], this.params[2 * k + 1], text)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Parameter `k` percent-decoded into `out` from 0 (`routerDecode`): its
|
|
263
|
+
* length, `ROUTER_BAD_ESCAPE` for a malformed escape, or `ROUTER_NO_ROOM`
|
|
264
|
+
* when `out` is too short. Nothing is written on a code.
|
|
265
|
+
*/
|
|
266
|
+
param(k: i32, out: u8[]): i32 {
|
|
267
|
+
this.checkParam(k)
|
|
268
|
+
return routerDecode(this.head, this.params[2 * k], this.params[2 * k + 1], out)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Parameter `k` read as a decimal number that is not negative and fits an `i32`, or -1. */
|
|
272
|
+
paramDecimal(k: i32): i32 {
|
|
273
|
+
this.checkParam(k)
|
|
274
|
+
return handlerDecimal(this.head, this.params[2 * k], this.params[2 * k + 1])
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
checkParam(k: i32): void {
|
|
278
|
+
if (k < 0 || k >= this.paramCount) {
|
|
279
|
+
panic(`HttpRequest.param: the route has ${this.paramCount} parameter(s), not one at ${k}`)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* The first query parameter named `name`, its value decoded into `out` from
|
|
285
|
+
* 0 — `+` as a space and `%XX` as its byte, the form encoding
|
|
286
|
+
* (application/x-www-form-urlencoded) — and its length; a name is compared
|
|
287
|
+
* decoded too. `HANDLER_ABSENT` when no parameter has the name,
|
|
288
|
+
* `ROUTER_BAD_ESCAPE` for a malformed escape in its value, `ROUTER_NO_ROOM`
|
|
289
|
+
* when `out` is too short. A name with no `=` has an empty value.
|
|
290
|
+
*/
|
|
291
|
+
query(name: string, out: u8[]): i32 {
|
|
292
|
+
const at: i32 = this.queryIndex(name)
|
|
293
|
+
if (at < 0) {
|
|
294
|
+
return HANDLER_ABSENT
|
|
295
|
+
}
|
|
296
|
+
return handlerFormDecode(this.head, this.queryValueStart, this.queryValueEnd, out)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Whether the query holds a parameter named `name`. */
|
|
300
|
+
hasQuery(name: string): boolean {
|
|
301
|
+
return this.queryIndex(name) >= 0
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** The span `queryIndex` found, raw. */
|
|
305
|
+
queryValueStart: i32 = 0
|
|
306
|
+
queryValueEnd: i32 = 0
|
|
307
|
+
|
|
308
|
+
/** Finds the first parameter named `name`: where its pair starts, or -1; its raw value goes to `queryValue...`. */
|
|
309
|
+
queryIndex(name: string): i32 {
|
|
310
|
+
let at: i32 = this.pathEnd + 1
|
|
311
|
+
const end: i32 = this.targetEnd
|
|
312
|
+
while (at < end) {
|
|
313
|
+
let pairEnd: i32 = at
|
|
314
|
+
while (pairEnd < end && toI32(this.head[pairEnd]) !== 38) {
|
|
315
|
+
pairEnd += 1
|
|
316
|
+
}
|
|
317
|
+
let keyEnd: i32 = at
|
|
318
|
+
while (keyEnd < pairEnd && toI32(this.head[keyEnd]) !== 61) {
|
|
319
|
+
keyEnd += 1
|
|
320
|
+
}
|
|
321
|
+
if (handlerFormIs(this.head, at, keyEnd, name)) {
|
|
322
|
+
this.queryValueStart = keyEnd < pairEnd ? keyEnd + 1 : pairEnd
|
|
323
|
+
this.queryValueEnd = pairEnd
|
|
324
|
+
return at
|
|
325
|
+
}
|
|
326
|
+
at = pairEnd + 1
|
|
327
|
+
}
|
|
328
|
+
return -1
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** The index of the first field named `name`, which is lowercase, or `HANDLER_ABSENT`. */
|
|
332
|
+
header(name: string): i32 {
|
|
333
|
+
for (let i: i32 = 0; i < this.headerCount; i++) {
|
|
334
|
+
if (handlerSpanIs(this.head, this.spans[4 * i], this.spans[4 * i + 1], name)) {
|
|
335
|
+
return i
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return HANDLER_ABSENT
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Where field `i`'s value starts in `head`. */
|
|
342
|
+
valueStart(i: i32): i32 {
|
|
343
|
+
return this.spans[4 * i + 2]
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Where field `i`'s value ends in `head`. */
|
|
347
|
+
valueEnd(i: i32): i32 {
|
|
348
|
+
return this.spans[4 * i + 3]
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** Whether the first field named `name` (lowercase) has exactly the value `value`. */
|
|
352
|
+
headerIs(name: string, value: string): boolean {
|
|
353
|
+
const i: i32 = this.header(name)
|
|
354
|
+
return i >= 0 && handlerSpanIs(this.head, this.spans[4 * i + 2], this.spans[4 * i + 3], value)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* The HTTP/1.1 server: a carrier, its readiness loop, a router and a pool of
|
|
360
|
+
* requests. One per thread; nothing in it is shared.
|
|
361
|
+
*/
|
|
362
|
+
export class HttpHandler {
|
|
363
|
+
server: Http1Server
|
|
364
|
+
router: Router
|
|
365
|
+
/** Every request the pool has made, and the indices of the free ones. */
|
|
366
|
+
requests: HttpRequest[]
|
|
367
|
+
free: i32[]
|
|
368
|
+
/** Whole requests waiting for `take`, a ring of `queue.length` with `queueCount` from `queueStart`. */
|
|
369
|
+
queue: i32[]
|
|
370
|
+
/** Per slot: its generation, its request (-1 for none), and whether it holds a connection. */
|
|
371
|
+
slotGen: i32[]
|
|
372
|
+
current: i32[]
|
|
373
|
+
open: boolean[]
|
|
374
|
+
/** `pollWait`'s output. */
|
|
375
|
+
readyFds: i32[]
|
|
376
|
+
/** Per watched id: whether its descriptor was ready in the last `wait`. */
|
|
377
|
+
watchedFired: boolean[]
|
|
378
|
+
/** `json` and `problem` write here. */
|
|
379
|
+
scratch: u8[]
|
|
380
|
+
writer: JsonWriter
|
|
381
|
+
/** `refuse`'s scratch for `Router.match`. */
|
|
382
|
+
probe: i32[]
|
|
383
|
+
/** One field for `send`; two for a 405. */
|
|
384
|
+
typeName: u8[][]
|
|
385
|
+
typeValue: u8[][]
|
|
386
|
+
allowNames: u8[][]
|
|
387
|
+
allowValues: u8[][]
|
|
388
|
+
/** Each `Allow` value made, by the bit mask of the methods it lists: made once a mask and kept. */
|
|
389
|
+
allowMasks: i64[]
|
|
390
|
+
allowBytes: u8[][]
|
|
391
|
+
jsonType: u8[]
|
|
392
|
+
problemType: u8[]
|
|
393
|
+
noFields: u8[][]
|
|
394
|
+
/**
|
|
395
|
+
* When an accept failed for want of a resource (EMFILE, ENFILE, ENOMEM):
|
|
396
|
+
* the listener is out of the loop until then, which it would otherwise wake
|
|
397
|
+
* every turn while its connection waits. 0 while it is in the loop.
|
|
398
|
+
*/
|
|
399
|
+
acceptRestUntil: i64 = 0
|
|
400
|
+
/** `bodyTimeout` in nanoseconds. */
|
|
401
|
+
bodyTimeout: i64 = 0
|
|
402
|
+
/** When the current turn's sockets were ready (`monotonicNanos`): a head read in it is stamped with this. */
|
|
403
|
+
turnAt: i64 = 0
|
|
404
|
+
/** When `wait` next looks for a body past `bodyTimeout`. */
|
|
405
|
+
bodyCheckAt: i64 = 0
|
|
406
|
+
listener: i32 = 0
|
|
407
|
+
loop: i32 = 0
|
|
408
|
+
queueStart: i32 = 0
|
|
409
|
+
queueCount: i32 = 0
|
|
410
|
+
outputSize: i32 = 0
|
|
411
|
+
/** Set by `drain`: accept nothing more and close each connection once it is idle. */
|
|
412
|
+
draining: boolean = false
|
|
413
|
+
/** Whether `json` gave out the writer for a value `sendJson` has not yet sent. */
|
|
414
|
+
jsonBegun: boolean = false
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* A handler over `listener`, a listening socket the program made
|
|
418
|
+
* (`tcpListen`, or `tcpListenFlags` with SO_REUSEPORT for a worker of
|
|
419
|
+
* `nish/net/prefork`), routing through `router`, whose routes are added
|
|
420
|
+
* before the first `wait`.
|
|
421
|
+
*/
|
|
422
|
+
constructor(limits: HandlerLimits, listener: i32, router: Router) {
|
|
423
|
+
if (limits.slots < 1 || limits.jsonBytes < 256 || limits.maxEvents < 1 || limits.bodyTimeout < 1) {
|
|
424
|
+
panic(
|
|
425
|
+
`HttpHandler: slots ${limits.slots}, jsonBytes ${limits.jsonBytes}, maxEvents ${limits.maxEvents} and bodyTimeout ${limits.bodyTimeout} need at least 1, 256, 1 and 1`
|
|
426
|
+
)
|
|
427
|
+
}
|
|
428
|
+
this.bodyTimeout = toI64(limits.bodyTimeout) * HANDLER_NANOS_PER_MS
|
|
429
|
+
this.server = new Http1Server(limits.http, listener, limits.slots)
|
|
430
|
+
this.router = router
|
|
431
|
+
this.listener = listener
|
|
432
|
+
this.outputSize = limits.http.outputSize
|
|
433
|
+
this.requests = []
|
|
434
|
+
this.free = []
|
|
435
|
+
this.queue = new Array<i32>(16)
|
|
436
|
+
this.slotGen = new Array<i32>(limits.slots)
|
|
437
|
+
this.current = new Array<i32>(limits.slots)
|
|
438
|
+
this.open = new Array<boolean>(limits.slots)
|
|
439
|
+
for (let s: i32 = 0; s < limits.slots; s++) {
|
|
440
|
+
this.current[s] = -1
|
|
441
|
+
}
|
|
442
|
+
this.readyFds = new Array<i32>(2 * limits.maxEvents)
|
|
443
|
+
this.watchedFired = new Array<boolean>(HANDLER_MAX_WATCHED)
|
|
444
|
+
this.scratch = new Array<u8>(limits.jsonBytes)
|
|
445
|
+
this.writer = new JsonWriter(this.scratch, HANDLER_ZERO)
|
|
446
|
+
this.probe = new Array<i32>(2 * router.maxParams + 2)
|
|
447
|
+
this.jsonType = httpFieldBytes("application/json")
|
|
448
|
+
this.problemType = httpFieldBytes("application/problem+json")
|
|
449
|
+
const contentType: u8[] = httpFieldBytes("content-type")
|
|
450
|
+
this.typeName = [contentType]
|
|
451
|
+
this.typeValue = [this.jsonType]
|
|
452
|
+
this.allowNames = [contentType, httpFieldBytes("allow")]
|
|
453
|
+
// The `Allow` value, `allowValues[1]`, is set for each 405.
|
|
454
|
+
this.allowValues = [this.problemType, this.problemType]
|
|
455
|
+
this.allowMasks = []
|
|
456
|
+
this.allowBytes = []
|
|
457
|
+
this.noFields = []
|
|
458
|
+
this.loop = pollCreate()
|
|
459
|
+
if (this.loop < 0) {
|
|
460
|
+
panic(`HttpHandler: pollCreate failed: ${this.loop}`)
|
|
461
|
+
}
|
|
462
|
+
pollAdd(this.loop, listener, H1_WANT_READ, HANDLER_LISTENER)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ---- The loop -------------------------------------------------------------------
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Watches `fd` for reading beside the handler's sockets, as `id` (0 to
|
|
469
|
+
* `HANDLER_MAX_WATCHED - 1`): `fired(id)` says whether it was ready in the
|
|
470
|
+
* last `wait`. For a program's own descriptors — `signalFd()` for a
|
|
471
|
+
* graceful stop, a pipe from another thread.
|
|
472
|
+
*/
|
|
473
|
+
watch(fd: i32, id: i32): void {
|
|
474
|
+
if (id < 0 || id >= HANDLER_MAX_WATCHED) {
|
|
475
|
+
panic(`HttpHandler.watch: id ${id} is outside 0 to ${HANDLER_MAX_WATCHED - 1}`)
|
|
476
|
+
}
|
|
477
|
+
pollAdd(this.loop, fd, H1_WANT_READ, -2 - id)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** Whether watched descriptor `id` was ready in the last `wait`. */
|
|
481
|
+
fired(id: i32): boolean {
|
|
482
|
+
return id >= 0 && id < HANDLER_MAX_WATCHED && this.watchedFired[id]
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* One turn of the loop: waits up to `timeoutMs` (-1 for no limit) for a
|
|
487
|
+
* socket to be ready, accepts, reads and writes what is, closes the
|
|
488
|
+
* connections past their timeouts, and answers how many whole requests
|
|
489
|
+
* are waiting for `take`.
|
|
490
|
+
*/
|
|
491
|
+
wait(timeoutMs: i32): i32 {
|
|
492
|
+
for (let id: i32 = 0; id < HANDLER_MAX_WATCHED; id++) {
|
|
493
|
+
this.watchedFired[id] = false
|
|
494
|
+
}
|
|
495
|
+
if (this.acceptRestUntil > 0 && monotonicNanos() >= this.acceptRestUntil) {
|
|
496
|
+
this.acceptRestUntil = 0
|
|
497
|
+
if (!this.draining) {
|
|
498
|
+
pollAdd(this.loop, this.listener, H1_WANT_READ, HANDLER_LISTENER)
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
let timeout: i32 = timeoutMs
|
|
502
|
+
if (this.acceptRestUntil > 0 && !this.draining) {
|
|
503
|
+
// Wake for the listener's return, whatever the program asked.
|
|
504
|
+
const rest: i32 = toI32((this.acceptRestUntil - monotonicNanos()) / toI64(1000000)) + 1
|
|
505
|
+
if (timeout < 0 || rest < timeout) {
|
|
506
|
+
timeout = rest
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
const n: i32 = pollWait(this.loop, this.readyFds, timeout)
|
|
510
|
+
const now: i64 = monotonicNanos()
|
|
511
|
+
this.turnAt = now
|
|
512
|
+
for (let k: i32 = 0; k < n; k++) {
|
|
513
|
+
const token: i32 = this.readyFds[2 * k]
|
|
514
|
+
if (token === HANDLER_LISTENER) {
|
|
515
|
+
this.acceptAll()
|
|
516
|
+
} else if (token <= -2) {
|
|
517
|
+
const id: i32 = -2 - token
|
|
518
|
+
if (id < HANDLER_MAX_WATCHED) {
|
|
519
|
+
this.watchedFired[id] = true
|
|
520
|
+
}
|
|
521
|
+
} else if (token >= 0 && this.server.holds(token)) {
|
|
522
|
+
if ((this.readyFds[2 * k + 1] & HANDLER_READ_BITS) !== 0) {
|
|
523
|
+
this.server.readable(token)
|
|
524
|
+
} else {
|
|
525
|
+
this.server.writable(token)
|
|
526
|
+
}
|
|
527
|
+
this.drive(token)
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (this.server.expire(now) > 0) {
|
|
531
|
+
this.sweep()
|
|
532
|
+
}
|
|
533
|
+
if (now >= this.bodyCheckAt) {
|
|
534
|
+
this.bodyCheckAt = now + HANDLER_BODY_SWEEP
|
|
535
|
+
this.expireBodies(now)
|
|
536
|
+
}
|
|
537
|
+
return this.queueCount
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Begins a graceful stop: the handler accepts no more connections, closes
|
|
542
|
+
* each idle one now, and closes each other one once the request on it is
|
|
543
|
+
* answered and sent, so `connections()` falls to 0 as the program answers
|
|
544
|
+
* what it holds. A request already read on a draining connection is still
|
|
545
|
+
* handed over and answered. The listener is the program's to close.
|
|
546
|
+
*/
|
|
547
|
+
drain(): void {
|
|
548
|
+
if (this.draining) {
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
this.draining = true
|
|
552
|
+
if (this.acceptRestUntil === 0) {
|
|
553
|
+
pollRemove(this.loop, this.listener)
|
|
554
|
+
}
|
|
555
|
+
for (let s: i32 = 0; s < toI32(this.open.length) && s < toI32(this.current.length); s++) {
|
|
556
|
+
if (
|
|
557
|
+
this.open[s] &&
|
|
558
|
+
this.server.holds(s) &&
|
|
559
|
+
this.current[s] === -1 &&
|
|
560
|
+
!this.server.connection(s).wantsWrite()
|
|
561
|
+
) {
|
|
562
|
+
this.closeSlot(s)
|
|
563
|
+
} else if (this.open[s] && this.server.holds(s)) {
|
|
564
|
+
// Its exchange is its last: the answer says `Connection: close` if
|
|
565
|
+
// it is not yet started, and no pipelined request is read behind it.
|
|
566
|
+
this.server.connection(s).closeAfter = true
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/** Takes every waiting connection. */
|
|
572
|
+
acceptAll(): void {
|
|
573
|
+
if (this.draining) {
|
|
574
|
+
return
|
|
575
|
+
}
|
|
576
|
+
let slot: i32 = this.server.accept()
|
|
577
|
+
while (slot >= 0 || slot === H1_POOL_FULL || slot === HANDLER_ACCEPT_ABORTED) {
|
|
578
|
+
if (slot >= 0) {
|
|
579
|
+
this.open[slot] = true
|
|
580
|
+
this.current[slot] = -1
|
|
581
|
+
pollAdd(this.loop, this.server.fd(slot), H1_WANT_READ, slot)
|
|
582
|
+
}
|
|
583
|
+
slot = this.server.accept()
|
|
584
|
+
}
|
|
585
|
+
if (slot !== HANDLER_ACCEPT_NONE) {
|
|
586
|
+
// Out of descriptors or memory: the connection stays in the backlog
|
|
587
|
+
// and the listener stays readable, so rest it rather than spin.
|
|
588
|
+
pollRemove(this.loop, this.listener)
|
|
589
|
+
this.acceptRestUntil = monotonicNanos() + HANDLER_ACCEPT_REST
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Takes `slot`'s events until it needs the socket again, then sends what it
|
|
595
|
+
* holds and re-arms it, or closes it once it is over.
|
|
596
|
+
*/
|
|
597
|
+
drive(slot: i32): void {
|
|
598
|
+
const conn: Http1Connection = this.server.connection(slot)
|
|
599
|
+
let event: i32 = this.server.next(slot)
|
|
600
|
+
while (event !== H1_NEED_MORE && event !== H1_ERROR) {
|
|
601
|
+
if (event === H1_REQUEST) {
|
|
602
|
+
this.begin(slot, conn)
|
|
603
|
+
} else if (event === H1_BODY) {
|
|
604
|
+
this.gather(slot, conn)
|
|
605
|
+
} else if (event === H1_END) {
|
|
606
|
+
this.whole(slot, conn)
|
|
607
|
+
} else if (event === H1_WRITE) {
|
|
608
|
+
this.resume(slot, conn)
|
|
609
|
+
}
|
|
610
|
+
event = this.server.next(slot)
|
|
611
|
+
}
|
|
612
|
+
if (event === H1_ERROR) {
|
|
613
|
+
// The carrier has written its refusal and ends the connection; a
|
|
614
|
+
// request it was reading is nobody's.
|
|
615
|
+
this.releaseReading(slot)
|
|
616
|
+
}
|
|
617
|
+
const wants: i32 = this.server.flush(slot)
|
|
618
|
+
if ((wants & H1_DONE) !== 0) {
|
|
619
|
+
this.closeSlot(slot)
|
|
620
|
+
return
|
|
621
|
+
}
|
|
622
|
+
if (this.draining && this.current[slot] === -1 && !conn.wantsWrite()) {
|
|
623
|
+
this.closeSlot(slot)
|
|
624
|
+
return
|
|
625
|
+
}
|
|
626
|
+
if (this.server.rearm(slot, wants)) {
|
|
627
|
+
pollModify(this.loop, this.server.fd(slot), wants & (H1_WANT_READ | H1_WANT_WRITE), slot)
|
|
628
|
+
}
|
|
629
|
+
const at: i32 = this.current[slot]
|
|
630
|
+
if (at >= 0 && this.requests[at].state === HANDLER_PARKED && this.roomy(conn)) {
|
|
631
|
+
this.enqueue(this.requests[at])
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** Whether `conn`'s output holds at most half its size unsent, so a response head fits behind it. */
|
|
636
|
+
roomy(conn: Http1Connection): boolean {
|
|
637
|
+
return (conn.outputEnd - conn.outputStart) * 2 <= this.outputSize
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/** A head: a request from the free list, the head copied into it, and its route. */
|
|
641
|
+
begin(slot: i32, conn: Http1Connection): void {
|
|
642
|
+
if (this.draining) {
|
|
643
|
+
// A head read during the drain is the connection's last.
|
|
644
|
+
conn.closeAfter = true
|
|
645
|
+
}
|
|
646
|
+
const r: HttpRequest = this.obtain()
|
|
647
|
+
const p = conn.parser
|
|
648
|
+
const length: i32 = p.headLength
|
|
649
|
+
if (toI32(r.head.length) < length) {
|
|
650
|
+
r.head = new Array<u8>(handlerGrown(toI32(r.head.length), length))
|
|
651
|
+
}
|
|
652
|
+
copyBytes(r.head, HANDLER_ZERO, p.head, HANDLER_ZERO, length)
|
|
653
|
+
const spanCount: i32 = 4 * p.headerCount
|
|
654
|
+
if (toI32(r.spans.length) < spanCount) {
|
|
655
|
+
r.spans = new Array<i32>(handlerGrown(toI32(r.spans.length), spanCount))
|
|
656
|
+
}
|
|
657
|
+
for (let k: i32 = 0; k < spanCount; k++) {
|
|
658
|
+
r.spans[k] = p.spans[k]
|
|
659
|
+
}
|
|
660
|
+
r.headerCount = p.headerCount
|
|
661
|
+
r.methodStart = p.methodStart
|
|
662
|
+
r.methodEnd = p.methodEnd
|
|
663
|
+
r.targetStart = p.targetStart
|
|
664
|
+
r.targetEnd = p.targetEnd
|
|
665
|
+
let pathEnd: i32 = r.targetStart
|
|
666
|
+
while (pathEnd < r.targetEnd && toI32(r.head[pathEnd]) !== 63) {
|
|
667
|
+
pathEnd += 1
|
|
668
|
+
}
|
|
669
|
+
r.pathEnd = pathEnd
|
|
670
|
+
r.bodyLength = 0
|
|
671
|
+
r.slot = slot
|
|
672
|
+
r.gen = this.slotGen[slot]
|
|
673
|
+
r.since = this.turnAt
|
|
674
|
+
r.state = HANDLER_READING
|
|
675
|
+
r.pendingStart = 0
|
|
676
|
+
r.pendingEnd = 0
|
|
677
|
+
this.route(r)
|
|
678
|
+
this.current[slot] = r.index
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Routes `r` through the router. A HEAD that only a GET route matches takes
|
|
683
|
+
* that route, since a GET resource answers HEAD (RFC 9110 §9.3.2) and the
|
|
684
|
+
* carrier sends no body for it.
|
|
685
|
+
*/
|
|
686
|
+
route(r: HttpRequest): void {
|
|
687
|
+
const params: i32[] = r.params
|
|
688
|
+
let route: i32 = this.router.match(
|
|
689
|
+
r.head,
|
|
690
|
+
r.methodStart,
|
|
691
|
+
r.methodEnd,
|
|
692
|
+
r.head,
|
|
693
|
+
r.targetStart,
|
|
694
|
+
r.targetEnd,
|
|
695
|
+
params
|
|
696
|
+
)
|
|
697
|
+
if (route === ROUTER_METHOD_NOT_ALLOWED && r.methodIs("HEAD")) {
|
|
698
|
+
const get: i32 = this.router.methodIndex("GET")
|
|
699
|
+
if (get >= 0) {
|
|
700
|
+
const viaGet: i32 = this.router.match(
|
|
701
|
+
this.router.methodBytes,
|
|
702
|
+
this.router.methodStart[get],
|
|
703
|
+
this.router.methodEnd[get],
|
|
704
|
+
r.head,
|
|
705
|
+
r.targetStart,
|
|
706
|
+
r.targetEnd,
|
|
707
|
+
params
|
|
708
|
+
)
|
|
709
|
+
if (viaGet >= 0) {
|
|
710
|
+
route = viaGet
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
r.route = route
|
|
715
|
+
r.paramCount = route >= 0 ? this.router.paramCount(route) : 0
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/** Body bytes: appended to the request's body, which the carrier's cap bounds. */
|
|
719
|
+
gather(slot: i32, conn: Http1Connection): void {
|
|
720
|
+
const at: i32 = this.current[slot]
|
|
721
|
+
if (at < 0) {
|
|
722
|
+
return
|
|
723
|
+
}
|
|
724
|
+
const r: HttpRequest = this.requests[at]
|
|
725
|
+
const n: i32 = conn.dataLength
|
|
726
|
+
const need: i32 = r.bodyLength + n
|
|
727
|
+
if (toI32(r.body.length) < need) {
|
|
728
|
+
const grown: u8[] = new Array<u8>(handlerGrown(toI32(r.body.length), need))
|
|
729
|
+
copyBytes(grown, HANDLER_ZERO, r.body, HANDLER_ZERO, r.bodyLength)
|
|
730
|
+
r.body = grown
|
|
731
|
+
}
|
|
732
|
+
copyBytes(r.body, r.bodyLength, conn.data, conn.dataStart, n)
|
|
733
|
+
r.bodyLength = need
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/** The request is whole: queued for `take`, or parked while the response before it is still mostly unsent. */
|
|
737
|
+
whole(slot: i32, conn: Http1Connection): void {
|
|
738
|
+
const at: i32 = this.current[slot]
|
|
739
|
+
if (at < 0) {
|
|
740
|
+
return
|
|
741
|
+
}
|
|
742
|
+
const r: HttpRequest = this.requests[at]
|
|
743
|
+
if (this.roomy(conn)) {
|
|
744
|
+
this.enqueue(r)
|
|
745
|
+
} else {
|
|
746
|
+
r.state = HANDLER_PARKED
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
enqueue(r: HttpRequest): void {
|
|
751
|
+
r.state = HANDLER_QUEUED
|
|
752
|
+
const size: i32 = toI32(this.queue.length)
|
|
753
|
+
if (this.queueCount === size) {
|
|
754
|
+
const grown: i32[] = new Array<i32>(2 * size)
|
|
755
|
+
for (let k: i32 = 0; k < size && k < toI32(grown.length); k++) {
|
|
756
|
+
grown[k] = this.queue[(this.queueStart + k) % size]
|
|
757
|
+
}
|
|
758
|
+
this.queue = grown
|
|
759
|
+
this.queueStart = 0
|
|
760
|
+
}
|
|
761
|
+
this.queue[(this.queueStart + this.queueCount) % toI32(this.queue.length)] = r.index
|
|
762
|
+
this.queueCount += 1
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/** `H1_WRITE`: the carrier has room again for the response under way. */
|
|
766
|
+
resume(slot: i32, conn: Http1Connection): void {
|
|
767
|
+
const at: i32 = this.current[slot]
|
|
768
|
+
if (at < 0) {
|
|
769
|
+
return
|
|
770
|
+
}
|
|
771
|
+
const r: HttpRequest = this.requests[at]
|
|
772
|
+
if (r.state !== HANDLER_STREAMING && r.state !== HANDLER_FLUSHING) {
|
|
773
|
+
return
|
|
774
|
+
}
|
|
775
|
+
this.pushPending(r, conn)
|
|
776
|
+
if (r.state === HANDLER_FLUSHING) {
|
|
777
|
+
this.finish(r, conn)
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/** Hands the carrier as much of `r`'s pending bytes as it takes. */
|
|
782
|
+
pushPending(r: HttpRequest, conn: Http1Connection): void {
|
|
783
|
+
const left: i32 = r.pendingEnd - r.pendingStart
|
|
784
|
+
if (left === 0) {
|
|
785
|
+
return
|
|
786
|
+
}
|
|
787
|
+
const taken: i32 = conn.write(r.pending, r.pendingStart, left)
|
|
788
|
+
if (taken > 0) {
|
|
789
|
+
r.pendingStart += taken
|
|
790
|
+
}
|
|
791
|
+
if (r.pendingStart === r.pendingEnd) {
|
|
792
|
+
r.pendingStart = 0
|
|
793
|
+
r.pendingEnd = 0
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/** Ends `r`'s response once nothing is pending, and hands `r` back; otherwise leaves it flushing. */
|
|
798
|
+
finish(r: HttpRequest, conn: Http1Connection): void {
|
|
799
|
+
r.state = HANDLER_FLUSHING
|
|
800
|
+
if (r.pendingEnd > r.pendingStart) {
|
|
801
|
+
return
|
|
802
|
+
}
|
|
803
|
+
// `H1_AGAIN`: no room for the last chunk yet; `H1_WRITE` comes back here.
|
|
804
|
+
if (conn.end() === H1_AGAIN) {
|
|
805
|
+
return
|
|
806
|
+
}
|
|
807
|
+
this.current[r.slot] = -1
|
|
808
|
+
this.release(r)
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/** A free request, or a new one. */
|
|
812
|
+
obtain(): HttpRequest {
|
|
813
|
+
const count: i32 = toI32(this.free.length)
|
|
814
|
+
if (count > 0) {
|
|
815
|
+
const at: i32 = this.free[count - 1]
|
|
816
|
+
this.free.pop()
|
|
817
|
+
return this.requests[at]
|
|
818
|
+
}
|
|
819
|
+
const r: HttpRequest = new HttpRequest(toI32(this.requests.length))
|
|
820
|
+
r.params = new Array<i32>(2 * this.router.maxParams + 2)
|
|
821
|
+
this.requests.push(r)
|
|
822
|
+
return r
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
release(r: HttpRequest): void {
|
|
826
|
+
if (r.state === HANDLER_FREE) {
|
|
827
|
+
return
|
|
828
|
+
}
|
|
829
|
+
r.state = HANDLER_FREE
|
|
830
|
+
this.free.push(r.index)
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/** Hands back the request `slot` was reading when the carrier refused it. */
|
|
834
|
+
releaseReading(slot: i32): void {
|
|
835
|
+
const at: i32 = this.current[slot]
|
|
836
|
+
if (at >= 0 && this.requests[at].state === HANDLER_READING) {
|
|
837
|
+
this.current[slot] = -1
|
|
838
|
+
this.release(this.requests[at])
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Closes `slot` and bumps its generation, so any answer still owed for it is
|
|
844
|
+
* dropped. A request the handler alone holds goes back now; one queued or
|
|
845
|
+
* taken goes back when `ready` passes it or the program answers it.
|
|
846
|
+
*/
|
|
847
|
+
closeSlot(slot: i32): void {
|
|
848
|
+
this.server.close(slot)
|
|
849
|
+
this.forget(slot)
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/** The bookkeeping of a slot the carrier has closed. */
|
|
853
|
+
forget(slot: i32): void {
|
|
854
|
+
this.open[slot] = false
|
|
855
|
+
this.slotGen[slot] = this.slotGen[slot] + 1
|
|
856
|
+
const at: i32 = this.current[slot]
|
|
857
|
+
this.current[slot] = -1
|
|
858
|
+
if (at >= 0) {
|
|
859
|
+
const r: HttpRequest = this.requests[at]
|
|
860
|
+
if (r.state === HANDLER_READING || r.state === HANDLER_PARKED || r.state === HANDLER_FLUSHING) {
|
|
861
|
+
this.release(r)
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/** Closes every connection whose request has been reading its body for longer than `bodyTimeout` at `now`. */
|
|
867
|
+
expireBodies(now: i64): void {
|
|
868
|
+
for (let s: i32 = 0; s < toI32(this.current.length) && s < toI32(this.open.length); s++) {
|
|
869
|
+
const at: i32 = this.current[s]
|
|
870
|
+
if (this.open[s] && at >= 0 && at < toI32(this.requests.length)) {
|
|
871
|
+
const r: HttpRequest = this.requests[at]
|
|
872
|
+
if (r.state === HANDLER_READING && now - r.since > this.bodyTimeout) {
|
|
873
|
+
this.closeSlot(s)
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/** Every slot the carrier's `expire` closed. */
|
|
880
|
+
sweep(): void {
|
|
881
|
+
for (let s: i32 = 0; s < toI32(this.open.length); s++) {
|
|
882
|
+
if (this.open[s] && !this.server.holds(s)) {
|
|
883
|
+
this.forget(s)
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// ---- Requests -------------------------------------------------------------------
|
|
889
|
+
|
|
890
|
+
/** Whether a whole request is waiting for `take`. Requests whose connection closed while queued are handed back here. */
|
|
891
|
+
ready(): boolean {
|
|
892
|
+
while (this.queueCount > 0) {
|
|
893
|
+
const r: HttpRequest = this.requests[this.queue[this.queueStart]]
|
|
894
|
+
if (this.live(r)) {
|
|
895
|
+
return true
|
|
896
|
+
}
|
|
897
|
+
this.queueStart = (this.queueStart + 1) % toI32(this.queue.length)
|
|
898
|
+
this.queueCount -= 1
|
|
899
|
+
this.release(r)
|
|
900
|
+
}
|
|
901
|
+
return false
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/** The next whole request, which the program now owes an answer. Panics when none is `ready`. */
|
|
905
|
+
take(): HttpRequest {
|
|
906
|
+
if (!this.ready()) {
|
|
907
|
+
panic("HttpHandler.take: no request is ready")
|
|
908
|
+
}
|
|
909
|
+
const r: HttpRequest = this.requests[this.queue[this.queueStart]]
|
|
910
|
+
this.queueStart = (this.queueStart + 1) % toI32(this.queue.length)
|
|
911
|
+
this.queueCount -= 1
|
|
912
|
+
r.state = HANDLER_HELD
|
|
913
|
+
return r
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/** Whether `r`'s connection is the one it was read on, still open. */
|
|
917
|
+
live(r: HttpRequest): boolean {
|
|
918
|
+
return this.server.holds(r.slot) && this.slotGen[r.slot] === r.gen
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/** How many requests the pool has made: the most ever in flight at once. */
|
|
922
|
+
pooled(): i32 {
|
|
923
|
+
return toI32(this.requests.length)
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/** How many connections are open. */
|
|
927
|
+
connections(): i32 {
|
|
928
|
+
return this.server.busy()
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// ---- Responses ------------------------------------------------------------------
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Starts `r`'s response: `status` (200 to 599), the fields `names[k]`:
|
|
935
|
+
* `values[k]`, and a body of `bodyLength` bytes or `HTTP1_CHUNKED`; then
|
|
936
|
+
* `write` the body and `end`. Answers `HANDLER_SENT`, or `HANDLER_DROPPED`
|
|
937
|
+
* when `r`'s connection has closed — every later `write` and `end` for it
|
|
938
|
+
* then does nothing, and `end` hands it back.
|
|
939
|
+
*/
|
|
940
|
+
start(r: HttpRequest, status: i32, names: u8[][], values: u8[][], bodyLength: i32): i32 {
|
|
941
|
+
if (r.state !== HANDLER_HELD) {
|
|
942
|
+
panic(`HttpHandler.start: the request is not one taken and unanswered (state ${r.state})`)
|
|
943
|
+
}
|
|
944
|
+
if (status < 200 || status > 599) {
|
|
945
|
+
panic(`HttpHandler.start: status ${status} is outside 200 to 599`)
|
|
946
|
+
}
|
|
947
|
+
if (bodyLength < HTTP1_CHUNKED) {
|
|
948
|
+
panic(`HttpHandler.start: body length ${bodyLength}`)
|
|
949
|
+
}
|
|
950
|
+
r.state = HANDLER_STREAMING
|
|
951
|
+
if (!this.live(r)) {
|
|
952
|
+
return HANDLER_DROPPED
|
|
953
|
+
}
|
|
954
|
+
const conn: Http1Connection = this.server.connection(r.slot)
|
|
955
|
+
const framing: i32 = status === 204 || status === 304 ? HTTP1_NO_BODY : bodyLength
|
|
956
|
+
const sent: i32 = conn.respond(status, names, values, framing)
|
|
957
|
+
if (sent === H1_AGAIN) {
|
|
958
|
+
// The output still holds more of the response before this one than a
|
|
959
|
+
// head can fit behind — only for a head near half the output, since a
|
|
960
|
+
// request waits for that much room before it is queued. Ending the
|
|
961
|
+
// connection is the one answer left.
|
|
962
|
+
this.closeSlot(r.slot)
|
|
963
|
+
return HANDLER_DROPPED
|
|
964
|
+
}
|
|
965
|
+
if (sent !== 0) {
|
|
966
|
+
panic(
|
|
967
|
+
`HttpHandler.start: the carrier refused the head (${sent}): a reserved field, or a name or value that is not a token or field text`
|
|
968
|
+
)
|
|
969
|
+
}
|
|
970
|
+
return HANDLER_SENT
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Writes `buf[off .. off + len)` of `r`'s body. What the carrier has no room
|
|
975
|
+
* for is copied and sent as room comes, so `buf` is the caller's again on
|
|
976
|
+
* return. Answers `HANDLER_SENT` or `HANDLER_DROPPED`.
|
|
977
|
+
*/
|
|
978
|
+
write(r: HttpRequest, buf: u8[], off: i32, len: i32): i32 {
|
|
979
|
+
if (r.state !== HANDLER_STREAMING) {
|
|
980
|
+
panic(`HttpHandler.write: the response is not started (state ${r.state})`)
|
|
981
|
+
}
|
|
982
|
+
if (off < 0 || len < 0 || off > toI32(buf.length) - len) {
|
|
983
|
+
panic(`HttpHandler.write: the window ${off} + ${len} is outside its ${buf.length} bytes`)
|
|
984
|
+
}
|
|
985
|
+
if (!this.live(r)) {
|
|
986
|
+
return HANDLER_DROPPED
|
|
987
|
+
}
|
|
988
|
+
const conn: Http1Connection = this.server.connection(r.slot)
|
|
989
|
+
// The carrier checks the length of what it takes; what is pending it has
|
|
990
|
+
// not seen, so the check counts it here.
|
|
991
|
+
const owed: i32 = conn.responseLeft - (r.pendingEnd - r.pendingStart)
|
|
992
|
+
if (conn.responseFraming >= 0 && !conn.responseEmpty && len > owed) {
|
|
993
|
+
panic(
|
|
994
|
+
`HttpHandler.write: ${len} byte(s) is more than the ${owed} the length the response declared leaves`
|
|
995
|
+
)
|
|
996
|
+
}
|
|
997
|
+
let taken: i32 = 0
|
|
998
|
+
if (r.pendingEnd === r.pendingStart) {
|
|
999
|
+
taken = conn.write(buf, off, len)
|
|
1000
|
+
if (taken === H1_AGAIN) {
|
|
1001
|
+
taken = 0
|
|
1002
|
+
} else if (taken < 0) {
|
|
1003
|
+
panic(
|
|
1004
|
+
`HttpHandler.write: the carrier refused the body (${taken}): more than the length the response declared`
|
|
1005
|
+
)
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (taken < len) {
|
|
1009
|
+
this.keep(r, buf, off + taken, len - taken)
|
|
1010
|
+
}
|
|
1011
|
+
this.drive(r.slot)
|
|
1012
|
+
return HANDLER_SENT
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/** Appends bytes to `r`'s pending buffer, growing it once to the largest response it has held. */
|
|
1016
|
+
keep(r: HttpRequest, buf: u8[], off: i32, len: i32): void {
|
|
1017
|
+
const live: i32 = r.pendingEnd - r.pendingStart
|
|
1018
|
+
if (toI32(r.pending.length) - r.pendingEnd < len) {
|
|
1019
|
+
if (r.pendingStart > 0 || toI32(r.pending.length) < live + len) {
|
|
1020
|
+
const target: u8[] =
|
|
1021
|
+
toI32(r.pending.length) < live + len
|
|
1022
|
+
? new Array<u8>(handlerGrown(toI32(r.pending.length), live + len))
|
|
1023
|
+
: r.pending
|
|
1024
|
+
copyBytes(target, HANDLER_ZERO, r.pending, r.pendingStart, live)
|
|
1025
|
+
r.pending = target
|
|
1026
|
+
r.pendingStart = 0
|
|
1027
|
+
r.pendingEnd = live
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
copyBytes(r.pending, r.pendingEnd, buf, off, len)
|
|
1031
|
+
r.pendingEnd += len
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Ends `r`'s response. `r` is the handler's again: it goes back once the
|
|
1036
|
+
* carrier has taken every byte, and the connection's next request is read.
|
|
1037
|
+
* Answers `HANDLER_SENT` or `HANDLER_DROPPED`.
|
|
1038
|
+
*/
|
|
1039
|
+
end(r: HttpRequest): i32 {
|
|
1040
|
+
if (r.state !== HANDLER_STREAMING) {
|
|
1041
|
+
panic(`HttpHandler.end: the response is not started (state ${r.state})`)
|
|
1042
|
+
}
|
|
1043
|
+
if (!this.live(r)) {
|
|
1044
|
+
this.release(r)
|
|
1045
|
+
return HANDLER_DROPPED
|
|
1046
|
+
}
|
|
1047
|
+
const slot: i32 = r.slot
|
|
1048
|
+
const conn: Http1Connection = this.server.connection(slot)
|
|
1049
|
+
const short: i32 = conn.responseLeft - (r.pendingEnd - r.pendingStart)
|
|
1050
|
+
if (conn.responseFraming >= 0 && short > 0 && !conn.responseEmpty) {
|
|
1051
|
+
panic(`HttpHandler.end: the body is ${short} byte(s) short of the length the response declared`)
|
|
1052
|
+
}
|
|
1053
|
+
this.finish(r, conn)
|
|
1054
|
+
this.drive(slot)
|
|
1055
|
+
return HANDLER_SENT
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/** Answers `r` with `status`, a `Content-Type` of `contentType` and the body `body[off .. off + len)`, in one call. */
|
|
1059
|
+
send(r: HttpRequest, status: i32, contentType: u8[], body: u8[], off: i32, len: i32): i32 {
|
|
1060
|
+
this.typeValue[0] = contentType
|
|
1061
|
+
return this.sendFields(r, status, this.typeName, this.typeValue, body, off, len)
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/** Answers `r` with `status`, the fields `names[k]`: `values[k]` and the body `body[off .. off + len)`, in one call. */
|
|
1065
|
+
sendFields(
|
|
1066
|
+
r: HttpRequest,
|
|
1067
|
+
status: i32,
|
|
1068
|
+
names: u8[][],
|
|
1069
|
+
values: u8[][],
|
|
1070
|
+
body: u8[],
|
|
1071
|
+
off: i32,
|
|
1072
|
+
len: i32
|
|
1073
|
+
): i32 {
|
|
1074
|
+
const started: i32 = this.start(r, status, names, values, len)
|
|
1075
|
+
if (started === HANDLER_DROPPED) {
|
|
1076
|
+
this.release(r)
|
|
1077
|
+
return HANDLER_DROPPED
|
|
1078
|
+
}
|
|
1079
|
+
if (len > 0 && status !== 204 && status !== 304) {
|
|
1080
|
+
this.write(r, body, off, len)
|
|
1081
|
+
}
|
|
1082
|
+
return this.end(r)
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/** Answers `r` with `status` and no body: a 204, or a status whose meaning is all there is to say. */
|
|
1086
|
+
empty(r: HttpRequest, status: i32): i32 {
|
|
1087
|
+
return this.sendFields(r, status, this.noFields, this.noFields, this.scratch, HANDLER_ZERO, HANDLER_ZERO)
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* The JSON writer, reset onto the handler's scratch buffer: write one value
|
|
1092
|
+
* with it, then `sendJson`. One JSON body at a time, since every request
|
|
1093
|
+
* shares the buffer.
|
|
1094
|
+
*/
|
|
1095
|
+
json(): JsonWriter {
|
|
1096
|
+
this.writer.reset(this.scratch, HANDLER_ZERO)
|
|
1097
|
+
this.jsonBegun = true
|
|
1098
|
+
return this.writer
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* Answers `r` with `status` and the value `json`'s writer holds, as
|
|
1103
|
+
* `application/json`. A value the writer refused or did not finish — it
|
|
1104
|
+
* outgrew `jsonBytes`, say — is answered as a 500 problem instead.
|
|
1105
|
+
*/
|
|
1106
|
+
sendJson(r: HttpRequest, status: i32): i32 {
|
|
1107
|
+
if (!this.jsonBegun) {
|
|
1108
|
+
panic("HttpHandler.sendJson: no value was begun with json()")
|
|
1109
|
+
}
|
|
1110
|
+
this.jsonBegun = false
|
|
1111
|
+
if (!this.writer.complete()) {
|
|
1112
|
+
return this.problem(r, toI32(500), "Internal Server Error", "the response did not fit its buffer")
|
|
1113
|
+
}
|
|
1114
|
+
return this.send(r, status, this.jsonType, this.scratch, HANDLER_ZERO, this.writer.end)
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* Answers `r` with an RFC 9457 problem: `application/problem+json` with
|
|
1119
|
+
* `type` `about:blank`, `title` (the status's reason, for that type),
|
|
1120
|
+
* `status` and, when not empty, `detail`, a sentence for a person about
|
|
1121
|
+
* this occurrence. Nothing of the request is echoed, so a detail is the
|
|
1122
|
+
* program's own words.
|
|
1123
|
+
*/
|
|
1124
|
+
problem(r: HttpRequest, status: i32, title: string, detail: string): i32 {
|
|
1125
|
+
const length: i32 = this.writeProblem(status, title, detail)
|
|
1126
|
+
return this.send(r, status, this.problemType, this.scratch, HANDLER_ZERO, length)
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* Writes a problem object into the scratch buffer and answers its length.
|
|
1131
|
+
* One too large for it is written without its detail, and then without its
|
|
1132
|
+
* title, which leaves room enough in the least `jsonBytes`.
|
|
1133
|
+
*/
|
|
1134
|
+
writeProblem(status: i32, title: string, detail: string): i32 {
|
|
1135
|
+
let length: i32 = this.writeProblemObject(status, title, detail)
|
|
1136
|
+
if (length < 0) {
|
|
1137
|
+
length = this.writeProblemObject(status, title, "")
|
|
1138
|
+
}
|
|
1139
|
+
if (length < 0) {
|
|
1140
|
+
length = this.writeProblemObject(status, "", "")
|
|
1141
|
+
}
|
|
1142
|
+
this.jsonBegun = false
|
|
1143
|
+
return length
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/** `writeProblem`'s one try: the object's length, or -1 when it did not fit. */
|
|
1147
|
+
writeProblemObject(status: i32, title: string, detail: string): i32 {
|
|
1148
|
+
const w: JsonWriter = this.json()
|
|
1149
|
+
w.beginObject()
|
|
1150
|
+
w.key("type")
|
|
1151
|
+
w.string("about:blank")
|
|
1152
|
+
if (toI32(title.length) > 0) {
|
|
1153
|
+
w.key("title")
|
|
1154
|
+
w.string(title)
|
|
1155
|
+
}
|
|
1156
|
+
w.key("status")
|
|
1157
|
+
w.i32(status)
|
|
1158
|
+
if (toI32(detail.length) > 0) {
|
|
1159
|
+
w.key("detail")
|
|
1160
|
+
w.string(detail)
|
|
1161
|
+
}
|
|
1162
|
+
w.endObject()
|
|
1163
|
+
return w.complete() ? w.end : -1
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Answers a request the router refused, by its `route`: 405 with an `Allow`
|
|
1168
|
+
* field naming the methods the path has (RFC 9110 §15.5.6), and 404 for
|
|
1169
|
+
* anything else. A parameter's malformed escape is the program's to refuse,
|
|
1170
|
+
* when it decodes the parameter.
|
|
1171
|
+
*/
|
|
1172
|
+
refuse(r: HttpRequest): i32 {
|
|
1173
|
+
if (r.route === ROUTER_METHOD_NOT_ALLOWED) {
|
|
1174
|
+
const length: i32 = this.writeProblem(toI32(405), "Method Not Allowed", "")
|
|
1175
|
+
this.allowValues[1] = this.allowFor(r)
|
|
1176
|
+
return this.sendFields(
|
|
1177
|
+
r,
|
|
1178
|
+
toI32(405),
|
|
1179
|
+
this.allowNames,
|
|
1180
|
+
this.allowValues,
|
|
1181
|
+
this.scratch,
|
|
1182
|
+
HANDLER_ZERO,
|
|
1183
|
+
length
|
|
1184
|
+
)
|
|
1185
|
+
}
|
|
1186
|
+
return this.problem(r, toI32(404), "Not Found", "")
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
/**
|
|
1190
|
+
* The `Allow` value for `r`'s path: each method a route of it takes, and
|
|
1191
|
+
* HEAD wherever GET is, since a GET route answers HEAD too. Made once for
|
|
1192
|
+
* each set of methods.
|
|
1193
|
+
*/
|
|
1194
|
+
allowFor(r: HttpRequest): u8[] {
|
|
1195
|
+
const router: Router = this.router
|
|
1196
|
+
const methods: i32 = toI32(router.methodStart.length)
|
|
1197
|
+
let mask: i64 = 0
|
|
1198
|
+
for (let m: i32 = 0; m < methods; m++) {
|
|
1199
|
+
const route: i32 = router.match(
|
|
1200
|
+
router.methodBytes,
|
|
1201
|
+
router.methodStart[m],
|
|
1202
|
+
router.methodEnd[m],
|
|
1203
|
+
r.head,
|
|
1204
|
+
r.targetStart,
|
|
1205
|
+
r.targetEnd,
|
|
1206
|
+
this.probe
|
|
1207
|
+
)
|
|
1208
|
+
if (route >= 0) {
|
|
1209
|
+
mask = mask | (toI64(1) << toI64(m))
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
for (let k: i32 = 0; k < toI32(this.allowMasks.length); k++) {
|
|
1213
|
+
if (this.allowMasks[k] === mask) {
|
|
1214
|
+
return this.allowBytes[k]
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
let get: boolean = false
|
|
1218
|
+
let head: boolean = false
|
|
1219
|
+
const value: u8[] = []
|
|
1220
|
+
for (let m: i32 = 0; m < methods; m++) {
|
|
1221
|
+
if ((mask & (toI64(1) << toI64(m))) !== toI64(0)) {
|
|
1222
|
+
const start: i32 = router.methodStart[m]
|
|
1223
|
+
const end: i32 = router.methodEnd[m]
|
|
1224
|
+
get = get || handlerSpanIs(router.methodBytes, start, end, "GET")
|
|
1225
|
+
head = head || handlerSpanIs(router.methodBytes, start, end, "HEAD")
|
|
1226
|
+
handlerAllowAppend(value, router.methodBytes, start, end)
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
if (get && !head) {
|
|
1230
|
+
const name: u8[] = httpFieldBytes("HEAD")
|
|
1231
|
+
handlerAllowAppend(value, name, HANDLER_ZERO, toI32(name.length))
|
|
1232
|
+
}
|
|
1233
|
+
this.allowMasks.push(mask)
|
|
1234
|
+
this.allowBytes.push(value)
|
|
1235
|
+
return value
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/** Appends `bytes[start .. end)` to an `Allow` value, after a `, ` if it is not the first. */
|
|
1240
|
+
const handlerAllowAppend = (value: u8[], bytes: u8[], start: i32, end: i32): void => {
|
|
1241
|
+
if (value.length > 0) {
|
|
1242
|
+
value.push(toU8(44))
|
|
1243
|
+
value.push(toU8(32))
|
|
1244
|
+
}
|
|
1245
|
+
for (let k: i32 = start; k < end; k++) {
|
|
1246
|
+
if (k >= 0 && k < toI32(bytes.length)) {
|
|
1247
|
+
value.push(bytes[k])
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/** A buffer size of at least `need`: `have` doubled until it is, starting from 64. */
|
|
1253
|
+
const handlerGrown = (have: i32, need: i32): i32 => {
|
|
1254
|
+
let size: i32 = have < 64 ? 64 : have
|
|
1255
|
+
while (size < need && size < 1073741824) {
|
|
1256
|
+
size = size * 2
|
|
1257
|
+
}
|
|
1258
|
+
return size < need ? need : size
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/** Whether `bytes[start .. end)` is exactly `text`. */
|
|
1262
|
+
const handlerSpanIs = (bytes: u8[], start: i32, end: i32, text: string): boolean => {
|
|
1263
|
+
const n: i32 = toI32(text.length)
|
|
1264
|
+
if (end - start !== n || start < 0 || end > toI32(bytes.length)) {
|
|
1265
|
+
return false
|
|
1266
|
+
}
|
|
1267
|
+
for (let k: i32 = 0; k < n; k++) {
|
|
1268
|
+
if (toI32(bytes[start + k]) !== toI32(text.charCodeAt(k))) {
|
|
1269
|
+
return false
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return true
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/** `bytes[start .. end)` as a decimal number that is not negative and fits an `i32`, or -1. */
|
|
1276
|
+
const handlerDecimal = (bytes: u8[], start: i32, end: i32): i32 => {
|
|
1277
|
+
if (start < 0 || end <= start || end - start > 10 || end > toI32(bytes.length)) {
|
|
1278
|
+
return -1
|
|
1279
|
+
}
|
|
1280
|
+
let n: i64 = 0
|
|
1281
|
+
for (let k: i32 = start; k < end && k < toI32(bytes.length); k++) {
|
|
1282
|
+
const digit: i32 = toI32(bytes[k]) - 48
|
|
1283
|
+
if (digit < 0 || digit > 9) {
|
|
1284
|
+
return -1
|
|
1285
|
+
}
|
|
1286
|
+
n = n * toI64(10) + toI64(digit)
|
|
1287
|
+
}
|
|
1288
|
+
return n > toI64(2147483647) ? -1 : toI32(n)
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* The byte the form encoding spells at `bytes[at ..)`, before `end`, and how
|
|
1293
|
+
* many bytes it took, packed as `byte | taken << 8`; -1 for a malformed escape.
|
|
1294
|
+
*/
|
|
1295
|
+
const handlerFormByte = (bytes: u8[], at: i32, end: i32): i32 => {
|
|
1296
|
+
const c: i32 = toI32(bytes[at])
|
|
1297
|
+
if (c === 43) {
|
|
1298
|
+
return 32 | HANDLER_TOOK_ONE
|
|
1299
|
+
}
|
|
1300
|
+
if (c !== 37) {
|
|
1301
|
+
return c | HANDLER_TOOK_ONE
|
|
1302
|
+
}
|
|
1303
|
+
if (at + 2 >= end) {
|
|
1304
|
+
return -1
|
|
1305
|
+
}
|
|
1306
|
+
const hi: i32 = http1HexValue(toI32(bytes[at + 1]))
|
|
1307
|
+
const lo: i32 = http1HexValue(toI32(bytes[at + 2]))
|
|
1308
|
+
if (hi < 0 || lo < 0) {
|
|
1309
|
+
return -1
|
|
1310
|
+
}
|
|
1311
|
+
return (hi * 16 + lo) | HANDLER_TOOK_THREE
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/** Whether `bytes[start .. end)`, form-decoded, is exactly `name`; a malformed escape matches nothing. */
|
|
1315
|
+
const handlerFormIs = (bytes: u8[], start: i32, end: i32, name: string): boolean => {
|
|
1316
|
+
const n: i32 = toI32(name.length)
|
|
1317
|
+
let at: i32 = start
|
|
1318
|
+
let k: i32 = 0
|
|
1319
|
+
while (at < end) {
|
|
1320
|
+
const packed: i32 = handlerFormByte(bytes, at, end)
|
|
1321
|
+
if (packed < 0 || k >= n || (packed & 255) !== toI32(name.charCodeAt(k))) {
|
|
1322
|
+
return false
|
|
1323
|
+
}
|
|
1324
|
+
at += packed >> 8
|
|
1325
|
+
k += 1
|
|
1326
|
+
}
|
|
1327
|
+
return k === n
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* `bytes[start .. end)` form-decoded into `out` from 0: its length,
|
|
1332
|
+
* `ROUTER_BAD_ESCAPE`, or `ROUTER_NO_ROOM`. `%00` decodes like any byte, as a
|
|
1333
|
+
* route parameter's does.
|
|
1334
|
+
*/
|
|
1335
|
+
const handlerFormDecode = (bytes: u8[], start: i32, end: i32, out: u8[]): i32 => {
|
|
1336
|
+
const length: i32 = percentDecode(
|
|
1337
|
+
bytes,
|
|
1338
|
+
start,
|
|
1339
|
+
end - start,
|
|
1340
|
+
out,
|
|
1341
|
+
HANDLER_ZERO,
|
|
1342
|
+
URL_DECODE_FORM | URL_DECODE_ALLOW_NUL
|
|
1343
|
+
)
|
|
1344
|
+
if (length === URL_DECODE_ERR_SPACE) {
|
|
1345
|
+
return ROUTER_NO_ROOM
|
|
1346
|
+
}
|
|
1347
|
+
return length < 0 ? ROUTER_BAD_ESCAPE : length
|
|
1348
|
+
}
|