@amritk/nish-x86_64-linux 0.18.0 → 0.21.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 +58 -28
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +3 -2
- package/runtime/browser.d.ts +14 -0
- package/runtime/browser.mjs +88 -0
- package/runtime/core/arrays.d.ts +18 -0
- package/runtime/core/arrays.mjs +125 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +70 -0
- package/runtime/core/host.mjs +311 -0
- package/runtime/core/numbers.d.ts +53 -0
- package/runtime/core/numbers.mjs +316 -0
- package/runtime/core/result.d.ts +41 -0
- package/runtime/core/result.mjs +127 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +99 -5
- package/runtime/nish.h +289 -19
- package/runtime/nish.mjs +39 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +8 -7
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +214 -16
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +235 -11
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +5 -14
- package/runtime/runtime.c +257 -17
- package/runtime/shim.mjs +6 -915
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +437 -28
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +108 -58
- package/std/base64.ts +116 -0
- package/std/bytes.ts +419 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +382 -54
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +20 -131
- package/std/json-tokenizer.ts +4237 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1182 -0
- package/std/json.ts +1202 -34
- package/std/math.ts +608 -0
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +501 -62
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +261 -43
- package/std/net/http3.ts +187 -28
- package/std/net/postgres.ts +1875 -0
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +108 -11
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/threads.ts +438 -14
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
package/std/net/http1-server.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* }
|
|
23
23
|
* wants = server.flush(slot);
|
|
24
24
|
* if ((wants & H1_DONE) !== 0) { server.close(slot); }
|
|
25
|
-
* else { pollModify(loop, server.fd(slot), wants & 3, slot); }
|
|
25
|
+
* else if (server.rearm(slot, wants)) { pollModify(loop, server.fd(slot), wants & 3, slot); }
|
|
26
26
|
*
|
|
27
27
|
* **Events.** `next()` answers `H1_REQUEST` (a request's head: the parser's
|
|
28
28
|
* spans and `...Is` methods read it), `H1_BODY` (some of its body, de-chunked,
|
|
@@ -65,6 +65,13 @@
|
|
|
65
65
|
* §15.4.5). A 1xx before the final response (100 Continue) is allowed; 101 is
|
|
66
66
|
* `acceptWebSocket`'s.
|
|
67
67
|
*
|
|
68
|
+
* **The Date.** Every final response, an error included, carries a `Date`
|
|
69
|
+
* field (RFC 9110 §6.6.1) from `HttpDate`, which formats the IMF-fixdate at
|
|
70
|
+
* most once a second; a 1xx carries none. The pool's slots share one, so a
|
|
71
|
+
* busy server formats a second once, not once a slot. A program that sets its
|
|
72
|
+
* own `date` field sends that one instead, so a response has one `Date`.
|
|
73
|
+
* `date.pin(unixMs)` fixes it, for a test that compares bytes.
|
|
74
|
+
*
|
|
68
75
|
* **Refusals.** Every refusal of `nish/net/http1`'s parser — the smuggling
|
|
69
76
|
* shapes, a `Content-Length` with a `Transfer-Encoding`, two that disagree,
|
|
70
77
|
* a malformed chunk size — is answered with its status (400, 413, 414, 431,
|
|
@@ -81,11 +88,23 @@
|
|
|
81
88
|
*
|
|
82
89
|
* **Caps are start-up numbers**, all in `Http1Config`: the request target,
|
|
83
90
|
* the header section and its field count, a request body, the chunk handed
|
|
84
|
-
* over, the output buffer, a WebSocket message
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
91
|
+
* over, the output buffer, a WebSocket message, the idle timeout and the
|
|
92
|
+
* deadline for a whole request head (`headTimeout`, against slowloris); the
|
|
93
|
+
* slots are the pool's size. `Http1Server` builds a slot's connection on the
|
|
94
|
+
* first accept into it and keeps it, so a pool sized for its peak costs at
|
|
95
|
+
* start-up only what does not depend on the size, and holds at most `size`
|
|
96
|
+
* connections once the peak has come; `Http1TlsServer` does the same for a
|
|
97
|
+
* slot's connection and, through `TlsTcpServer`, its TLS state. Every buffer
|
|
98
|
+
* is allocated with its slot or grown to its cap on first use and kept, so a
|
|
99
|
+
* warmed slot serves request after request with `Arena.used()` flat.
|
|
100
|
+
* Accepting a WebSocket allocates (the accept key), once a connection.
|
|
101
|
+
*
|
|
102
|
+
* **Output is borrowed.** The pools' slots share an `Http1OutputPool`: a
|
|
103
|
+
* connection takes an `outputSize` buffer when it first needs room to write
|
|
104
|
+
* and gives it back once its carrier has sent everything, so the output
|
|
105
|
+
* memory a pool holds follows the connections the socket is holding back,
|
|
106
|
+
* not its size. A pool that ever had `n` connections writing at once keeps
|
|
107
|
+
* `n` buffers and allocates no more.
|
|
89
108
|
*
|
|
90
109
|
* Private helpers share the importing program's flat symbol namespace
|
|
91
110
|
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
@@ -128,6 +147,7 @@ import {
|
|
|
128
147
|
websocketWriteFrame,
|
|
129
148
|
} from "nish/net/websocket"
|
|
130
149
|
import { TlsServerConfig } from "nish/net/tls"
|
|
150
|
+
import { HTTP_DATE_LAST_SECOND, HTTP_DATE_SIZE, httpDateWrite } from "nish/net/http-fields"
|
|
131
151
|
import { TLS_RECORD_DONE, TLS_RECORD_STATE_OPEN, TLS_RECORD_WANT_WRITE } from "nish/net/tls/record-server"
|
|
132
152
|
import { TlsTcpServer } from "nish/net/tls-tcp"
|
|
133
153
|
|
|
@@ -228,6 +248,82 @@ const http1ServerCopy = (out: u8[], at: i32, src: u8[], off: i32, n: i32): i32 =
|
|
|
228
248
|
return at + n
|
|
229
249
|
}
|
|
230
250
|
|
|
251
|
+
/**
|
|
252
|
+
* The `Date` field an origin server with a clock sends on every final
|
|
253
|
+
* response (RFC 9110 §6.6.1): the IMF-fixdate of now, in 29 bytes the object
|
|
254
|
+
* owns and rewrites in place, at most once a second.
|
|
255
|
+
*
|
|
256
|
+
* It reads the wall clock, `Date.now()`, which a wasm32 build has no host for,
|
|
257
|
+
* so it lives here, in a module that is native already, while the formatting
|
|
258
|
+
* it calls, `httpDateWrite`, stays in `nish/net/http-fields`, which compiles
|
|
259
|
+
* for wasm32 (the split `nish/crypto/x509` and `x509-random` make). `pin`
|
|
260
|
+
* replaces the clock with one instant, which is how a test asserts a response
|
|
261
|
+
* byte for byte.
|
|
262
|
+
*/
|
|
263
|
+
export class HttpDate {
|
|
264
|
+
/** The IMF-fixdate of `second`: what `current` answers, made once. */
|
|
265
|
+
bytes: u8[]
|
|
266
|
+
/** The second `bytes` holds, since 1970; -1 before the first `current`. */
|
|
267
|
+
second: i64 = -1
|
|
268
|
+
/** The instant `pin` fixed, in milliseconds since 1970, while `pinned`. */
|
|
269
|
+
pinnedMs: i64 = 0
|
|
270
|
+
/** How many times `bytes` has been rewritten. */
|
|
271
|
+
rewrites: i64 = 0
|
|
272
|
+
pinned: boolean = false
|
|
273
|
+
|
|
274
|
+
/** A date on the wall clock, with nothing formatted yet. */
|
|
275
|
+
constructor() {
|
|
276
|
+
this.bytes = new Array<u8>(HTTP_DATE_SIZE)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* The 29 bytes for now, which are rewritten only when the second differs
|
|
281
|
+
* from the one they hold — earlier as well as later, since a corrected clock
|
|
282
|
+
* steps back. It allocates nothing, and the array is the same one each time,
|
|
283
|
+
* so a caller copies it out rather than keeping it. A wall clock before 1970
|
|
284
|
+
* or past 9999 answers the nearer of the two ends.
|
|
285
|
+
*/
|
|
286
|
+
current(): u8[] {
|
|
287
|
+
const ms: i64 = this.pinned ? this.pinnedMs : toI64(Date.now())
|
|
288
|
+
let second: i64 = ms / 1000
|
|
289
|
+
if (ms < 0) {
|
|
290
|
+
second = 0
|
|
291
|
+
} else if (second > HTTP_DATE_LAST_SECOND) {
|
|
292
|
+
second = HTTP_DATE_LAST_SECOND
|
|
293
|
+
}
|
|
294
|
+
if (second !== this.second) {
|
|
295
|
+
httpDateWrite(this.bytes, 0, second)
|
|
296
|
+
this.second = second
|
|
297
|
+
this.rewrites += 1
|
|
298
|
+
}
|
|
299
|
+
return this.bytes
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Makes `current` answer `unixMs`, milliseconds since 1970, until `unpin`.
|
|
304
|
+
* False, and nothing changed, for an instant before 1970-01-01T00:00:00Z or
|
|
305
|
+
* after 9999-12-31T23:59:59.999Z, which an IMF-fixdate cannot name.
|
|
306
|
+
*/
|
|
307
|
+
pin(unixMs: i64): boolean {
|
|
308
|
+
if (unixMs < 0 || unixMs / 1000 > HTTP_DATE_LAST_SECOND) {
|
|
309
|
+
return false
|
|
310
|
+
}
|
|
311
|
+
this.pinnedMs = unixMs
|
|
312
|
+
this.pinned = true
|
|
313
|
+
return true
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Puts `current` back on the wall clock. */
|
|
317
|
+
unpin(): void {
|
|
318
|
+
this.pinned = false
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** How many times the bytes have been rewritten: one per second that `current` was asked for, at most. */
|
|
322
|
+
formats(): i64 {
|
|
323
|
+
return this.rewrites
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
231
327
|
/**
|
|
232
328
|
* The caps of every connection made from it, fixed when the connection is
|
|
233
329
|
* made. Each is a number the program sets once, at start-up.
|
|
@@ -249,6 +345,13 @@ export class Http1Config {
|
|
|
249
345
|
maxMessage: i32 = 1048576
|
|
250
346
|
/** Milliseconds without a byte read or written after which `expire` closes a slot. */
|
|
251
347
|
idleTimeout: i32 = 60000
|
|
348
|
+
/**
|
|
349
|
+
* Milliseconds a request head may take, from its first byte to its blank
|
|
350
|
+
* line, before `expire` closes the slot however steadily its bytes come. The
|
|
351
|
+
* idle timeout alone restarts on every byte, so a client that sends one byte
|
|
352
|
+
* of its head each `idleTimeout` would hold its slot for good (slowloris).
|
|
353
|
+
*/
|
|
354
|
+
headTimeout: i32 = 30000
|
|
252
355
|
}
|
|
253
356
|
|
|
254
357
|
/** The reason phrase a status is sent with; RFC 9112 §4 lets a client ignore it. */
|
|
@@ -311,6 +414,44 @@ const http1ServerReserved = (name: u8[]): boolean =>
|
|
|
311
414
|
http1BytesAre(name, "transfer-encoding") ||
|
|
312
415
|
http1BytesAre(name, "content-length")
|
|
313
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Output buffers shared by the connections of one pool, each `size` bytes. A
|
|
419
|
+
* connection borrows one when it has something to write and gives it back
|
|
420
|
+
* once its carrier has sent everything, so a pool's output memory follows the
|
|
421
|
+
* connections the socket is holding back, not the slots: Go's `net/http`
|
|
422
|
+
* pools its writers the same way, and uWebSockets shares one per loop. A
|
|
423
|
+
* buffer is made only when none is free, so the pool grows to the most
|
|
424
|
+
* connections ever writing at once and then allocates nothing.
|
|
425
|
+
*/
|
|
426
|
+
export class Http1OutputPool {
|
|
427
|
+
// Widest first, so the layout packs without padding.
|
|
428
|
+
free: u8[][]
|
|
429
|
+
size: i32 = 0
|
|
430
|
+
/** How many buffers the pool has made. */
|
|
431
|
+
made: i32 = 0
|
|
432
|
+
|
|
433
|
+
constructor(size: i32) {
|
|
434
|
+
this.size = size
|
|
435
|
+
this.free = []
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** A buffer of `size` bytes: the one given back last, or a new one. */
|
|
439
|
+
take(): u8[] {
|
|
440
|
+
if (this.free.length > 0) {
|
|
441
|
+
const buf: u8[] = this.free[this.free.length - 1]
|
|
442
|
+
this.free.pop()
|
|
443
|
+
return buf
|
|
444
|
+
}
|
|
445
|
+
this.made = this.made + 1
|
|
446
|
+
return new Array<u8>(this.size)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** Takes back a buffer `take` gave. */
|
|
450
|
+
give(buf: u8[]): void {
|
|
451
|
+
this.free.push(buf)
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
314
455
|
/**
|
|
315
456
|
* One connection's HTTP/1.1 server, sans-IO: bytes in through `feed`, events
|
|
316
457
|
* out of `next`, and the response the program writes in
|
|
@@ -320,12 +461,27 @@ export class Http1Connection {
|
|
|
320
461
|
// References, then 64-bit fields, then 32-bit ones, then flags: the order
|
|
321
462
|
// the layout packs without padding.
|
|
322
463
|
config: Http1Config
|
|
464
|
+
/**
|
|
465
|
+
* The `Date` every final response carries. The connection makes its own;
|
|
466
|
+
* `Http1Server` and `Http1TlsServer` put theirs here, so that every slot of a
|
|
467
|
+
* pool shares one and the pool formats at most once a second. A program may
|
|
468
|
+
* put another, to share it or to pin its clock.
|
|
469
|
+
*/
|
|
470
|
+
date: HttpDate
|
|
323
471
|
/** The request parser: its head (`methodIs`, `targetIs`, `header`, spans) is the last `H1_REQUEST`'s. */
|
|
324
472
|
parser: Http1Parser
|
|
325
473
|
/** The frame decoder, once the slot has switched to WebSocket. */
|
|
326
474
|
ws: WsDecoder
|
|
327
|
-
/**
|
|
475
|
+
/**
|
|
476
|
+
* Bytes to send are `output[outputStart .. outputEnd)`. Empty until the
|
|
477
|
+
* connection first needs room to write: then its own buffer, made once, or
|
|
478
|
+
* one borrowed from `pool` and given back by `release` once sent.
|
|
479
|
+
*/
|
|
328
480
|
output: u8[]
|
|
481
|
+
/** The empty array `output` is while the connection holds no buffer. */
|
|
482
|
+
noOutput: u8[]
|
|
483
|
+
/** Where `output` is borrowed from, for a connection of a pool; null for one that keeps its own. */
|
|
484
|
+
pool: Http1OutputPool | null
|
|
329
485
|
/** `H1_BODY`'s and `H1_WS_MESSAGE`'s bytes. */
|
|
330
486
|
data: u8[]
|
|
331
487
|
/** A ping's payload, kept until its pong fits. */
|
|
@@ -334,6 +490,8 @@ export class Http1Connection {
|
|
|
334
490
|
noFields: u8[][]
|
|
335
491
|
/** When the carrier last moved a byte for the connection, in `monotonicNanos`. */
|
|
336
492
|
lastActive: i64 = 0
|
|
493
|
+
/** When the head being read began to arrive, in `monotonicNanos`; meaningful while `headTimed`. */
|
|
494
|
+
headSince: i64 = 0
|
|
337
495
|
outputStart: i32 = 0
|
|
338
496
|
outputEnd: i32 = 0
|
|
339
497
|
dataStart: i32 = 0
|
|
@@ -369,6 +527,8 @@ export class Http1Connection {
|
|
|
369
527
|
pongPending: boolean = false
|
|
370
528
|
/** The peer's byte stream has ended. */
|
|
371
529
|
inputEnded: boolean = false
|
|
530
|
+
/** A head has begun to arrive and is not read whole yet: `headSince` is its deadline's start. */
|
|
531
|
+
headTimed: boolean = false
|
|
372
532
|
|
|
373
533
|
/** A connection under `config`. A cap outside what the connection can work with panics. */
|
|
374
534
|
constructor(config: Http1Config) {
|
|
@@ -384,6 +544,7 @@ export class Http1Connection {
|
|
|
384
544
|
http1ServerCheckCap("outputSize", config.outputSize, output, big)
|
|
385
545
|
http1ServerCheckCap("maxMessage", config.maxMessage, H1_ZERO, big - H1_FRAME_HEADER)
|
|
386
546
|
http1ServerCheckCap("idleTimeout", config.idleTimeout, one, big)
|
|
547
|
+
http1ServerCheckCap("headTimeout", config.headTimeout, one, big)
|
|
387
548
|
// The buffers the peer's bytes fill grow by doubling and stop at 2^30
|
|
388
549
|
// (`Http1Parser`'s input and head, `WsDecoder`'s input), so a cap whose
|
|
389
550
|
// buffer could need more is refused here rather than reached by a peer:
|
|
@@ -396,12 +557,15 @@ export class Http1Connection {
|
|
|
396
557
|
big - H1_LINE_OVERHEAD
|
|
397
558
|
)
|
|
398
559
|
this.config = config
|
|
560
|
+
this.date = new HttpDate()
|
|
399
561
|
this.parser = new Http1Parser(config.maxTarget, config.maxHeaderBytes, config.maxHeaders, config.maxBody)
|
|
400
562
|
this.parser.keepText = false
|
|
401
563
|
this.parser.maxChunk = config.chunkSize
|
|
402
564
|
this.ws = new WsDecoder(true, config.maxMessage)
|
|
403
|
-
this.
|
|
404
|
-
this.
|
|
565
|
+
this.noOutput = []
|
|
566
|
+
this.output = this.noOutput
|
|
567
|
+
this.pool = null
|
|
568
|
+
this.data = this.noOutput
|
|
405
569
|
this.pong = new Array<u8>(WS_MAX_CONTROL)
|
|
406
570
|
this.noFields = []
|
|
407
571
|
// A line must fit whole before the parser can read it, so the buffer
|
|
@@ -423,7 +587,7 @@ export class Http1Connection {
|
|
|
423
587
|
this.ws.reset()
|
|
424
588
|
this.outputStart = 0
|
|
425
589
|
this.outputEnd = 0
|
|
426
|
-
this.data = this.
|
|
590
|
+
this.data = this.noOutput
|
|
427
591
|
this.dataStart = 0
|
|
428
592
|
this.dataLength = 0
|
|
429
593
|
this.opcode = 0
|
|
@@ -441,6 +605,9 @@ export class Http1Connection {
|
|
|
441
605
|
this.writeHeld = false
|
|
442
606
|
this.pongPending = false
|
|
443
607
|
this.inputEnded = false
|
|
608
|
+
this.headTimed = false
|
|
609
|
+
this.headSince = 0
|
|
610
|
+
this.release()
|
|
444
611
|
}
|
|
445
612
|
|
|
446
613
|
// ---- Bytes in and out ---------------------------------------------------------
|
|
@@ -492,6 +659,44 @@ export class Http1Connection {
|
|
|
492
659
|
this.outputEnd = 0
|
|
493
660
|
this.pongPending = false
|
|
494
661
|
this.writeHeld = false
|
|
662
|
+
this.release()
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Borrows `output` from the pool between connections of one, for a
|
|
667
|
+
* connection that keeps its own makes it the first time: before anything
|
|
668
|
+
* is written into it, which is why `room` calls it. It allocates only when
|
|
669
|
+
* the pool has no free buffer, or for a connection's own, once.
|
|
670
|
+
*/
|
|
671
|
+
acquire(): void {
|
|
672
|
+
if (toI32(this.output.length) > 0) {
|
|
673
|
+
return
|
|
674
|
+
}
|
|
675
|
+
const pool: Http1OutputPool | null = this.pool
|
|
676
|
+
this.output = pool !== null ? pool.take() : new Array<u8>(this.config.outputSize)
|
|
677
|
+
this.outputStart = 0
|
|
678
|
+
this.outputEnd = 0
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Gives `output` back to the pool once nothing in it is waiting: no bytes
|
|
683
|
+
* to send, no write held back for room. The carrier calls it after each
|
|
684
|
+
* send; a connection that keeps its own buffer keeps it.
|
|
685
|
+
*/
|
|
686
|
+
release(): void {
|
|
687
|
+
const pool: Http1OutputPool | null = this.pool
|
|
688
|
+
if (
|
|
689
|
+
pool === null ||
|
|
690
|
+
toI32(this.output.length) === 0 ||
|
|
691
|
+
this.outputEnd > this.outputStart ||
|
|
692
|
+
this.writeHeld
|
|
693
|
+
) {
|
|
694
|
+
return
|
|
695
|
+
}
|
|
696
|
+
pool.give(this.output)
|
|
697
|
+
this.output = this.noOutput
|
|
698
|
+
this.outputStart = 0
|
|
699
|
+
this.outputEnd = 0
|
|
495
700
|
}
|
|
496
701
|
|
|
497
702
|
/** Marks the first `n` bytes of the output sent. */
|
|
@@ -518,6 +723,7 @@ export class Http1Connection {
|
|
|
518
723
|
|
|
519
724
|
/** The room at the end of the output, once what was sent is moved out of the way. */
|
|
520
725
|
room(): i32 {
|
|
726
|
+
this.acquire()
|
|
521
727
|
const output: u8[] = this.output
|
|
522
728
|
const live: i32 = this.outputEnd - this.outputStart
|
|
523
729
|
if (this.outputStart > 0) {
|
|
@@ -550,7 +756,7 @@ export class Http1Connection {
|
|
|
550
756
|
if (this.writeHeld) {
|
|
551
757
|
// Half the output free before the program is asked to write again, so
|
|
552
758
|
// that a slow reader is not answered one small write at a time.
|
|
553
|
-
if (this.bodyRoom() * 2 <
|
|
759
|
+
if (this.bodyRoom() * 2 < this.config.outputSize - H1_CONTROL_RESERVE) {
|
|
554
760
|
return H1_NEED_MORE
|
|
555
761
|
}
|
|
556
762
|
this.writeHeld = false
|
|
@@ -587,9 +793,16 @@ export class Http1Connection {
|
|
|
587
793
|
this.state = H1_S_CLOSING
|
|
588
794
|
return H1_NEED_MORE
|
|
589
795
|
}
|
|
796
|
+
// A pipelined head that waited behind the exchange starts its clock
|
|
797
|
+
// now that it is the one being read. The carrier stamped `lastActive`
|
|
798
|
+
// when it last moved a byte, which is no earlier than these bytes came.
|
|
799
|
+
this.headArrived(this.lastActive)
|
|
590
800
|
}
|
|
591
801
|
const event: i32 = this.parser.next()
|
|
592
802
|
if (event === HTTP1_HEAD) {
|
|
803
|
+
// The head is whole, so its deadline is met; the next head's starts
|
|
804
|
+
// when its first byte is fed (`headArrived`).
|
|
805
|
+
this.headTimed = false
|
|
593
806
|
this.requestOpen = true
|
|
594
807
|
this.response = H1_R_NONE
|
|
595
808
|
this.closeAfter = !this.parser.keepAlive
|
|
@@ -642,7 +855,8 @@ export class Http1Connection {
|
|
|
642
855
|
this.noFields,
|
|
643
856
|
this.noFields,
|
|
644
857
|
H1_ZERO,
|
|
645
|
-
"close"
|
|
858
|
+
"close",
|
|
859
|
+
this.date.current()
|
|
646
860
|
)
|
|
647
861
|
if (end >= 0) {
|
|
648
862
|
this.outputEnd = end
|
|
@@ -794,14 +1008,15 @@ export class Http1Connection {
|
|
|
794
1008
|
names,
|
|
795
1009
|
values,
|
|
796
1010
|
framing,
|
|
797
|
-
connection
|
|
1011
|
+
connection,
|
|
1012
|
+
this.date.current()
|
|
798
1013
|
)
|
|
799
1014
|
if (end === HTTP1_REFUSED) {
|
|
800
1015
|
return H1_INVALID
|
|
801
1016
|
}
|
|
802
1017
|
if (end < 0 || end - this.outputEnd > room) {
|
|
803
1018
|
// The head was not written, or ate into the reserve: take it back.
|
|
804
|
-
if (end - this.outputEnd >
|
|
1019
|
+
if (end - this.outputEnd > this.config.outputSize - H1_CONTROL_RESERVE) {
|
|
805
1020
|
return H1_INVALID
|
|
806
1021
|
}
|
|
807
1022
|
if (this.outputEnd === 0) {
|
|
@@ -950,7 +1165,7 @@ export class Http1Connection {
|
|
|
950
1165
|
if (this.state !== H1_S_WEBSOCKET) {
|
|
951
1166
|
return H1_CLOSED
|
|
952
1167
|
}
|
|
953
|
-
if (opcode === WS_OP_CLOSE || len >
|
|
1168
|
+
if (opcode === WS_OP_CLOSE || len > this.config.outputSize - H1_CONTROL_RESERVE - 14) {
|
|
954
1169
|
return H1_INVALID
|
|
955
1170
|
}
|
|
956
1171
|
if (websocketFrameSize(len, false) > this.bodyRoom()) {
|
|
@@ -992,6 +1207,44 @@ export class Http1Connection {
|
|
|
992
1207
|
idle(now: i64): boolean {
|
|
993
1208
|
return now - this.lastActive > toI64(this.config.idleTimeout) * toI64(1000000)
|
|
994
1209
|
}
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Starts the head deadline at `now` (`monotonicNanos`) when bytes of a
|
|
1213
|
+
* request head wait to be read and its clock has not started. The carriers
|
|
1214
|
+
* call it after each `feed`; a program that feeds a connection itself calls
|
|
1215
|
+
* it the same way. Bytes that wait while the previous exchange is still
|
|
1216
|
+
* open start no clock: they count from when it is over (`nextRequest`).
|
|
1217
|
+
*/
|
|
1218
|
+
headArrived(now: i64): void {
|
|
1219
|
+
if (this.parser.buffered() > 0) {
|
|
1220
|
+
this.headAwaited(now)
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* Starts the head deadline at `now` (`monotonicNanos`) when the connection
|
|
1226
|
+
* waits for a head and its clock has not started, whether or not a byte of
|
|
1227
|
+
* it has been read. `Http1TlsServer` calls it when ciphertext arrives after
|
|
1228
|
+
* the handshake: a record trickled a byte at a time hands no plaintext to
|
|
1229
|
+
* `headArrived` until it is whole, so its bytes would otherwise only keep
|
|
1230
|
+
* the slot from going idle.
|
|
1231
|
+
*/
|
|
1232
|
+
headAwaited(now: i64): void {
|
|
1233
|
+
if (!this.headTimed && this.state === H1_S_HTTP && !this.requestOpen) {
|
|
1234
|
+
this.headTimed = true
|
|
1235
|
+
this.headSince = now
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/** Whether a head has been arriving for longer than `headTimeout` at `now` (`monotonicNanos`). */
|
|
1240
|
+
headLate(now: i64): boolean {
|
|
1241
|
+
return this.headTimed && now - this.headSince > toI64(this.config.headTimeout) * toI64(1000000)
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/** Whether `expire` closes the slot at `now`: idle, or a head past its deadline. */
|
|
1245
|
+
expired(now: i64): boolean {
|
|
1246
|
+
return this.idle(now) || this.headLate(now)
|
|
1247
|
+
}
|
|
995
1248
|
}
|
|
996
1249
|
|
|
997
1250
|
// ---- Carriers -------------------------------------------------------------------
|
|
@@ -1003,34 +1256,72 @@ export class Http1Connection {
|
|
|
1003
1256
|
*/
|
|
1004
1257
|
export class Http1Server {
|
|
1005
1258
|
config: Http1Config
|
|
1259
|
+
/**
|
|
1260
|
+
* The connections built so far, slot `k`'s at `k`: one per slot ever
|
|
1261
|
+
* accepted into, and slot 0's once `connection` has asked for it.
|
|
1262
|
+
*/
|
|
1006
1263
|
connections: Http1Connection[]
|
|
1007
|
-
/**
|
|
1264
|
+
/** The `Date` every slot's responses carry, formatted once a second for all of them. `date.pin` fixes its clock. */
|
|
1265
|
+
date: HttpDate
|
|
1266
|
+
/**
|
|
1267
|
+
* Each slot's descriptor, or -1 while it is free, for the slots accepted
|
|
1268
|
+
* into so far: its length is the high-water mark, and a slot past it has
|
|
1269
|
+
* never been used.
|
|
1270
|
+
*/
|
|
1008
1271
|
fds: i32[]
|
|
1272
|
+
/** Each used slot's interest as the loop last armed it, for `rearm`. */
|
|
1273
|
+
armed: i32[]
|
|
1274
|
+
/** The output buffers the slots borrow while they write. */
|
|
1275
|
+
outputs: Http1OutputPool
|
|
1276
|
+
/**
|
|
1277
|
+
* The slots closed since they were used, a stack whose top `accept` takes
|
|
1278
|
+
* before it builds a slot past the high-water mark: finding one costs the
|
|
1279
|
+
* same with every slot free as with all but one busy, where a scan of
|
|
1280
|
+
* `fds` walked the busy ones.
|
|
1281
|
+
*/
|
|
1282
|
+
free: i32[]
|
|
1009
1283
|
/** Where the socket is read into before the connection takes the bytes. */
|
|
1010
1284
|
scratch: u8[]
|
|
1011
1285
|
/** The last accepted peer's address, in `nish:net`'s 18-byte form. */
|
|
1012
1286
|
peer: u8[]
|
|
1287
|
+
/** How many slots the pool may hold, which `size` answers. */
|
|
1288
|
+
slots: i32 = 1
|
|
1013
1289
|
/** The listening socket, which the program made with `tcpListen` and still owns. */
|
|
1014
1290
|
listener: i32 = -1
|
|
1015
1291
|
|
|
1016
|
-
/**
|
|
1292
|
+
/**
|
|
1293
|
+
* A pool of `size` slots, at least one, serving `config` on `listener`.
|
|
1294
|
+
* No slot is built here: a slot's connection and its buffers are
|
|
1295
|
+
* allocated by the first `accept` into it and kept, so a pool sized for a
|
|
1296
|
+
* peak costs at start-up only the read scratch, the shared `Date` and the
|
|
1297
|
+
* output pool, and at most `size` connections once the peak has come.
|
|
1298
|
+
*/
|
|
1017
1299
|
constructor(config: Http1Config, listener: i32, size: i32) {
|
|
1018
1300
|
this.config = config
|
|
1019
1301
|
this.listener = listener
|
|
1020
1302
|
this.connections = []
|
|
1021
1303
|
this.fds = []
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1304
|
+
this.armed = []
|
|
1305
|
+
this.free = []
|
|
1306
|
+
this.slots = size < 1 ? 1 : size
|
|
1307
|
+
this.outputs = new Http1OutputPool(config.outputSize)
|
|
1308
|
+
this.date = new HttpDate()
|
|
1027
1309
|
this.scratch = new Array<u8>(config.chunkSize)
|
|
1028
1310
|
this.peer = new Array<u8>(H1_ADDRESS_SIZE)
|
|
1029
1311
|
}
|
|
1030
1312
|
|
|
1031
|
-
/**
|
|
1313
|
+
/** A new connection on this pool's outputs and `Date`, for the next slot built. */
|
|
1314
|
+
build(): Http1Connection {
|
|
1315
|
+
const conn: Http1Connection = new Http1Connection(this.config)
|
|
1316
|
+
conn.pool = this.outputs
|
|
1317
|
+
conn.date = this.date
|
|
1318
|
+
this.connections.push(conn)
|
|
1319
|
+
return conn
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/** How many slots the pool has, built or not. */
|
|
1032
1323
|
size(): i32 {
|
|
1033
|
-
return
|
|
1324
|
+
return this.slots
|
|
1034
1325
|
}
|
|
1035
1326
|
|
|
1036
1327
|
/** How many slots hold a connection. */
|
|
@@ -1054,36 +1345,57 @@ export class Http1Server {
|
|
|
1054
1345
|
return this.holds(slot) ? this.fds[slot] : -1
|
|
1055
1346
|
}
|
|
1056
1347
|
|
|
1057
|
-
/**
|
|
1348
|
+
/**
|
|
1349
|
+
* The connection of `slot`. A slot never accepted into, or out of range,
|
|
1350
|
+
* names slot 0's connection, which this builds when no slot has been yet
|
|
1351
|
+
* (the first `accept` then takes it, restarted).
|
|
1352
|
+
*/
|
|
1058
1353
|
connection(slot: i32): Http1Connection {
|
|
1059
|
-
|
|
1060
|
-
|
|
1354
|
+
if (slot >= 0 && slot < toI32(this.connections.length)) {
|
|
1355
|
+
return this.connections[slot]
|
|
1356
|
+
}
|
|
1357
|
+
return this.connections.length > 0 ? this.connections[0] : this.build()
|
|
1061
1358
|
}
|
|
1062
1359
|
|
|
1063
1360
|
/**
|
|
1064
1361
|
* Accepts the next waiting connection into a free slot and answers the
|
|
1065
|
-
* slot
|
|
1066
|
-
*
|
|
1362
|
+
* slot, which the program adds to its loop with `H1_WANT_READ`; -11 when
|
|
1363
|
+
* none is waiting, `H1_POOL_FULL` when every slot is busy (the connection
|
|
1364
|
+
* is closed), or `tcpAccept`'s own failure.
|
|
1067
1365
|
*/
|
|
1068
1366
|
accept(): i32 {
|
|
1069
1367
|
const fd: i32 = tcpAccept(this.listener, this.peer)
|
|
1070
1368
|
if (fd < 0) {
|
|
1071
1369
|
return fd
|
|
1072
1370
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1371
|
+
// A slot closed before is taken first, then the next never used, so the
|
|
1372
|
+
// first accepts take slots 0, 1, 2 ... and a pool that never fills never
|
|
1373
|
+
// builds its last slots.
|
|
1374
|
+
let k: i32 = H1_POOL_FULL
|
|
1375
|
+
if (this.free.length > 0) {
|
|
1376
|
+
k = this.free[this.free.length - 1]
|
|
1377
|
+
this.free.pop()
|
|
1378
|
+
} else if (toI32(this.fds.length) < this.slots) {
|
|
1379
|
+
k = toI32(this.fds.length)
|
|
1380
|
+
this.fds.push(H1_FREE)
|
|
1381
|
+
this.armed.push(H1_WANT_READ)
|
|
1382
|
+
}
|
|
1383
|
+
if (k < 0 || k >= toI32(this.fds.length) || k >= toI32(this.armed.length)) {
|
|
1384
|
+
netClose(fd)
|
|
1385
|
+
return H1_POOL_FULL
|
|
1386
|
+
}
|
|
1387
|
+
const conn: Http1Connection = k < toI32(this.connections.length) ? this.connections[k] : this.build()
|
|
1388
|
+
this.fds[k] = fd
|
|
1389
|
+
this.armed[k] = H1_WANT_READ
|
|
1390
|
+
conn.restart()
|
|
1391
|
+
conn.lastActive = monotonicNanos()
|
|
1392
|
+
return k
|
|
1084
1393
|
}
|
|
1085
1394
|
|
|
1086
|
-
/**
|
|
1395
|
+
/**
|
|
1396
|
+
* The socket of `slot` is readable: reads it while the connection has room
|
|
1397
|
+
* and the socket has more, then sends. Answers the interest.
|
|
1398
|
+
*/
|
|
1087
1399
|
readable(slot: i32): i32 {
|
|
1088
1400
|
if (!this.holds(slot)) {
|
|
1089
1401
|
return H1_DONE
|
|
@@ -1108,9 +1420,18 @@ export class Http1Server {
|
|
|
1108
1420
|
}
|
|
1109
1421
|
moved = true
|
|
1110
1422
|
conn.feed(this.scratch, H1_ZERO, n)
|
|
1423
|
+
// A short read emptied the socket. The loop's readiness is
|
|
1424
|
+
// level-triggered, so more input, or the peer's close, wakes it again;
|
|
1425
|
+
// reading once more here would only answer -11, at the price of a
|
|
1426
|
+
// system call on every request.
|
|
1427
|
+
if (n < want) {
|
|
1428
|
+
break
|
|
1429
|
+
}
|
|
1111
1430
|
}
|
|
1112
1431
|
if (moved) {
|
|
1113
|
-
|
|
1432
|
+
const now: i64 = monotonicNanos()
|
|
1433
|
+
conn.lastActive = now
|
|
1434
|
+
conn.headArrived(now)
|
|
1114
1435
|
}
|
|
1115
1436
|
return this.flush(slot)
|
|
1116
1437
|
}
|
|
@@ -1180,6 +1501,23 @@ export class Http1Server {
|
|
|
1180
1501
|
if (conn.outputEnd - conn.outputStart < pending) {
|
|
1181
1502
|
conn.lastActive = monotonicNanos()
|
|
1182
1503
|
}
|
|
1504
|
+
conn.release()
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/**
|
|
1508
|
+
* Whether the program must `pollModify` `slot`'s descriptor to `wants`, the
|
|
1509
|
+
* interest `readable`, `writable` or `flush` answered, and records it as
|
|
1510
|
+
* armed when it must. A keep-alive connection answers the same interest
|
|
1511
|
+
* request after request, and each `pollModify` is a system call
|
|
1512
|
+
* (`epoll_ctl`) that changes nothing.
|
|
1513
|
+
*/
|
|
1514
|
+
rearm(slot: i32, wants: i32): boolean {
|
|
1515
|
+
const interest: i32 = wants & (H1_WANT_READ | H1_WANT_WRITE)
|
|
1516
|
+
if (!this.holds(slot) || slot >= toI32(this.armed.length) || this.armed[slot] === interest) {
|
|
1517
|
+
return false
|
|
1518
|
+
}
|
|
1519
|
+
this.armed[slot] = interest
|
|
1520
|
+
return true
|
|
1183
1521
|
}
|
|
1184
1522
|
|
|
1185
1523
|
/** Closes `slot`'s socket, whatever is left unsent, and frees the slot. */
|
|
@@ -1189,13 +1527,20 @@ export class Http1Server {
|
|
|
1189
1527
|
}
|
|
1190
1528
|
netClose(this.fds[slot])
|
|
1191
1529
|
this.fds[slot] = H1_FREE
|
|
1530
|
+
this.free.push(slot)
|
|
1531
|
+
if (slot < toI32(this.connections.length)) {
|
|
1532
|
+
this.connections[slot].abort()
|
|
1533
|
+
}
|
|
1192
1534
|
}
|
|
1193
1535
|
|
|
1194
|
-
/**
|
|
1536
|
+
/**
|
|
1537
|
+
* Closes every slot that has been idle past `idleTimeout`, or reading one
|
|
1538
|
+
* head for longer than `headTimeout`, at `now` (`monotonicNanos`); answers how many.
|
|
1539
|
+
*/
|
|
1195
1540
|
expire(now: i64): i32 {
|
|
1196
1541
|
let n: i32 = 0
|
|
1197
1542
|
for (let k: i32 = 0; k < toI32(this.fds.length) && k < toI32(this.connections.length); k++) {
|
|
1198
|
-
if (this.fds[k] >= 0 && this.connections[k].
|
|
1543
|
+
if (this.fds[k] >= 0 && this.connections[k].expired(now)) {
|
|
1199
1544
|
this.close(k)
|
|
1200
1545
|
n = n + 1
|
|
1201
1546
|
}
|
|
@@ -1209,25 +1554,69 @@ export class Http1Server {
|
|
|
1209
1554
|
* `Http1Connection` beside each slot of a `TlsTcpServer`, run the way
|
|
1210
1555
|
* `nish/net/http2-tls` runs HTTP/2. A handshake that chose ALPN `http/1.1`,
|
|
1211
1556
|
* or none (a client that does not send ALPN speaks HTTP/1.1), reaches its
|
|
1212
|
-
* connection; any other protocol is shut down with `close_notify`.
|
|
1557
|
+
* connection; any other protocol is shut down with `close_notify`. A slot's
|
|
1558
|
+
* connection is built on the first accept into it and kept, as
|
|
1559
|
+
* `Http1Server`'s is.
|
|
1213
1560
|
*/
|
|
1214
1561
|
export class Http1TlsServer {
|
|
1562
|
+
config: Http1Config
|
|
1215
1563
|
tls: TlsTcpServer
|
|
1564
|
+
/**
|
|
1565
|
+
* The connections built so far, slot `k`'s at `k`: one per slot ever
|
|
1566
|
+
* accepted into. `connection` never adds to it; a slot never built reads
|
|
1567
|
+
* `vacant` instead.
|
|
1568
|
+
*/
|
|
1216
1569
|
connections: Http1Connection[]
|
|
1570
|
+
/**
|
|
1571
|
+
* What `connection` answers for every slot never built: one connection no
|
|
1572
|
+
* slot holds and every such slot shares, so the slot never reads as
|
|
1573
|
+
* another slot's live request. Its first read allocates it, once per
|
|
1574
|
+
* server, and a caller must not change it.
|
|
1575
|
+
*/
|
|
1576
|
+
vacant: Http1Connection | null = null
|
|
1577
|
+
/** The `Date` every slot's responses carry, as `Http1Server.date` is. */
|
|
1578
|
+
date: HttpDate
|
|
1217
1579
|
/** Where TLS decrypts into before the connection takes the bytes. */
|
|
1218
1580
|
scratch: u8[]
|
|
1581
|
+
/** The output buffers the slots borrow while they write. */
|
|
1582
|
+
outputs: Http1OutputPool
|
|
1219
1583
|
|
|
1220
|
-
/**
|
|
1584
|
+
/**
|
|
1585
|
+
* A pool of `size` slots, at least one, serving `config` over TLS under
|
|
1586
|
+
* `tlsConfig` on `listener`. No slot is built here: the first `accept` into
|
|
1587
|
+
* a slot builds its connection and, in `tls`, its handshake state, and
|
|
1588
|
+
* keeps them, so a pool sized for a peak costs at start-up only the read
|
|
1589
|
+
* scratches, the shared `Date` and the empty pools.
|
|
1590
|
+
*/
|
|
1221
1591
|
constructor(tlsConfig: TlsServerConfig, config: Http1Config, listener: i32, size: i32) {
|
|
1592
|
+
this.config = config
|
|
1222
1593
|
this.tls = new TlsTcpServer(tlsConfig, listener, size)
|
|
1223
1594
|
this.connections = []
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
}
|
|
1595
|
+
this.outputs = new Http1OutputPool(config.outputSize)
|
|
1596
|
+
this.date = new HttpDate()
|
|
1227
1597
|
this.scratch = new Array<u8>(config.chunkSize)
|
|
1228
1598
|
}
|
|
1229
1599
|
|
|
1230
|
-
/**
|
|
1600
|
+
/**
|
|
1601
|
+
* A new connection on this pool's outputs and `Date`, for the next slot
|
|
1602
|
+
* built. Called by `accept` alone, as `tls` builds slots: slot `k`'s
|
|
1603
|
+
* connection must sit at index `k` of `connections`.
|
|
1604
|
+
*/
|
|
1605
|
+
build(): Http1Connection {
|
|
1606
|
+
const conn: Http1Connection = this.make()
|
|
1607
|
+
this.connections.push(conn)
|
|
1608
|
+
return conn
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
/** A connection on this pool's outputs and `Date` that no slot holds yet. */
|
|
1612
|
+
make(): Http1Connection {
|
|
1613
|
+
const conn: Http1Connection = new Http1Connection(this.config)
|
|
1614
|
+
conn.pool = this.outputs
|
|
1615
|
+
conn.date = this.date
|
|
1616
|
+
return conn
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
/** How many slots the pool has, built or not. */
|
|
1231
1620
|
size(): i32 {
|
|
1232
1621
|
return this.tls.size()
|
|
1233
1622
|
}
|
|
@@ -1247,10 +1636,32 @@ export class Http1TlsServer {
|
|
|
1247
1636
|
return this.tls.fd(slot)
|
|
1248
1637
|
}
|
|
1249
1638
|
|
|
1250
|
-
/**
|
|
1639
|
+
/**
|
|
1640
|
+
* Whether `slot` holds a connection and has its HTTP/1.1 connection built,
|
|
1641
|
+
* as `Http2TlsServer.live` asks: a slot accepted on `tls` directly holds a
|
|
1642
|
+
* TLS session and no `Http1Connection`, and reads as free here.
|
|
1643
|
+
*/
|
|
1644
|
+
live(slot: i32): boolean {
|
|
1645
|
+
return this.tls.holds(slot) && slot < toI32(this.connections.length)
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
/**
|
|
1649
|
+
* The HTTP/1.1 connection of `slot`. A slot never accepted into answers
|
|
1650
|
+
* `vacant`, as a free slot answers a connection that holds nothing, and
|
|
1651
|
+
* builds no slot; a slot out of range names slot 0's.
|
|
1652
|
+
*/
|
|
1251
1653
|
connection(slot: i32): Http1Connection {
|
|
1252
|
-
const
|
|
1253
|
-
|
|
1654
|
+
const index: i32 = slot >= 0 && slot < this.tls.size() ? slot : 0
|
|
1655
|
+
if (index >= 0 && index < toI32(this.connections.length)) {
|
|
1656
|
+
return this.connections[index]
|
|
1657
|
+
}
|
|
1658
|
+
const vacant: Http1Connection | null = this.vacant
|
|
1659
|
+
if (vacant !== null) {
|
|
1660
|
+
return vacant
|
|
1661
|
+
}
|
|
1662
|
+
const made: Http1Connection = this.make()
|
|
1663
|
+
this.vacant = made
|
|
1664
|
+
return made
|
|
1254
1665
|
}
|
|
1255
1666
|
|
|
1256
1667
|
/** The ALPN protocol `slot`'s handshake chose, or empty. */
|
|
@@ -1267,11 +1678,17 @@ export class Http1TlsServer {
|
|
|
1267
1678
|
/**
|
|
1268
1679
|
* Accepts the next waiting connection as `TlsTcpServer.accept` does, with
|
|
1269
1680
|
* the caller's 32-byte server random and x25519 key, and gives it a fresh
|
|
1270
|
-
* connection. Answers the slot, or
|
|
1681
|
+
* connection, building the slot's on its first accept. Answers the slot, or
|
|
1682
|
+
* what `TlsTcpServer.accept` answers.
|
|
1271
1683
|
*/
|
|
1272
1684
|
accept(serverRandom: u8[], ephemeralPrivate: u8[]): i32 {
|
|
1273
1685
|
const slot: i32 = this.tls.accept(serverRandom, ephemeralPrivate)
|
|
1274
|
-
if (slot >= 0
|
|
1686
|
+
if (slot >= 0) {
|
|
1687
|
+
// `tls` builds a slot past its high-water mark one at a time, so this
|
|
1688
|
+
// builds at most the one connection, keeping slot `k`'s at `k`.
|
|
1689
|
+
while (toI32(this.connections.length) <= slot) {
|
|
1690
|
+
this.build()
|
|
1691
|
+
}
|
|
1275
1692
|
const conn: Http1Connection = this.connections[slot]
|
|
1276
1693
|
conn.restart()
|
|
1277
1694
|
conn.lastActive = monotonicNanos()
|
|
@@ -1281,10 +1698,19 @@ export class Http1TlsServer {
|
|
|
1281
1698
|
|
|
1282
1699
|
/** The socket of `slot` is readable: TLS reads it, and what the connection has to send goes after. Answers the interest. */
|
|
1283
1700
|
readable(slot: i32): i32 {
|
|
1284
|
-
|
|
1285
|
-
|
|
1701
|
+
const live: boolean = this.live(slot)
|
|
1702
|
+
const now: i64 = monotonicNanos()
|
|
1703
|
+
if (live) {
|
|
1704
|
+
this.connections[slot].lastActive = now
|
|
1286
1705
|
}
|
|
1287
1706
|
this.tls.readable(slot)
|
|
1707
|
+
// Ciphertext after the handshake is the start of a head, or of a record
|
|
1708
|
+
// that will hold one, so the head's clock starts here; a record trickled
|
|
1709
|
+
// a byte at a time would hand `next` no plaintext until it is whole. The
|
|
1710
|
+
// handshake itself is bounded by `idleTimeout` alone (TLS-4).
|
|
1711
|
+
if (live && this.tls.connection(slot).state === TLS_RECORD_STATE_OPEN) {
|
|
1712
|
+
this.connections[slot].headAwaited(now)
|
|
1713
|
+
}
|
|
1288
1714
|
return this.flush(slot)
|
|
1289
1715
|
}
|
|
1290
1716
|
|
|
@@ -1306,7 +1732,7 @@ export class Http1TlsServer {
|
|
|
1306
1732
|
* slot, or one whose ALPN was another protocol, answers `H1_ERROR`.
|
|
1307
1733
|
*/
|
|
1308
1734
|
next(slot: i32): i32 {
|
|
1309
|
-
if (!this.
|
|
1735
|
+
if (!this.live(slot) || this.refused(slot)) {
|
|
1310
1736
|
return H1_ERROR
|
|
1311
1737
|
}
|
|
1312
1738
|
const conn: Http1Connection = this.connections[slot]
|
|
@@ -1339,7 +1765,7 @@ export class Http1TlsServer {
|
|
|
1339
1765
|
* connection still holds bytes.
|
|
1340
1766
|
*/
|
|
1341
1767
|
flush(slot: i32): i32 {
|
|
1342
|
-
if (!this.
|
|
1768
|
+
if (!this.live(slot)) {
|
|
1343
1769
|
return TLS_RECORD_DONE
|
|
1344
1770
|
}
|
|
1345
1771
|
if (this.refused(slot)) {
|
|
@@ -1354,8 +1780,14 @@ export class Http1TlsServer {
|
|
|
1354
1780
|
return conn.wantsWrite() && (wants & TLS_RECORD_DONE) === 0 ? wants | TLS_RECORD_WANT_WRITE : wants
|
|
1355
1781
|
}
|
|
1356
1782
|
|
|
1357
|
-
/**
|
|
1783
|
+
/**
|
|
1784
|
+
* Writes what `slot`'s connection holds into TLS, as far as TLS takes it;
|
|
1785
|
+
* a slot never built has nothing to write.
|
|
1786
|
+
*/
|
|
1358
1787
|
send(slot: i32): void {
|
|
1788
|
+
if (slot < 0 || slot >= toI32(this.connections.length)) {
|
|
1789
|
+
return
|
|
1790
|
+
}
|
|
1359
1791
|
const conn: Http1Connection = this.connections[slot]
|
|
1360
1792
|
const pending: i32 = conn.outputEnd - conn.outputStart
|
|
1361
1793
|
while (conn.wantsWrite()) {
|
|
@@ -1368,18 +1800,25 @@ export class Http1TlsServer {
|
|
|
1368
1800
|
if (conn.outputEnd - conn.outputStart < pending) {
|
|
1369
1801
|
conn.lastActive = monotonicNanos()
|
|
1370
1802
|
}
|
|
1803
|
+
conn.release()
|
|
1371
1804
|
}
|
|
1372
1805
|
|
|
1373
1806
|
/** Ends `slot`'s connection and frees the slot at once, as `TlsTcpServer.close` does. */
|
|
1374
1807
|
close(slot: i32): void {
|
|
1375
1808
|
this.tls.close(slot)
|
|
1809
|
+
if (slot >= 0 && slot < toI32(this.connections.length)) {
|
|
1810
|
+
this.connections[slot].abort()
|
|
1811
|
+
}
|
|
1376
1812
|
}
|
|
1377
1813
|
|
|
1378
|
-
/**
|
|
1814
|
+
/**
|
|
1815
|
+
* Closes every slot that has been idle past `idleTimeout`, or reading one
|
|
1816
|
+
* head for longer than `headTimeout`, at `now` (`monotonicNanos`); answers how many.
|
|
1817
|
+
*/
|
|
1379
1818
|
expire(now: i64): i32 {
|
|
1380
1819
|
let n: i32 = 0
|
|
1381
1820
|
for (let k: i32 = 0; k < this.tls.size() && k < toI32(this.connections.length); k++) {
|
|
1382
|
-
if (this.tls.holds(k) && this.connections[k].
|
|
1821
|
+
if (this.tls.holds(k) && this.connections[k].expired(now)) {
|
|
1383
1822
|
this.tls.close(k)
|
|
1384
1823
|
n = n + 1
|
|
1385
1824
|
}
|