@amritk/nish-x86_64-linux 0.20.0 → 0.22.1
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 +52 -10
- package/bin/nish +0 -0
- package/docs/AI.md +2777 -0
- package/package.json +2 -1
- package/runtime/browser.d.ts +6 -0
- package/runtime/browser.mjs +77 -0
- package/runtime/core/arrays.d.ts +36 -0
- package/runtime/core/arrays.mjs +153 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +80 -0
- package/runtime/core/host.mjs +325 -0
- package/runtime/core/numbers.d.ts +66 -0
- package/runtime/core/numbers.mjs +343 -0
- package/runtime/core/result.d.ts +45 -0
- package/runtime/core/result.mjs +140 -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 +46 -2
- package/runtime/nish.h +75 -2
- package/runtime/nish.mjs +23 -3
- package/runtime/node.mjs +386 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +1 -1
- package/runtime/runtime-host.c +1 -1
- package/runtime/runtime-net.c +12 -1
- package/runtime/runtime-os.c +1 -1
- package/runtime/runtime-parallel.c +1 -1
- package/runtime/runtime-simd.c +231 -7
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +0 -11
- package/runtime/runtime.c +190 -6
- package/runtime/shim.mjs +6 -985
- 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 +311 -59
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +32 -12
- package/std/base64.ts +116 -0
- package/std/bytes.ts +256 -0
- package/std/cli.ts +344 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +1557 -0
- package/std/crypto/hmac.ts +138 -2
- package/std/crypto/x509.ts +11 -122
- package/std/json-tokenizer.ts +2998 -57
- package/std/json-value.ts +22 -3
- package/std/json.ts +87 -8
- package/std/math.ts +608 -0
- package/std/net/http1-server.ts +282 -40
- package/std/net/http1.ts +40 -21
- package/std/net/http3-server.ts +50 -12
- package/std/net/postgres.ts +2294 -0
- package/std/net/prefork.ts +23 -3
- package/std/net/quic-listener.ts +20 -4
- package/std/random.ts +66 -4
- 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/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
package/std/net/http1-server.ts
CHANGED
|
@@ -88,7 +88,8 @@
|
|
|
88
88
|
*
|
|
89
89
|
* **Caps are start-up numbers**, all in `Http1Config`: the request target,
|
|
90
90
|
* the header section and its field count, a request body, the chunk handed
|
|
91
|
-
* over, the output buffer, a WebSocket message
|
|
91
|
+
* over, the output buffer, a WebSocket message, the idle timeout and the
|
|
92
|
+
* deadline for a whole request head (`headTimeout`, against slowloris); the
|
|
92
93
|
* slots are the pool's size. `Http1Server` builds a slot's connection on the
|
|
93
94
|
* first accept into it and keeps it, so a pool sized for its peak costs at
|
|
94
95
|
* start-up only what does not depend on the size, and holds at most `size`
|
|
@@ -112,6 +113,7 @@
|
|
|
112
113
|
* implementation.
|
|
113
114
|
*/
|
|
114
115
|
import { Secret } from "nish:secret"
|
|
116
|
+
import { copyBytes } from "nish/bytes"
|
|
115
117
|
import { netClose, netRead, netWrite, tcpAccept } from "nish:net"
|
|
116
118
|
import {
|
|
117
119
|
HTTP1_BODY,
|
|
@@ -119,6 +121,8 @@ import {
|
|
|
119
121
|
HTTP1_CLOSED,
|
|
120
122
|
HTTP1_END,
|
|
121
123
|
HTTP1_ERROR,
|
|
124
|
+
HTTP1_FIRST_HEAD,
|
|
125
|
+
HTTP1_FIRST_INPUT,
|
|
122
126
|
HTTP1_HEAD,
|
|
123
127
|
HTTP1_NO_BODY,
|
|
124
128
|
HTTP1_REFUSED,
|
|
@@ -239,6 +243,11 @@ const http1ServerCheckWindow = (what: string, buf: u8[], off: i32, len: i32): vo
|
|
|
239
243
|
|
|
240
244
|
/** Copies `src[off .. off + n)` to `out` at `at`, which the caller has made sure holds it, and answers where it ends. */
|
|
241
245
|
const http1ServerCopy = (out: u8[], at: i32, src: u8[], off: i32, n: i32): i32 => {
|
|
246
|
+
if (n > 0 && at >= 0 && off >= 0 && at <= toI32(out.length) - n && off <= toI32(src.length) - n) {
|
|
247
|
+
// Both windows fit, as they do for every caller: one move, not a byte loop.
|
|
248
|
+
copyBytes(out, at, src, off, n)
|
|
249
|
+
return at + n
|
|
250
|
+
}
|
|
242
251
|
for (let k: i32 = 0; k < n && at + k < toI32(out.length) && off + k < toI32(src.length); k++) {
|
|
243
252
|
if (at + k >= 0 && off + k >= 0) {
|
|
244
253
|
out[at + k] = src[off + k]
|
|
@@ -344,6 +353,22 @@ export class Http1Config {
|
|
|
344
353
|
maxMessage: i32 = 1048576
|
|
345
354
|
/** Milliseconds without a byte read or written after which `expire` closes a slot. */
|
|
346
355
|
idleTimeout: i32 = 60000
|
|
356
|
+
/**
|
|
357
|
+
* Milliseconds a request head may take, from its first byte to its blank
|
|
358
|
+
* line, before `expire` closes the slot however steadily its bytes come. The
|
|
359
|
+
* idle timeout alone restarts on every byte, so a client that sends one byte
|
|
360
|
+
* of its head each `idleTimeout` would hold its slot for good (slowloris).
|
|
361
|
+
*/
|
|
362
|
+
headTimeout: i32 = 30000
|
|
363
|
+
/**
|
|
364
|
+
* Lend a slot's input and head buffers back to the server between
|
|
365
|
+
* exchanges: once a response is sent and no byte of the next request has
|
|
366
|
+
* come, the slot gives both to pools the server's slots share, and takes
|
|
367
|
+
* them again with the next byte. The memory they hold then follows the
|
|
368
|
+
* connections mid-request rather than every keep-alive connection open,
|
|
369
|
+
* for two pool operations a request. Off by default.
|
|
370
|
+
*/
|
|
371
|
+
idleInput: boolean = false
|
|
347
372
|
}
|
|
348
373
|
|
|
349
374
|
/** The reason phrase a status is sent with; RFC 9112 §4 lets a client ignore it. */
|
|
@@ -462,8 +487,12 @@ export class Http1Connection {
|
|
|
462
487
|
date: HttpDate
|
|
463
488
|
/** The request parser: its head (`methodIs`, `targetIs`, `header`, spans) is the last `H1_REQUEST`'s. */
|
|
464
489
|
parser: Http1Parser
|
|
465
|
-
/**
|
|
466
|
-
|
|
490
|
+
/**
|
|
491
|
+
* The frame decoder: made when the slot first switches to WebSocket, so a
|
|
492
|
+
* slot that only ever speaks HTTP never holds its buffers, then kept for
|
|
493
|
+
* the slot's later peers.
|
|
494
|
+
*/
|
|
495
|
+
ws: WsDecoder | null
|
|
467
496
|
/**
|
|
468
497
|
* Bytes to send are `output[outputStart .. outputEnd)`. Empty until the
|
|
469
498
|
* connection first needs room to write: then its own buffer, made once, or
|
|
@@ -474,14 +503,19 @@ export class Http1Connection {
|
|
|
474
503
|
noOutput: u8[]
|
|
475
504
|
/** Where `output` is borrowed from, for a connection of a pool; null for one that keeps its own. */
|
|
476
505
|
pool: Http1OutputPool | null
|
|
506
|
+
/** Where the parser's input and head buffers go while the slot rests, with `Http1Config.idleInput`; null otherwise. */
|
|
507
|
+
inputs: Http1OutputPool | null
|
|
508
|
+
heads: Http1OutputPool | null
|
|
477
509
|
/** `H1_BODY`'s and `H1_WS_MESSAGE`'s bytes. */
|
|
478
510
|
data: u8[]
|
|
479
|
-
/** A ping's payload, kept until its pong fits. */
|
|
511
|
+
/** A ping's payload, kept until its pong fits: empty until the first ping. */
|
|
480
512
|
pong: u8[]
|
|
481
513
|
/** No fields, for the responses the connection writes itself: made once. */
|
|
482
514
|
noFields: u8[][]
|
|
483
515
|
/** When the carrier last moved a byte for the connection, in `monotonicNanos`. */
|
|
484
516
|
lastActive: i64 = 0
|
|
517
|
+
/** When the head being read began to arrive, in `monotonicNanos`; meaningful while `headTimed`. */
|
|
518
|
+
headSince: i64 = 0
|
|
485
519
|
outputStart: i32 = 0
|
|
486
520
|
outputEnd: i32 = 0
|
|
487
521
|
dataStart: i32 = 0
|
|
@@ -517,6 +551,8 @@ export class Http1Connection {
|
|
|
517
551
|
pongPending: boolean = false
|
|
518
552
|
/** The peer's byte stream has ended. */
|
|
519
553
|
inputEnded: boolean = false
|
|
554
|
+
/** A head has begun to arrive and is not read whole yet: `headSince` is its deadline's start. */
|
|
555
|
+
headTimed: boolean = false
|
|
520
556
|
|
|
521
557
|
/** A connection under `config`. A cap outside what the connection can work with panics. */
|
|
522
558
|
constructor(config: Http1Config) {
|
|
@@ -532,6 +568,7 @@ export class Http1Connection {
|
|
|
532
568
|
http1ServerCheckCap("outputSize", config.outputSize, output, big)
|
|
533
569
|
http1ServerCheckCap("maxMessage", config.maxMessage, H1_ZERO, big - H1_FRAME_HEADER)
|
|
534
570
|
http1ServerCheckCap("idleTimeout", config.idleTimeout, one, big)
|
|
571
|
+
http1ServerCheckCap("headTimeout", config.headTimeout, one, big)
|
|
535
572
|
// The buffers the peer's bytes fill grow by doubling and stop at 2^30
|
|
536
573
|
// (`Http1Parser`'s input and head, `WsDecoder`'s input), so a cap whose
|
|
537
574
|
// buffer could need more is refused here rather than reached by a peer:
|
|
@@ -548,12 +585,14 @@ export class Http1Connection {
|
|
|
548
585
|
this.parser = new Http1Parser(config.maxTarget, config.maxHeaderBytes, config.maxHeaders, config.maxBody)
|
|
549
586
|
this.parser.keepText = false
|
|
550
587
|
this.parser.maxChunk = config.chunkSize
|
|
551
|
-
this.ws =
|
|
588
|
+
this.ws = null
|
|
552
589
|
this.noOutput = []
|
|
553
590
|
this.output = this.noOutput
|
|
554
591
|
this.pool = null
|
|
592
|
+
this.inputs = null
|
|
593
|
+
this.heads = null
|
|
555
594
|
this.data = this.noOutput
|
|
556
|
-
this.pong =
|
|
595
|
+
this.pong = this.noOutput
|
|
557
596
|
this.noFields = []
|
|
558
597
|
// A line must fit whole before the parser can read it, so the buffer
|
|
559
598
|
// holds the longest request line or field line however small a chunk is.
|
|
@@ -571,7 +610,10 @@ export class Http1Connection {
|
|
|
571
610
|
/** Puts the connection back as the constructor left it, for the next peer. It allocates nothing. */
|
|
572
611
|
restart(): void {
|
|
573
612
|
this.parser.restart()
|
|
574
|
-
this.ws
|
|
613
|
+
const ws: WsDecoder | null = this.ws
|
|
614
|
+
if (ws !== null) {
|
|
615
|
+
ws.reset()
|
|
616
|
+
}
|
|
575
617
|
this.outputStart = 0
|
|
576
618
|
this.outputEnd = 0
|
|
577
619
|
this.data = this.noOutput
|
|
@@ -592,7 +634,52 @@ export class Http1Connection {
|
|
|
592
634
|
this.writeHeld = false
|
|
593
635
|
this.pongPending = false
|
|
594
636
|
this.inputEnded = false
|
|
637
|
+
this.headTimed = false
|
|
638
|
+
this.headSince = 0
|
|
595
639
|
this.release()
|
|
640
|
+
this.park()
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Gives the parser's input and head buffers to `inputs` and `heads` while
|
|
645
|
+
* the connection rests between exchanges: speaking HTTP, no request open,
|
|
646
|
+
* nothing fed and unread. Does nothing without the pools or otherwise.
|
|
647
|
+
*/
|
|
648
|
+
park(): void {
|
|
649
|
+
const inputs: Http1OutputPool | null = this.inputs
|
|
650
|
+
const heads: Http1OutputPool | null = this.heads
|
|
651
|
+
const p: Http1Parser = this.parser
|
|
652
|
+
if (inputs === null || heads === null || this.state !== H1_S_HTTP || this.requestOpen || !p.resting()) {
|
|
653
|
+
return
|
|
654
|
+
}
|
|
655
|
+
if (toI32(p.buf.length) > 0) {
|
|
656
|
+
inputs.give(p.buf)
|
|
657
|
+
p.buf = this.noOutput
|
|
658
|
+
p.body = this.noOutput
|
|
659
|
+
p.start = 0
|
|
660
|
+
p.end = 0
|
|
661
|
+
p.scan = 0
|
|
662
|
+
p.lineStart = 0
|
|
663
|
+
p.lineEnd = 0
|
|
664
|
+
}
|
|
665
|
+
if (toI32(p.head.length) > 0) {
|
|
666
|
+
heads.give(p.head)
|
|
667
|
+
p.head = this.noOutput
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/** Takes back the buffers `park` gave away, before bytes are fed. */
|
|
672
|
+
unpark(): void {
|
|
673
|
+
const inputs: Http1OutputPool | null = this.inputs
|
|
674
|
+
const heads: Http1OutputPool | null = this.heads
|
|
675
|
+
const p: Http1Parser = this.parser
|
|
676
|
+
if (inputs !== null && toI32(p.buf.length) === 0) {
|
|
677
|
+
p.buf = inputs.take()
|
|
678
|
+
p.body = p.buf
|
|
679
|
+
}
|
|
680
|
+
if (heads !== null && toI32(p.head.length) === 0) {
|
|
681
|
+
p.head = heads.take()
|
|
682
|
+
}
|
|
596
683
|
}
|
|
597
684
|
|
|
598
685
|
// ---- Bytes in and out ---------------------------------------------------------
|
|
@@ -607,7 +694,9 @@ export class Http1Connection {
|
|
|
607
694
|
if (this.writeHeld || this.inputEnded || this.state === H1_S_CLOSING || this.state === H1_S_FAILED) {
|
|
608
695
|
return 0
|
|
609
696
|
}
|
|
610
|
-
const
|
|
697
|
+
const ws: WsDecoder | null = this.ws
|
|
698
|
+
const held: i32 =
|
|
699
|
+
this.state === H1_S_WEBSOCKET && ws !== null ? ws.end - ws.start : this.parser.buffered()
|
|
611
700
|
const room: i32 = this.inputLimit() - held
|
|
612
701
|
return room > 0 ? room : 0
|
|
613
702
|
}
|
|
@@ -625,8 +714,9 @@ export class Http1Connection {
|
|
|
625
714
|
return 0
|
|
626
715
|
}
|
|
627
716
|
if (this.state === H1_S_WEBSOCKET) {
|
|
628
|
-
this.
|
|
717
|
+
this.decoder().feed(buf, off, n)
|
|
629
718
|
} else {
|
|
719
|
+
this.unpark()
|
|
630
720
|
this.parser.feed(buf, off, n)
|
|
631
721
|
}
|
|
632
722
|
return n
|
|
@@ -712,12 +802,8 @@ export class Http1Connection {
|
|
|
712
802
|
const output: u8[] = this.output
|
|
713
803
|
const live: i32 = this.outputEnd - this.outputStart
|
|
714
804
|
if (this.outputStart > 0) {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
k < live && k < toI32(output.length) && this.outputStart + k < toI32(output.length);
|
|
718
|
-
k++
|
|
719
|
-
) {
|
|
720
|
-
output[k] = output[this.outputStart + k]
|
|
805
|
+
if (live > 0 && this.outputStart <= toI32(output.length) - live) {
|
|
806
|
+
copyBytes(output, 0, output, this.outputStart, live)
|
|
721
807
|
}
|
|
722
808
|
this.outputStart = 0
|
|
723
809
|
this.outputEnd = live
|
|
@@ -769,8 +855,9 @@ export class Http1Connection {
|
|
|
769
855
|
this.response = H1_R_NONE
|
|
770
856
|
if (this.upgraded) {
|
|
771
857
|
const p: Http1Parser = this.parser
|
|
772
|
-
this.
|
|
773
|
-
|
|
858
|
+
const ws: WsDecoder = this.decoder()
|
|
859
|
+
ws.reset()
|
|
860
|
+
ws.feed(p.buf, p.start, p.buffered())
|
|
774
861
|
this.state = H1_S_WEBSOCKET
|
|
775
862
|
return this.nextFrame()
|
|
776
863
|
}
|
|
@@ -778,9 +865,16 @@ export class Http1Connection {
|
|
|
778
865
|
this.state = H1_S_CLOSING
|
|
779
866
|
return H1_NEED_MORE
|
|
780
867
|
}
|
|
868
|
+
// A pipelined head that waited behind the exchange starts its clock
|
|
869
|
+
// now that it is the one being read. The carrier stamped `lastActive`
|
|
870
|
+
// when it last moved a byte, which is no earlier than these bytes came.
|
|
871
|
+
this.headArrived(this.lastActive)
|
|
781
872
|
}
|
|
782
873
|
const event: i32 = this.parser.next()
|
|
783
874
|
if (event === HTTP1_HEAD) {
|
|
875
|
+
// The head is whole, so its deadline is met; the next head's starts
|
|
876
|
+
// when its first byte is fed (`headArrived`).
|
|
877
|
+
this.headTimed = false
|
|
784
878
|
this.requestOpen = true
|
|
785
879
|
this.response = H1_R_NONE
|
|
786
880
|
this.closeAfter = !this.parser.keepAlive
|
|
@@ -845,35 +939,50 @@ export class Http1Connection {
|
|
|
845
939
|
return H1_ERROR
|
|
846
940
|
}
|
|
847
941
|
|
|
942
|
+
/** The frame decoder, made on the slot's first switch to WebSocket. */
|
|
943
|
+
decoder(): WsDecoder {
|
|
944
|
+
const ws: WsDecoder | null = this.ws
|
|
945
|
+
if (ws !== null) {
|
|
946
|
+
return ws
|
|
947
|
+
}
|
|
948
|
+
const made: WsDecoder = new WsDecoder(true, this.config.maxMessage)
|
|
949
|
+
this.ws = made
|
|
950
|
+
return made
|
|
951
|
+
}
|
|
952
|
+
|
|
848
953
|
/** `next` once the slot has switched to WebSocket. */
|
|
849
954
|
nextFrame(): i32 {
|
|
850
955
|
while (true) {
|
|
851
956
|
if (this.pongPending && !this.sendPong()) {
|
|
852
957
|
return H1_NEED_MORE
|
|
853
958
|
}
|
|
854
|
-
const
|
|
959
|
+
const ws: WsDecoder = this.decoder()
|
|
960
|
+
const event: i32 = ws.next()
|
|
855
961
|
if (event === WS_MESSAGE) {
|
|
856
|
-
this.opcode =
|
|
857
|
-
this.data =
|
|
962
|
+
this.opcode = ws.opcode
|
|
963
|
+
this.data = ws.data
|
|
858
964
|
this.dataStart = 0
|
|
859
|
-
this.dataLength =
|
|
965
|
+
this.dataLength = ws.dataLen
|
|
860
966
|
return H1_WS_MESSAGE
|
|
861
967
|
}
|
|
862
968
|
if (event === WS_PING) {
|
|
863
|
-
const n: i32 =
|
|
864
|
-
|
|
865
|
-
this.pong
|
|
969
|
+
const n: i32 = ws.dataLen
|
|
970
|
+
if (this.pong.length === 0) {
|
|
971
|
+
this.pong = new Array<u8>(WS_MAX_CONTROL)
|
|
972
|
+
}
|
|
973
|
+
for (let k: i32 = 0; k < n && k < toI32(this.pong.length) && k < toI32(ws.data.length); k++) {
|
|
974
|
+
this.pong[k] = ws.data[k]
|
|
866
975
|
}
|
|
867
976
|
this.pongLength = n
|
|
868
977
|
this.pongPending = true
|
|
869
978
|
} else if (event === WS_CLOSE) {
|
|
870
|
-
this.status =
|
|
871
|
-
this.sendClose(
|
|
979
|
+
this.status = ws.closeCode
|
|
980
|
+
this.sendClose(ws.closeCode)
|
|
872
981
|
this.state = H1_S_CLOSING
|
|
873
982
|
return H1_WS_CLOSE
|
|
874
983
|
} else if (event === WS_ERROR) {
|
|
875
|
-
this.status =
|
|
876
|
-
this.sendClose(
|
|
984
|
+
this.status = ws.closeCode
|
|
985
|
+
this.sendClose(ws.closeCode)
|
|
877
986
|
this.state = H1_S_FAILED
|
|
878
987
|
return H1_ERROR
|
|
879
988
|
} else if (event === WS_NEED_MORE) {
|
|
@@ -913,7 +1022,7 @@ export class Http1Connection {
|
|
|
913
1022
|
const payload: u8[] | null = websocketClosePayload(code, "")
|
|
914
1023
|
// A code that may not be sent (1005, for a close that had none) is
|
|
915
1024
|
// answered with an empty close.
|
|
916
|
-
const body: u8[] = payload === null ? this.
|
|
1025
|
+
const body: u8[] = payload === null ? this.noOutput : payload
|
|
917
1026
|
const n: i32 = payload === null ? H1_ZERO : toI32(body.length)
|
|
918
1027
|
const end: i32 = websocketWriteFrame(
|
|
919
1028
|
this.output,
|
|
@@ -1185,6 +1294,68 @@ export class Http1Connection {
|
|
|
1185
1294
|
idle(now: i64): boolean {
|
|
1186
1295
|
return now - this.lastActive > toI64(this.config.idleTimeout) * toI64(1000000)
|
|
1187
1296
|
}
|
|
1297
|
+
|
|
1298
|
+
/**
|
|
1299
|
+
* Starts the head deadline at `now` (`monotonicNanos`) when bytes of a
|
|
1300
|
+
* request head wait to be read and its clock has not started. The carriers
|
|
1301
|
+
* call it after each `feed`; a program that feeds a connection itself calls
|
|
1302
|
+
* it the same way. Bytes that wait while the previous exchange is still
|
|
1303
|
+
* open start no clock: they count from when it is over (`nextRequest`).
|
|
1304
|
+
*/
|
|
1305
|
+
headArrived(now: i64): void {
|
|
1306
|
+
if (this.parser.buffered() > 0) {
|
|
1307
|
+
this.headAwaited(now)
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* Starts the head deadline at `now` (`monotonicNanos`) when the connection
|
|
1313
|
+
* waits for a head and its clock has not started, whether or not a byte of
|
|
1314
|
+
* it has been read. `Http1TlsServer` calls it when ciphertext arrives after
|
|
1315
|
+
* the handshake: a record trickled a byte at a time hands no plaintext to
|
|
1316
|
+
* `headArrived` until it is whole, so its bytes would otherwise only keep
|
|
1317
|
+
* the slot from going idle.
|
|
1318
|
+
*/
|
|
1319
|
+
headAwaited(now: i64): void {
|
|
1320
|
+
if (!this.headTimed && this.state === H1_S_HTTP && !this.requestOpen) {
|
|
1321
|
+
this.headTimed = true
|
|
1322
|
+
this.headSince = now
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
/** Whether a head has been arriving for longer than `headTimeout` at `now` (`monotonicNanos`). */
|
|
1327
|
+
headLate(now: i64): boolean {
|
|
1328
|
+
return this.headTimed && now - this.headSince > toI64(this.config.headTimeout) * toI64(1000000)
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/** Whether `expire` closes the slot at `now`: idle, or a head past its deadline. */
|
|
1332
|
+
expired(now: i64): boolean {
|
|
1333
|
+
return this.idle(now) || this.headLate(now)
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
/**
|
|
1337
|
+
* The soonest `expired` can turn true, however the connection moves from
|
|
1338
|
+
* here: `lastActive` only grows, and a head clock not yet started starts
|
|
1339
|
+
* no earlier than it.
|
|
1340
|
+
*/
|
|
1341
|
+
expiresNoSooner(): i64 {
|
|
1342
|
+
const idle: i64 = toI64(this.config.idleTimeout) * toI64(1000000)
|
|
1343
|
+
const head: i64 = toI64(this.config.headTimeout) * toI64(1000000)
|
|
1344
|
+
const soonest: i64 = this.lastActive + (head < idle ? head : idle)
|
|
1345
|
+
if (this.headTimed && this.headSince + head < soonest) {
|
|
1346
|
+
return this.headSince + head
|
|
1347
|
+
}
|
|
1348
|
+
return soonest
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
/**
|
|
1353
|
+
* The soonest any slot accepted after now can expire: no sooner than the
|
|
1354
|
+
* shorter of `idleTimeout` and `headTimeout` from now.
|
|
1355
|
+
*/
|
|
1356
|
+
const http1ExpireFloor = (config: Http1Config): i64 => {
|
|
1357
|
+
const shorter: i32 = config.headTimeout < config.idleTimeout ? config.headTimeout : config.idleTimeout
|
|
1358
|
+
return monotonicNanos() + toI64(shorter) * toI64(1000000)
|
|
1188
1359
|
}
|
|
1189
1360
|
|
|
1190
1361
|
// ---- Carriers -------------------------------------------------------------------
|
|
@@ -1213,6 +1384,9 @@ export class Http1Server {
|
|
|
1213
1384
|
armed: i32[]
|
|
1214
1385
|
/** The output buffers the slots borrow while they write. */
|
|
1215
1386
|
outputs: Http1OutputPool
|
|
1387
|
+
/** The input and head buffers resting slots lend, with `Http1Config.idleInput`. */
|
|
1388
|
+
inputs: Http1OutputPool
|
|
1389
|
+
heads: Http1OutputPool
|
|
1216
1390
|
/**
|
|
1217
1391
|
* The slots closed since they were used, a stack whose top `accept` takes
|
|
1218
1392
|
* before it builds a slot past the high-water mark: finding one costs the
|
|
@@ -1228,6 +1402,14 @@ export class Http1Server {
|
|
|
1228
1402
|
slots: i32 = 1
|
|
1229
1403
|
/** The listening socket, which the program made with `tcpListen` and still owns. */
|
|
1230
1404
|
listener: i32 = -1
|
|
1405
|
+
/**
|
|
1406
|
+
* No slot can expire before this (`monotonicNanos`), so `expire` called
|
|
1407
|
+
* sooner walks none: it walks every slot only about once per the shorter
|
|
1408
|
+
* timeout, however many there are.
|
|
1409
|
+
*/
|
|
1410
|
+
expireAfter: i64 = 0
|
|
1411
|
+
/** How many calls of `expire` walked every slot, for a program measuring its cost. */
|
|
1412
|
+
expireScans: i32 = 0
|
|
1231
1413
|
|
|
1232
1414
|
/**
|
|
1233
1415
|
* A pool of `size` slots, at least one, serving `config` on `listener`.
|
|
@@ -1245,6 +1427,8 @@ export class Http1Server {
|
|
|
1245
1427
|
this.free = []
|
|
1246
1428
|
this.slots = size < 1 ? 1 : size
|
|
1247
1429
|
this.outputs = new Http1OutputPool(config.outputSize)
|
|
1430
|
+
this.inputs = new Http1OutputPool(HTTP1_FIRST_INPUT)
|
|
1431
|
+
this.heads = new Http1OutputPool(HTTP1_FIRST_HEAD)
|
|
1248
1432
|
this.date = new HttpDate()
|
|
1249
1433
|
this.scratch = new Array<u8>(config.chunkSize)
|
|
1250
1434
|
this.peer = new Array<u8>(H1_ADDRESS_SIZE)
|
|
@@ -1255,6 +1439,10 @@ export class Http1Server {
|
|
|
1255
1439
|
const conn: Http1Connection = new Http1Connection(this.config)
|
|
1256
1440
|
conn.pool = this.outputs
|
|
1257
1441
|
conn.date = this.date
|
|
1442
|
+
if (this.config.idleInput) {
|
|
1443
|
+
conn.inputs = this.inputs
|
|
1444
|
+
conn.heads = this.heads
|
|
1445
|
+
}
|
|
1258
1446
|
this.connections.push(conn)
|
|
1259
1447
|
return conn
|
|
1260
1448
|
}
|
|
@@ -1369,7 +1557,9 @@ export class Http1Server {
|
|
|
1369
1557
|
}
|
|
1370
1558
|
}
|
|
1371
1559
|
if (moved) {
|
|
1372
|
-
|
|
1560
|
+
const now: i64 = monotonicNanos()
|
|
1561
|
+
conn.lastActive = now
|
|
1562
|
+
conn.headArrived(now)
|
|
1373
1563
|
}
|
|
1374
1564
|
return this.flush(slot)
|
|
1375
1565
|
}
|
|
@@ -1411,6 +1601,7 @@ export class Http1Server {
|
|
|
1411
1601
|
if (conn.isDone()) {
|
|
1412
1602
|
return H1_DONE
|
|
1413
1603
|
}
|
|
1604
|
+
conn.park()
|
|
1414
1605
|
let wants: i32 = 0
|
|
1415
1606
|
if (conn.inputRoom() > 0) {
|
|
1416
1607
|
wants = wants | H1_WANT_READ
|
|
@@ -1471,15 +1662,29 @@ export class Http1Server {
|
|
|
1471
1662
|
}
|
|
1472
1663
|
}
|
|
1473
1664
|
|
|
1474
|
-
/**
|
|
1665
|
+
/**
|
|
1666
|
+
* Closes every slot that has been idle past `idleTimeout`, or reading one
|
|
1667
|
+
* head for longer than `headTimeout`, at `now` (`monotonicNanos`); answers how many.
|
|
1668
|
+
*/
|
|
1475
1669
|
expire(now: i64): i32 {
|
|
1670
|
+
if (now < this.expireAfter) {
|
|
1671
|
+
return 0
|
|
1672
|
+
}
|
|
1673
|
+
this.expireScans = this.expireScans + 1
|
|
1674
|
+
let soonest: i64 = http1ExpireFloor(this.config)
|
|
1476
1675
|
let n: i32 = 0
|
|
1477
1676
|
for (let k: i32 = 0; k < toI32(this.fds.length) && k < toI32(this.connections.length); k++) {
|
|
1478
|
-
if (this.fds[k] >= 0
|
|
1479
|
-
this.
|
|
1480
|
-
|
|
1677
|
+
if (this.fds[k] >= 0) {
|
|
1678
|
+
const conn = this.connections[k]
|
|
1679
|
+
if (conn.expired(now)) {
|
|
1680
|
+
this.close(k)
|
|
1681
|
+
n = n + 1
|
|
1682
|
+
} else if (conn.expiresNoSooner() < soonest) {
|
|
1683
|
+
soonest = conn.expiresNoSooner()
|
|
1684
|
+
}
|
|
1481
1685
|
}
|
|
1482
1686
|
}
|
|
1687
|
+
this.expireAfter = soonest
|
|
1483
1688
|
return n
|
|
1484
1689
|
}
|
|
1485
1690
|
}
|
|
@@ -1515,6 +1720,13 @@ export class Http1TlsServer {
|
|
|
1515
1720
|
scratch: u8[]
|
|
1516
1721
|
/** The output buffers the slots borrow while they write. */
|
|
1517
1722
|
outputs: Http1OutputPool
|
|
1723
|
+
/** The input and head buffers resting slots lend, with `Http1Config.idleInput`. */
|
|
1724
|
+
inputs: Http1OutputPool
|
|
1725
|
+
heads: Http1OutputPool
|
|
1726
|
+
/** No slot can expire before this (`monotonicNanos`): `Http1Server.expireAfter`. */
|
|
1727
|
+
expireAfter: i64 = 0
|
|
1728
|
+
/** How many calls of `expire` walked every slot. */
|
|
1729
|
+
expireScans: i32 = 0
|
|
1518
1730
|
|
|
1519
1731
|
/**
|
|
1520
1732
|
* A pool of `size` slots, at least one, serving `config` over TLS under
|
|
@@ -1528,6 +1740,8 @@ export class Http1TlsServer {
|
|
|
1528
1740
|
this.tls = new TlsTcpServer(tlsConfig, listener, size)
|
|
1529
1741
|
this.connections = []
|
|
1530
1742
|
this.outputs = new Http1OutputPool(config.outputSize)
|
|
1743
|
+
this.inputs = new Http1OutputPool(HTTP1_FIRST_INPUT)
|
|
1744
|
+
this.heads = new Http1OutputPool(HTTP1_FIRST_HEAD)
|
|
1531
1745
|
this.date = new HttpDate()
|
|
1532
1746
|
this.scratch = new Array<u8>(config.chunkSize)
|
|
1533
1747
|
}
|
|
@@ -1548,6 +1762,10 @@ export class Http1TlsServer {
|
|
|
1548
1762
|
const conn: Http1Connection = new Http1Connection(this.config)
|
|
1549
1763
|
conn.pool = this.outputs
|
|
1550
1764
|
conn.date = this.date
|
|
1765
|
+
if (this.config.idleInput) {
|
|
1766
|
+
conn.inputs = this.inputs
|
|
1767
|
+
conn.heads = this.heads
|
|
1768
|
+
}
|
|
1551
1769
|
return conn
|
|
1552
1770
|
}
|
|
1553
1771
|
|
|
@@ -1633,10 +1851,19 @@ export class Http1TlsServer {
|
|
|
1633
1851
|
|
|
1634
1852
|
/** The socket of `slot` is readable: TLS reads it, and what the connection has to send goes after. Answers the interest. */
|
|
1635
1853
|
readable(slot: i32): i32 {
|
|
1636
|
-
|
|
1637
|
-
|
|
1854
|
+
const live: boolean = this.live(slot)
|
|
1855
|
+
const now: i64 = monotonicNanos()
|
|
1856
|
+
if (live) {
|
|
1857
|
+
this.connections[slot].lastActive = now
|
|
1638
1858
|
}
|
|
1639
1859
|
this.tls.readable(slot)
|
|
1860
|
+
// Ciphertext after the handshake is the start of a head, or of a record
|
|
1861
|
+
// that will hold one, so the head's clock starts here; a record trickled
|
|
1862
|
+
// a byte at a time would hand `next` no plaintext until it is whole. The
|
|
1863
|
+
// handshake itself is bounded by `idleTimeout` alone (TLS-4).
|
|
1864
|
+
if (live && this.tls.connection(slot).state === TLS_RECORD_STATE_OPEN) {
|
|
1865
|
+
this.connections[slot].headAwaited(now)
|
|
1866
|
+
}
|
|
1640
1867
|
return this.flush(slot)
|
|
1641
1868
|
}
|
|
1642
1869
|
|
|
@@ -1702,6 +1929,7 @@ export class Http1TlsServer {
|
|
|
1702
1929
|
if (conn.isDone()) {
|
|
1703
1930
|
return this.tls.shutdown(slot)
|
|
1704
1931
|
}
|
|
1932
|
+
conn.park()
|
|
1705
1933
|
const wants: i32 = this.tls.interest(slot)
|
|
1706
1934
|
return conn.wantsWrite() && (wants & TLS_RECORD_DONE) === 0 ? wants | TLS_RECORD_WANT_WRITE : wants
|
|
1707
1935
|
}
|
|
@@ -1737,15 +1965,29 @@ export class Http1TlsServer {
|
|
|
1737
1965
|
}
|
|
1738
1966
|
}
|
|
1739
1967
|
|
|
1740
|
-
/**
|
|
1968
|
+
/**
|
|
1969
|
+
* Closes every slot that has been idle past `idleTimeout`, or reading one
|
|
1970
|
+
* head for longer than `headTimeout`, at `now` (`monotonicNanos`); answers how many.
|
|
1971
|
+
*/
|
|
1741
1972
|
expire(now: i64): i32 {
|
|
1973
|
+
if (now < this.expireAfter) {
|
|
1974
|
+
return 0
|
|
1975
|
+
}
|
|
1976
|
+
this.expireScans = this.expireScans + 1
|
|
1977
|
+
let soonest: i64 = http1ExpireFloor(this.config)
|
|
1742
1978
|
let n: i32 = 0
|
|
1743
1979
|
for (let k: i32 = 0; k < this.tls.size() && k < toI32(this.connections.length); k++) {
|
|
1744
|
-
if (this.tls.holds(k)
|
|
1745
|
-
this.
|
|
1746
|
-
|
|
1980
|
+
if (this.tls.holds(k)) {
|
|
1981
|
+
const conn = this.connections[k]
|
|
1982
|
+
if (conn.expired(now)) {
|
|
1983
|
+
this.tls.close(k)
|
|
1984
|
+
n = n + 1
|
|
1985
|
+
} else if (conn.expiresNoSooner() < soonest) {
|
|
1986
|
+
soonest = conn.expiresNoSooner()
|
|
1987
|
+
}
|
|
1747
1988
|
}
|
|
1748
1989
|
}
|
|
1990
|
+
this.expireAfter = soonest
|
|
1749
1991
|
return n
|
|
1750
1992
|
}
|
|
1751
1993
|
}
|