@amritk/nish-x86_64-linux 0.28.0 → 0.30.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 +8 -5
- package/bin/nish +0 -0
- package/docs/AI.md +114 -24
- package/package.json +1 -1
- package/runtime/core/host.d.ts +2 -1
- package/runtime/core/host.mjs +4 -0
- package/runtime/nish.d.ts +127 -0
- package/runtime/nish.h +94 -5
- package/runtime/nish.mjs +18 -4
- package/runtime/runtime-net.c +386 -5
- package/runtime/runtime-os.c +59 -8
- package/runtime/runtime-parallel.c +60 -5
- package/runtime/runtime-region.c +231 -0
- package/runtime/runtime-sandbox.c +43 -34
- package/runtime/runtime-shm.c +541 -0
- package/runtime/runtime.c +838 -792
- package/scripts/build.sh +55 -12
- package/std/README.md +12 -3
- package/std/collections.ts +5 -5
- package/std/crypto/hmac.ts +305 -2
- package/std/crypto/jwt.ts +1661 -0
- package/std/json-typed.ts +34 -0
- 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 +54 -12
- package/std/net/http1-client.ts +270 -41
- package/std/net/http1-server.ts +145 -32
- package/std/net/http1.ts +274 -51
- package/std/net/outbound.ts +409 -0
- package/std/net/postgres-carrier.ts +985 -0
- package/std/net/postgres.ts +297 -4
- package/std/net/prefork.ts +344 -20
- package/std/net/upstream.ts +759 -0
- package/std/secret.ts +1 -1
- package/std/sync/ring.ts +587 -0
- package/std/sync/shm.ts +284 -0
- package/std/term-rate.ts +13 -1
- package/std/threads.ts +35 -3
- /package/runtime/{LICENSE-ryu → LICENSE-zmij} +0 -0
package/std/net/http1-server.ts
CHANGED
|
@@ -70,7 +70,11 @@
|
|
|
70
70
|
* most once a second; a 1xx carries none. The pool's slots share one, so a
|
|
71
71
|
* busy server formats a second once, not once a slot. A program that sets its
|
|
72
72
|
* own `date` field sends that one instead, so a response has one `Date`.
|
|
73
|
-
* `
|
|
73
|
+
* `Http1Server` reads the clock once per serve call — from `readable` or
|
|
74
|
+
* `writable` to the `flush` that ends it — and every response written in
|
|
75
|
+
* between carries that reading; one written outside a serve call, an answer
|
|
76
|
+
* given later, reads the clock then. `date.pin(unixMs)` fixes it, for a test
|
|
77
|
+
* that compares bytes.
|
|
74
78
|
*
|
|
75
79
|
* **Refusals.** Every refusal of `nish/net/http1`'s parser — the smuggling
|
|
76
80
|
* shapes, a `Content-Length` with a `Transfer-Encoding`, two that disagree,
|
|
@@ -118,11 +122,12 @@ import {
|
|
|
118
122
|
HTTP_CODING_GZIP,
|
|
119
123
|
HTTP_CODING_IDENTITY,
|
|
120
124
|
HTTP_CODING_NONE,
|
|
125
|
+
HTTP_DEFAULT_MSS,
|
|
121
126
|
HTTP_OFFER_GZIP,
|
|
122
127
|
HttpCompressor,
|
|
123
128
|
httpAcceptEncoding,
|
|
124
129
|
} from "nish/net/http-compress"
|
|
125
|
-
import { netClose, netRead, netWrite, tcpAccept } from "nish:net"
|
|
130
|
+
import { netClose, netMss, netRead, netWrite, tcpAccept } from "nish:net"
|
|
126
131
|
import {
|
|
127
132
|
HTTP1_BODY,
|
|
128
133
|
HTTP1_CHUNKED,
|
|
@@ -274,7 +279,8 @@ const http1ServerCopy = (out: u8[], at: i32, src: u8[], off: i32, n: i32): i32 =
|
|
|
274
279
|
* it calls, `httpDateWrite`, stays in `nish/net/http-fields`, which compiles
|
|
275
280
|
* for wasm32 (the split `nish/crypto/x509` and `x509-random` make). `pin`
|
|
276
281
|
* replaces the clock with one instant, which is how a test asserts a response
|
|
277
|
-
* byte for byte.
|
|
282
|
+
* byte for byte. `hold` reads the clock once for a run of responses, a serve
|
|
283
|
+
* call's, until `unhold`; `pin` wins over it.
|
|
278
284
|
*/
|
|
279
285
|
export class HttpDate {
|
|
280
286
|
/** The IMF-fixdate of `second`: what `current` answers, made once. */
|
|
@@ -283,9 +289,14 @@ export class HttpDate {
|
|
|
283
289
|
second: i64 = -1
|
|
284
290
|
/** The instant `pin` fixed, in milliseconds since 1970, while `pinned`. */
|
|
285
291
|
pinnedMs: i64 = 0
|
|
292
|
+
/** The clock reading `hold` took, in milliseconds since 1970, while `held`. */
|
|
293
|
+
heldMs: i64 = 0
|
|
286
294
|
/** How many times `bytes` has been rewritten. */
|
|
287
295
|
rewrites: i64 = 0
|
|
296
|
+
/** How many times the wall clock has been read. */
|
|
297
|
+
reads: i64 = 0
|
|
288
298
|
pinned: boolean = false
|
|
299
|
+
held: boolean = false
|
|
289
300
|
|
|
290
301
|
/** A date on the wall clock, with nothing formatted yet. */
|
|
291
302
|
constructor() {
|
|
@@ -300,7 +311,10 @@ export class HttpDate {
|
|
|
300
311
|
* or past 9999 answers the nearer of the two ends.
|
|
301
312
|
*/
|
|
302
313
|
current(): u8[] {
|
|
303
|
-
|
|
314
|
+
let ms: i64 = this.pinnedMs
|
|
315
|
+
if (!this.pinned) {
|
|
316
|
+
ms = this.held ? this.heldMs : this.now()
|
|
317
|
+
}
|
|
304
318
|
let second: i64 = ms / 1000
|
|
305
319
|
if (ms < 0) {
|
|
306
320
|
second = 0
|
|
@@ -334,6 +348,34 @@ export class HttpDate {
|
|
|
334
348
|
this.pinned = false
|
|
335
349
|
}
|
|
336
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Reads the wall clock once and makes `current` answer that reading until
|
|
353
|
+
* `unhold`, so the responses of one serve call cost one read. A pinned date
|
|
354
|
+
* reads nothing. A second `hold` takes a fresh reading.
|
|
355
|
+
*/
|
|
356
|
+
hold(): void {
|
|
357
|
+
if (!this.pinned) {
|
|
358
|
+
this.heldMs = this.now()
|
|
359
|
+
}
|
|
360
|
+
this.held = true
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Puts `current` back on a clock read per call, for a response written outside a serve call. */
|
|
364
|
+
unhold(): void {
|
|
365
|
+
this.held = false
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** The wall clock, in milliseconds since 1970. */
|
|
369
|
+
now(): i64 {
|
|
370
|
+
this.reads += 1
|
|
371
|
+
return toI64(Date.now())
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** How many times the wall clock has been read: once per `hold`, and once per `current` outside one. */
|
|
375
|
+
clockReads(): i64 {
|
|
376
|
+
return this.reads
|
|
377
|
+
}
|
|
378
|
+
|
|
337
379
|
/** How many times the bytes have been rewritten: one per second that `current` was asked for, at most. */
|
|
338
380
|
formats(): i64 {
|
|
339
381
|
return this.rewrites
|
|
@@ -493,7 +535,11 @@ export class Http1Connection {
|
|
|
493
535
|
* put another, to share it or to pin its clock.
|
|
494
536
|
*/
|
|
495
537
|
date: HttpDate
|
|
496
|
-
/**
|
|
538
|
+
/**
|
|
539
|
+
* The request parser: its head (`methodIs`, `targetIs`, `header`, spans) is
|
|
540
|
+
* the current request's from its `H1_REQUEST` until its response is done,
|
|
541
|
+
* `H1_END` included, however much of a pipelined request is fed behind it.
|
|
542
|
+
*/
|
|
497
543
|
parser: Http1Parser
|
|
498
544
|
/**
|
|
499
545
|
* The frame decoder: made when the slot first switches to WebSocket, so a
|
|
@@ -562,10 +608,14 @@ export class Http1Connection {
|
|
|
562
608
|
/** A head has begun to arrive and is not read whole yet: `headSince` is its deadline's start. */
|
|
563
609
|
headTimed: boolean = false
|
|
564
610
|
/**
|
|
565
|
-
*
|
|
566
|
-
* `HttpCompressor.segmentMode`; 0, the default,
|
|
611
|
+
* The TCP maximum segment size the program sets for this connection, for
|
|
612
|
+
* `respondEncoded` under `HttpCompressor.segmentMode`; 0, the default,
|
|
613
|
+
* leaves it to the compressor and the socket (`segmentMss`). Each accept
|
|
614
|
+
* puts it back to 0.
|
|
567
615
|
*/
|
|
568
616
|
mss: i32 = 0
|
|
617
|
+
/** The MSS `Http1Server.accept` read from this connection's socket (`netMss`), or 0 when it reports none. */
|
|
618
|
+
socketMss: i32 = 0
|
|
569
619
|
|
|
570
620
|
/** A connection under `config`. A cap outside what the connection can work with panics. */
|
|
571
621
|
constructor(config: Http1Config) {
|
|
@@ -649,6 +699,8 @@ export class Http1Connection {
|
|
|
649
699
|
this.inputEnded = false
|
|
650
700
|
this.headTimed = false
|
|
651
701
|
this.headSince = 0
|
|
702
|
+
this.mss = 0
|
|
703
|
+
this.socketMss = 0
|
|
652
704
|
this.release()
|
|
653
705
|
this.park()
|
|
654
706
|
}
|
|
@@ -1225,7 +1277,8 @@ export class Http1Connection {
|
|
|
1225
1277
|
* `Accept-Encoding` with gzip offered (`httpAcceptEncoding`):
|
|
1226
1278
|
* `HTTP_CODING_GZIP`, `HTTP_CODING_IDENTITY` (also with no such field), or
|
|
1227
1279
|
* `HTTP_CODING_NONE` when the client refuses identity and does not take
|
|
1228
|
-
* gzip. Allocates nothing.
|
|
1280
|
+
* gzip. Allocates nothing. Read at `H1_REQUEST`, `H1_BODY` or `H1_END`
|
|
1281
|
+
* alike: the head stays the request's until its response is done.
|
|
1229
1282
|
*/
|
|
1230
1283
|
acceptEncoding(): i32 {
|
|
1231
1284
|
return this.acceptEncodingOf(HTTP_OFFER_GZIP)
|
|
@@ -1255,6 +1308,22 @@ export class Http1Connection {
|
|
|
1255
1308
|
return httpAcceptEncoding(parser.head, start, end, offered)
|
|
1256
1309
|
}
|
|
1257
1310
|
|
|
1311
|
+
/**
|
|
1312
|
+
* The segment `respondEncoded` counts in under `enc.segmentMode`: this
|
|
1313
|
+
* connection's `mss` when the program set it, else `enc.mss` when the
|
|
1314
|
+
* program set it to other than `HTTP_DEFAULT_MSS`, else the socket's
|
|
1315
|
+
* (`socketMss`), else `HTTP_DEFAULT_MSS`.
|
|
1316
|
+
*/
|
|
1317
|
+
segmentMss(enc: HttpCompressor): i32 {
|
|
1318
|
+
if (this.mss > 0) {
|
|
1319
|
+
return this.mss
|
|
1320
|
+
}
|
|
1321
|
+
if (enc.mss > 0 && enc.mss !== HTTP_DEFAULT_MSS) {
|
|
1322
|
+
return enc.mss
|
|
1323
|
+
}
|
|
1324
|
+
return this.socketMss > 0 ? this.socketMss : HTTP_DEFAULT_MSS
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1258
1327
|
/**
|
|
1259
1328
|
* Answers the current request with all of `body[off .. off + len)` in one
|
|
1260
1329
|
* call: `respond`, `write` and `end`, the body compressed by `enc` when
|
|
@@ -1263,10 +1332,12 @@ export class Http1Connection {
|
|
|
1263
1332
|
* otherwise sent as it is; `Vary: Accept-Encoding` while `enc.vary` is set
|
|
1264
1333
|
* and the choice turned on the request (`HttpCompressor.varies`). Under
|
|
1265
1334
|
* `enc.segmentMode` the compressed response goes out only when, head
|
|
1266
|
-
* included, it takes fewer segments of `
|
|
1267
|
-
*
|
|
1268
|
-
*
|
|
1269
|
-
*
|
|
1335
|
+
* included, it takes fewer segments of `segmentMss(enc)` bytes than the
|
|
1336
|
+
* body as it is, and a response that fits one segment as it is goes so
|
|
1337
|
+
* without compressing (`enc.oneSegment`); `enc.decision`
|
|
1338
|
+
* says what was chosen. A request that refuses identity (`identityRefused`)
|
|
1339
|
+
* is sent gzip whatever the length, the saving or the segments
|
|
1340
|
+
* (`enc.compressAnyway`).
|
|
1270
1341
|
* The program's fields may not name `Content-Encoding` or, with
|
|
1271
1342
|
* `enc.vary`, `Vary`. The whole response is written or none of
|
|
1272
1343
|
* it: `H1_AGAIN` when the output has no room for it yet (`H1_WRITE` says
|
|
@@ -1290,16 +1361,6 @@ export class Http1Connection {
|
|
|
1290
1361
|
return H1_CLOSED
|
|
1291
1362
|
}
|
|
1292
1363
|
const required: boolean = coding === HTTP_CODING_GZIP && this.identityRefused()
|
|
1293
|
-
let packed: i32 = required
|
|
1294
|
-
? enc.compressAnyway(coding, body, off, len)
|
|
1295
|
-
: enc.compress(coding, body, off, len)
|
|
1296
|
-
let n: i32 = len
|
|
1297
|
-
if (packed >= 0) {
|
|
1298
|
-
enc.addFields(names, values)
|
|
1299
|
-
n = packed
|
|
1300
|
-
} else {
|
|
1301
|
-
enc.identityFields(names, values)
|
|
1302
|
-
}
|
|
1303
1364
|
// `respond` and the body together, or neither: what `respond` sets is
|
|
1304
1365
|
// put back when the body does not fit behind the head. `room` first, so
|
|
1305
1366
|
// that what was sent is already moved out of the way and the `outputEnd`
|
|
@@ -1307,15 +1368,43 @@ export class Http1Connection {
|
|
|
1307
1368
|
this.room()
|
|
1308
1369
|
const outputEnd: i32 = this.outputEnd
|
|
1309
1370
|
const closeAfter: boolean = this.closeAfter
|
|
1310
|
-
let
|
|
1311
|
-
|
|
1312
|
-
|
|
1371
|
+
let packed: i32 = -1
|
|
1372
|
+
let n: i32 = len
|
|
1373
|
+
let r: i32 = 0
|
|
1374
|
+
let queued: boolean = false
|
|
1375
|
+
const mss: i32 = this.segmentMss(enc)
|
|
1376
|
+
if (!required && enc.segmentMode && enc.oneSegment(coding, len, mss)) {
|
|
1377
|
+
// A response that fits one segment as it is cannot take fewer
|
|
1378
|
+
// compressed, so it goes as it is with no encoder run, unless its head
|
|
1379
|
+
// pushes it past the segment: then it is taken back and decided below.
|
|
1380
|
+
enc.identityFields(names, values)
|
|
1381
|
+
r = this.respond(status, enc.names, enc.values, len)
|
|
1382
|
+
if (r !== 0) {
|
|
1383
|
+
return r
|
|
1384
|
+
}
|
|
1385
|
+
queued = this.outputEnd - outputEnd + len <= enc.segmentSize(mss)
|
|
1386
|
+
if (!queued) {
|
|
1387
|
+
this.unrespond(outputEnd, closeAfter)
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
if (!queued) {
|
|
1391
|
+
packed = required ? enc.compressAnyway(coding, body, off, len) : enc.compress(coding, body, off, len)
|
|
1392
|
+
if (packed >= 0) {
|
|
1393
|
+
enc.addFields(names, values)
|
|
1394
|
+
n = packed
|
|
1395
|
+
} else {
|
|
1396
|
+
enc.identityFields(names, values)
|
|
1397
|
+
}
|
|
1398
|
+
r = this.respond(status, enc.names, enc.values, n)
|
|
1399
|
+
if (r !== 0) {
|
|
1400
|
+
return r
|
|
1401
|
+
}
|
|
1313
1402
|
}
|
|
1314
1403
|
if (packed >= 0 && enc.segmentMode && !required) {
|
|
1315
1404
|
// The head just written, and the one the body as it is would get.
|
|
1316
1405
|
const head: i32 = this.outputEnd - outputEnd
|
|
1317
1406
|
const plain: i32 = enc.http1IdentityHead(head, packed, len)
|
|
1318
|
-
if (!enc.fewerSegments(plain + len, head + packed,
|
|
1407
|
+
if (!enc.fewerSegments(plain + len, head + packed, mss)) {
|
|
1319
1408
|
this.unrespond(outputEnd, closeAfter)
|
|
1320
1409
|
enc.identityFields(names, values)
|
|
1321
1410
|
packed = -1
|
|
@@ -1525,7 +1614,11 @@ export class Http1Server {
|
|
|
1525
1614
|
* accepted into, and slot 0's once `connection` has asked for it.
|
|
1526
1615
|
*/
|
|
1527
1616
|
connections: Http1Connection[]
|
|
1528
|
-
/**
|
|
1617
|
+
/**
|
|
1618
|
+
* The `Date` every slot's responses carry, formatted once a second for all
|
|
1619
|
+
* of them and read from the clock once per serve call: `readable` and
|
|
1620
|
+
* `writable` hold a reading and `flush` lets it go. `date.pin` fixes its clock.
|
|
1621
|
+
*/
|
|
1529
1622
|
date: HttpDate
|
|
1530
1623
|
/**
|
|
1531
1624
|
* Each slot's descriptor, or -1 while it is free, for the slots accepted
|
|
@@ -1669,18 +1762,27 @@ export class Http1Server {
|
|
|
1669
1762
|
this.fds[k] = fd
|
|
1670
1763
|
this.armed[k] = H1_WANT_READ
|
|
1671
1764
|
conn.restart()
|
|
1765
|
+
// Once a connection, not a request, so a keep-alive request costs no
|
|
1766
|
+
// syscall: a path whose MSS moves after the handshake is not followed.
|
|
1767
|
+
const mss: i32 = netMss(fd)
|
|
1768
|
+
if (mss > 0) {
|
|
1769
|
+
conn.socketMss = mss
|
|
1770
|
+
}
|
|
1672
1771
|
conn.lastActive = monotonicNanos()
|
|
1673
1772
|
return k
|
|
1674
1773
|
}
|
|
1675
1774
|
|
|
1676
1775
|
/**
|
|
1677
1776
|
* The socket of `slot` is readable: reads it while the connection has room
|
|
1678
|
-
* and the socket has more, then sends. Answers the interest.
|
|
1777
|
+
* and the socket has more, then sends. Answers the interest. It begins a
|
|
1778
|
+
* serve call, which the program's `flush` of the slot ends: the responses
|
|
1779
|
+
* written in between carry one clock reading as their `Date`.
|
|
1679
1780
|
*/
|
|
1680
1781
|
readable(slot: i32): i32 {
|
|
1681
1782
|
if (!this.holds(slot)) {
|
|
1682
1783
|
return H1_DONE
|
|
1683
1784
|
}
|
|
1785
|
+
this.date.hold()
|
|
1684
1786
|
const conn: Http1Connection = this.connections[slot]
|
|
1685
1787
|
const fd: i32 = this.fds[slot]
|
|
1686
1788
|
let moved: boolean = false
|
|
@@ -1714,12 +1816,16 @@ export class Http1Server {
|
|
|
1714
1816
|
conn.lastActive = now
|
|
1715
1817
|
conn.headArrived(now)
|
|
1716
1818
|
}
|
|
1717
|
-
return this.
|
|
1819
|
+
return this.settle(slot)
|
|
1718
1820
|
}
|
|
1719
1821
|
|
|
1720
|
-
/** The socket of `slot` is writable. Answers the interest. */
|
|
1822
|
+
/** The socket of `slot` is writable: sends, and begins a serve call as `readable` does. Answers the interest. */
|
|
1721
1823
|
writable(slot: i32): i32 {
|
|
1722
|
-
|
|
1824
|
+
if (!this.holds(slot)) {
|
|
1825
|
+
return H1_DONE
|
|
1826
|
+
}
|
|
1827
|
+
this.date.hold()
|
|
1828
|
+
return this.settle(slot)
|
|
1723
1829
|
}
|
|
1724
1830
|
|
|
1725
1831
|
/**
|
|
@@ -1743,9 +1849,16 @@ export class Http1Server {
|
|
|
1743
1849
|
/**
|
|
1744
1850
|
* Sends what `slot`'s connection holds until the socket would block, and
|
|
1745
1851
|
* answers the interest: `H1_DONE` once the connection is over and sent,
|
|
1746
|
-
* otherwise read while it has room and write while it holds bytes.
|
|
1852
|
+
* otherwise read while it has room and write while it holds bytes. It ends
|
|
1853
|
+
* the serve call, so a response written after it reads the clock anew.
|
|
1747
1854
|
*/
|
|
1748
1855
|
flush(slot: i32): i32 {
|
|
1856
|
+
this.date.unhold()
|
|
1857
|
+
return this.settle(slot)
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
/** `flush` within a serve call: sends, answers the interest, and keeps the clock reading. */
|
|
1861
|
+
settle(slot: i32): i32 {
|
|
1749
1862
|
if (!this.holds(slot)) {
|
|
1750
1863
|
return H1_DONE
|
|
1751
1864
|
}
|