@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.ts
CHANGED
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
* Written from RFC 9112 and RFC 9110, not ported from another implementation.
|
|
82
82
|
*/
|
|
83
83
|
import { copyBytes } from "nish/bytes"
|
|
84
|
+
import { extract, loadU8x16, u8x16 } from "nish:simd"
|
|
84
85
|
|
|
85
86
|
/** `next()`: the input so far holds no further event; feed more. */
|
|
86
87
|
export const HTTP1_NEED_MORE: i32 = 0
|
|
@@ -88,7 +89,12 @@ export const HTTP1_NEED_MORE: i32 = 0
|
|
|
88
89
|
export const HTTP1_HEAD: i32 = 1
|
|
89
90
|
/** `next()`: `bodyLen` body bytes are at `body[bodyOff ..]`, valid until the next `feed`. */
|
|
90
91
|
export const HTTP1_BODY: i32 = 2
|
|
91
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* `next()`: the request's body is complete. Its head (method, target,
|
|
94
|
+
* version, fields, `keepAlive`) still reads as this request's until the next
|
|
95
|
+
* call to `next()`, so a response written at `HTTP1_END` sees the request it
|
|
96
|
+
* answers and never the pipelined one behind it.
|
|
97
|
+
*/
|
|
92
98
|
export const HTTP1_END: i32 = 3
|
|
93
99
|
/** `next()`, from now on: the request switched protocols; see `upgradeBytes()`. */
|
|
94
100
|
export const HTTP1_UPGRADE: i32 = 4
|
|
@@ -144,7 +150,7 @@ export class Http1Parser {
|
|
|
144
150
|
// The fields are declared widest first, references before numbers before
|
|
145
151
|
// flags, so the struct carries no padding; the groups say what each is.
|
|
146
152
|
|
|
147
|
-
// --- the request: valid from its `HTTP1_HEAD` until the next
|
|
153
|
+
// --- the request: valid from its `HTTP1_HEAD` until the `next()` after its `HTTP1_END`
|
|
148
154
|
method: string = ""
|
|
149
155
|
target: string = ""
|
|
150
156
|
/** The field names, in lowercase, in the order they arrived; `values` beside them. */
|
|
@@ -213,11 +219,22 @@ export class Http1Parser {
|
|
|
213
219
|
/** The line `http1TakeLine` found is `buf[lineStart .. lineEnd)`, its CRLF dropped. */
|
|
214
220
|
lineStart: i32 = 0
|
|
215
221
|
lineEnd: i32 = 0
|
|
216
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Whether the line being scanned holds a byte a field value may not: a
|
|
224
|
+
* control byte other than HTAB, or DEL (RFC 9110 §5.5). `http1TakeLine`
|
|
225
|
+
* sets it as it looks for the line's end, so a field line's bytes are read
|
|
226
|
+
* once, not once for the LF and again for the check.
|
|
227
|
+
*/
|
|
228
|
+
lineControl: boolean = false
|
|
217
229
|
chunked: boolean = false
|
|
218
230
|
keepAlive: boolean = false
|
|
219
231
|
/** Whether the request asked to switch protocols (`upgrade` is its value when `keepText`). */
|
|
220
232
|
upgrading: boolean = false
|
|
233
|
+
/**
|
|
234
|
+
* Whether a kept-alive request ended and its head is still held for the
|
|
235
|
+
* response: the next `next()` clears it before it reads a byte more.
|
|
236
|
+
*/
|
|
237
|
+
headHeld: boolean = false
|
|
221
238
|
/**
|
|
222
239
|
* Whether `method`, `target`, `names`, `values` and `upgrade` are filled as
|
|
223
240
|
* strings. A server that must allocate nothing per request turns it off and
|
|
@@ -361,7 +378,7 @@ export class Http1Parser {
|
|
|
361
378
|
* next feed and head make or the caller puts back first.
|
|
362
379
|
*/
|
|
363
380
|
resting(): boolean {
|
|
364
|
-
return this.state === HTTP1_S_REQUEST_LINE && this.end === this.start
|
|
381
|
+
return this.state === HTTP1_S_REQUEST_LINE && this.end === this.start && !this.headHeld
|
|
365
382
|
}
|
|
366
383
|
|
|
367
384
|
/** How many bytes were fed and not consumed yet: what a caller bounds its reads by. */
|
|
@@ -485,6 +502,61 @@ const http1StartRequest = (p: Http1Parser): void => {
|
|
|
485
502
|
p.bodyTotal = 0
|
|
486
503
|
p.sectionBytes = 0
|
|
487
504
|
p.fieldCount = 0
|
|
505
|
+
p.headHeld = false
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Whether any of the eight bytes of `x` is below 32 or is 127: a control
|
|
510
|
+
* byte or DEL, which `http1TakeLine` stops at. Two of the bit tricks of
|
|
511
|
+
* "Bit Twiddling Hacks" (S. E. Anderson, public domain): a byte below `n`
|
|
512
|
+
* leaves its high bit set in `(x - n * ones) & ~x`, for `n` up to 128, and
|
|
513
|
+
* `127` is the byte that is zero in `x ^ 127 * ones`. A byte from 128 up
|
|
514
|
+
* has its high bit set in `x`, so `~x` clears it: obs-text never matches.
|
|
515
|
+
* The answer is exact for the word, though not for which byte. Each
|
|
516
|
+
* subtraction is masked to 64 bits because a `u64` is a BigInt under Node,
|
|
517
|
+
* which does not wrap; natively the mask is a no-op.
|
|
518
|
+
*/
|
|
519
|
+
const http1WordHasControl = (x: u64): boolean => {
|
|
520
|
+
const ones32: u64 = toU64(16843009)
|
|
521
|
+
const ones: u64 = (ones32 << toU64(32)) | ones32
|
|
522
|
+
const low32: u64 = 0xffffffff
|
|
523
|
+
const all: u64 = (low32 << toU64(32)) | low32
|
|
524
|
+
const high: u64 = ones * toU64(128)
|
|
525
|
+
const below: u64 = (x - ones * toU64(32)) & all & ~x & high
|
|
526
|
+
const y: u64 = x ^ (ones * toU64(127))
|
|
527
|
+
const del: u64 = (y - ones) & all & ~y & high
|
|
528
|
+
return (below | del) !== toU64(0)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Lanes `at` to `at + 7` of `v`, the first the lowest byte: eight bytes of a
|
|
533
|
+
* sixteen-byte load as one word, which `opt` folds to a bitcast of the
|
|
534
|
+
* vector's half. One load is one bounds check for sixteen bytes, where eight
|
|
535
|
+
* reads of `buf[i + k]` would be eight.
|
|
536
|
+
*/
|
|
537
|
+
const http1Lanes = (v: u8x16, at: i32): u64 => {
|
|
538
|
+
if (at === 0) {
|
|
539
|
+
return (
|
|
540
|
+
toU64(extract(v, 0)) |
|
|
541
|
+
(toU64(extract(v, 1)) << toU64(8)) |
|
|
542
|
+
(toU64(extract(v, 2)) << toU64(16)) |
|
|
543
|
+
(toU64(extract(v, 3)) << toU64(24)) |
|
|
544
|
+
(toU64(extract(v, 4)) << toU64(32)) |
|
|
545
|
+
(toU64(extract(v, 5)) << toU64(40)) |
|
|
546
|
+
(toU64(extract(v, 6)) << toU64(48)) |
|
|
547
|
+
(toU64(extract(v, 7)) << toU64(56))
|
|
548
|
+
)
|
|
549
|
+
}
|
|
550
|
+
return (
|
|
551
|
+
toU64(extract(v, 8)) |
|
|
552
|
+
(toU64(extract(v, 9)) << toU64(8)) |
|
|
553
|
+
(toU64(extract(v, 10)) << toU64(16)) |
|
|
554
|
+
(toU64(extract(v, 11)) << toU64(24)) |
|
|
555
|
+
(toU64(extract(v, 12)) << toU64(32)) |
|
|
556
|
+
(toU64(extract(v, 13)) << toU64(40)) |
|
|
557
|
+
(toU64(extract(v, 14)) << toU64(48)) |
|
|
558
|
+
(toU64(extract(v, 15)) << toU64(56))
|
|
559
|
+
)
|
|
488
560
|
}
|
|
489
561
|
|
|
490
562
|
/** `http1TakeLine`'s answer when a whole line is ready; never an event. */
|
|
@@ -494,14 +566,42 @@ const HTTP1_LINE: i32 = -1
|
|
|
494
566
|
* Finds the next line: answers `HTTP1_LINE` with it in `lineStart ..
|
|
495
567
|
* lineEnd`, `HTTP1_NEED_MORE` when no LF has arrived yet, or the refusal. A line ends in CRLF and in
|
|
496
568
|
* nothing else; a LF without its CR is a 400. A line that has not ended by
|
|
497
|
-
* `limit` bytes is refused with `overStatus`.
|
|
569
|
+
* `limit` bytes is refused with `overStatus`. On the way it sets
|
|
570
|
+
* `lineControl` when the line holds a byte no field value may, which
|
|
571
|
+
* `http1FieldLine` reads instead of scanning the value again.
|
|
498
572
|
*/
|
|
499
573
|
const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
|
|
500
574
|
const buf: u8[] = p.buf
|
|
501
575
|
const end: i32 = p.end
|
|
502
|
-
let i: i32 = p.
|
|
503
|
-
|
|
504
|
-
|
|
576
|
+
let i: i32 = p.start
|
|
577
|
+
if (p.scan > p.start) {
|
|
578
|
+
i = p.scan
|
|
579
|
+
} else {
|
|
580
|
+
p.lineControl = false
|
|
581
|
+
}
|
|
582
|
+
const stop: i32 = end < toI32(buf.length) ? end : toI32(buf.length)
|
|
583
|
+
if (i < 0) {
|
|
584
|
+
i = 0
|
|
585
|
+
}
|
|
586
|
+
while (i < stop) {
|
|
587
|
+
// Most bytes are printable: sixteen at a time while a whole vector is,
|
|
588
|
+
// then one at a time, with no test in either loop but the bytes' and the
|
|
589
|
+
// bound's.
|
|
590
|
+
while (i >= 0 && i <= stop - 16 && i <= toI32(buf.length) - 16) {
|
|
591
|
+
const v: u8x16 = loadU8x16(buf, i)
|
|
592
|
+
if (http1WordHasControl(http1Lanes(v, 0)) || http1WordHasControl(http1Lanes(v, 8))) {
|
|
593
|
+
break
|
|
594
|
+
}
|
|
595
|
+
i += 16
|
|
596
|
+
}
|
|
597
|
+
while (i >= 0 && i < stop && i < toI32(buf.length) && buf[i] >= 32 && buf[i] !== 127) {
|
|
598
|
+
i += 1
|
|
599
|
+
}
|
|
600
|
+
if (i < 0 || i >= stop || i >= toI32(buf.length)) {
|
|
601
|
+
break
|
|
602
|
+
}
|
|
603
|
+
const c: i32 = toI32(buf[i])
|
|
604
|
+
if (c === 10) {
|
|
505
605
|
if (i === p.start || buf[i - 1] !== 13) {
|
|
506
606
|
return http1Fail(p, 400, "a line ended in a bare LF")
|
|
507
607
|
}
|
|
@@ -514,31 +614,49 @@ const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
|
|
|
514
614
|
p.scan = i + 1
|
|
515
615
|
return HTTP1_LINE
|
|
516
616
|
}
|
|
617
|
+
if (c === 13 && i + 1 >= stop) {
|
|
618
|
+
// A CR at the end of the bytes so far: whether it ends the line is
|
|
619
|
+
// the next byte's to say, so the next call looks at it again.
|
|
620
|
+
break
|
|
621
|
+
}
|
|
622
|
+
if (c !== 9 && !(c === 13 && i + 1 < toI32(buf.length) && buf[i + 1] === 10)) {
|
|
623
|
+
p.lineControl = true
|
|
624
|
+
}
|
|
517
625
|
i += 1
|
|
518
626
|
}
|
|
519
|
-
p.scan =
|
|
627
|
+
p.scan = i
|
|
520
628
|
if (end - p.start > limit + 1) {
|
|
521
629
|
return http1Fail(p, overStatus, "a line over its limit")
|
|
522
630
|
}
|
|
523
631
|
return HTTP1_NEED_MORE
|
|
524
632
|
}
|
|
525
633
|
|
|
634
|
+
/**
|
|
635
|
+
* The tchars of RFC 9110 §5.6.2 as three 32-bit masks, bit `c - 32`,
|
|
636
|
+
* `c - 64` and `c - 96` of each: `!` `#`-`'` `*` `+` `-` `.` and the digits
|
|
637
|
+
* in the first (0x03ff6cfa); the upper-case letters, `^` and `_` in the
|
|
638
|
+
* second (0xc7fffffe); `` ` ``, the lower-case letters, `|` and `~` in the
|
|
639
|
+
* third (0x57ffffff). No byte below 33 or above 126 is a tchar. One shift
|
|
640
|
+
* and mask replaces a chain of fourteen comparisons on the parser's hottest
|
|
641
|
+
* path (`bench/techempower`).
|
|
642
|
+
*/
|
|
643
|
+
const HTTP1_TCHAR_32: i32 = 67071226
|
|
644
|
+
const HTTP1_TCHAR_64: i32 = -939524098
|
|
645
|
+
const HTTP1_TCHAR_96: i32 = 1476395007
|
|
646
|
+
|
|
526
647
|
/** Whether `c` is a tchar of RFC 9110 §5.6.2, the bytes a token is made of. */
|
|
527
|
-
export const http1IsTokenByte = (c: i32): boolean =>
|
|
528
|
-
(c
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
(c
|
|
533
|
-
|
|
534
|
-
c
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
c
|
|
538
|
-
|
|
539
|
-
c === 96 ||
|
|
540
|
-
c === 124 ||
|
|
541
|
-
c === 126
|
|
648
|
+
export const http1IsTokenByte = (c: i32): boolean => {
|
|
649
|
+
if (c < 32 || c > 127) {
|
|
650
|
+
return false
|
|
651
|
+
}
|
|
652
|
+
let mask: i32 = HTTP1_TCHAR_96
|
|
653
|
+
if (c < 64) {
|
|
654
|
+
mask = HTTP1_TCHAR_32
|
|
655
|
+
} else if (c < 96) {
|
|
656
|
+
mask = HTTP1_TCHAR_64
|
|
657
|
+
}
|
|
658
|
+
return ((mask >> (c & 31)) & 1) !== 0
|
|
659
|
+
}
|
|
542
660
|
|
|
543
661
|
/** Whether `c` may appear in a field value (RFC 9110 §5.5): HTAB, SP, VCHAR or obs-text. */
|
|
544
662
|
export const http1IsValueByte = (c: i32): boolean => c === 9 || (c >= 32 && c !== 127)
|
|
@@ -709,6 +827,57 @@ const http1Tally = (p: Http1Parser, name: string, word: string): Http1ListTally
|
|
|
709
827
|
return tally
|
|
710
828
|
}
|
|
711
829
|
|
|
830
|
+
/** `http1ConnectionOptions`: the Connection list names `close`. */
|
|
831
|
+
const HTTP1_CONNECTION_CLOSE: i32 = 1
|
|
832
|
+
/** `http1ConnectionOptions`: the Connection list names `keep-alive`. */
|
|
833
|
+
const HTTP1_CONNECTION_KEEP_ALIVE: i32 = 2
|
|
834
|
+
/** `http1ConnectionOptions`: the Connection list names `upgrade`. */
|
|
835
|
+
const HTTP1_CONNECTION_UPGRADE: i32 = 4
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Which of `close`, `keep-alive` and `upgrade` the Connection fields name,
|
|
839
|
+
* in any case, as `HTTP1_CONNECTION_` bits: one walk of every Connection
|
|
840
|
+
* field's list, where three `http1Tally` walks would each read it all. An
|
|
841
|
+
* element is trimmed of OWS and compared whole, as `http1TallyRun` compares.
|
|
842
|
+
*/
|
|
843
|
+
const http1ConnectionOptions = (p: Http1Parser): i32 => {
|
|
844
|
+
const spans: i32[] = p.spans
|
|
845
|
+
const head: u8[] = p.head
|
|
846
|
+
let found: i32 = 0
|
|
847
|
+
for (let i: i32 = 0; i < p.headerCount && 4 * i + 3 < toI32(spans.length); i += 1) {
|
|
848
|
+
if (!http1HeadIs(head, spans[4 * i], spans[4 * i + 1], "connection", false)) {
|
|
849
|
+
continue
|
|
850
|
+
}
|
|
851
|
+
const to: i32 = spans[4 * i + 3]
|
|
852
|
+
let start: i32 = spans[4 * i + 2]
|
|
853
|
+
for (let k: i32 = start; k <= to; k += 1) {
|
|
854
|
+
const c: i32 = k < to && k >= 0 && k < toI32(head.length) ? toI32(head[k]) : 44
|
|
855
|
+
if (c !== 44) {
|
|
856
|
+
continue
|
|
857
|
+
}
|
|
858
|
+
let a: i32 = start
|
|
859
|
+
let b: i32 = k
|
|
860
|
+
while (a >= 0 && a < b && a < toI32(head.length) && http1IsOws(toI32(head[a]))) {
|
|
861
|
+
a += 1
|
|
862
|
+
}
|
|
863
|
+
while (b > a && b - 1 >= 0 && b - 1 < toI32(head.length) && http1IsOws(toI32(head[b - 1]))) {
|
|
864
|
+
b -= 1
|
|
865
|
+
}
|
|
866
|
+
// The length picks the one word an element could be.
|
|
867
|
+
const n: i32 = b - a
|
|
868
|
+
if (n === 5 && http1HeadIs(head, a, b, "close", true)) {
|
|
869
|
+
found = found | HTTP1_CONNECTION_CLOSE
|
|
870
|
+
} else if (n === 10 && http1HeadIs(head, a, b, "keep-alive", true)) {
|
|
871
|
+
found = found | HTTP1_CONNECTION_KEEP_ALIVE
|
|
872
|
+
} else if (n === 7 && http1HeadIs(head, a, b, "upgrade", true)) {
|
|
873
|
+
found = found | HTTP1_CONNECTION_UPGRADE
|
|
874
|
+
}
|
|
875
|
+
start = k + 1
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return found
|
|
879
|
+
}
|
|
880
|
+
|
|
712
881
|
/**
|
|
713
882
|
* Whether the buffered start of a request line holds more than
|
|
714
883
|
* `HTTP1_MAX_METHOD` bytes before its first space (or line end).
|
|
@@ -817,10 +986,10 @@ const http1FieldLine = (p: Http1Parser, keep: boolean): i32 => {
|
|
|
817
986
|
return http1Fail(p, 400, "a malformed field name")
|
|
818
987
|
}
|
|
819
988
|
const nameEnd: i32 = i
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
989
|
+
// The name is tchars and the colon is field text, so a byte no value may
|
|
990
|
+
// hold anywhere on the line is one in the value.
|
|
991
|
+
if (p.lineControl) {
|
|
992
|
+
return http1Fail(p, 400, "a control character in a field value")
|
|
824
993
|
}
|
|
825
994
|
p.fieldCount += 1
|
|
826
995
|
if (p.fieldCount > p.maxHeaders) {
|
|
@@ -845,9 +1014,13 @@ const http1FieldLine = (p: Http1Parser, keep: boolean): i32 => {
|
|
|
845
1014
|
}
|
|
846
1015
|
const base: i32 = 4 * p.headerCount
|
|
847
1016
|
http1SpanSet(p, base + 3, at + (valueEnd - a))
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1017
|
+
// The last of the four is set first, so the array has room for them all.
|
|
1018
|
+
const spans: i32[] = p.spans
|
|
1019
|
+
if (base >= 0 && base + 2 < toI32(spans.length)) {
|
|
1020
|
+
spans[base] = at
|
|
1021
|
+
spans[base + 1] = at + (nameEnd - a)
|
|
1022
|
+
spans[base + 2] = at + (valueStart - a)
|
|
1023
|
+
}
|
|
851
1024
|
p.headerCount += 1
|
|
852
1025
|
if (p.keepText) {
|
|
853
1026
|
p.names.push(http1Text(buf, a, nameEnd, true))
|
|
@@ -927,12 +1100,12 @@ const http1EndOfHead = (p: Http1Parser): i32 => {
|
|
|
927
1100
|
}
|
|
928
1101
|
p.contentLength = size
|
|
929
1102
|
}
|
|
930
|
-
//
|
|
931
|
-
// for a request without a Connection field
|
|
932
|
-
const
|
|
933
|
-
const close: boolean =
|
|
934
|
-
const keep: boolean =
|
|
935
|
-
const upgradeAsked: boolean =
|
|
1103
|
+
// One walk of the Connection list finds all three options, and none is
|
|
1104
|
+
// made for a request without a Connection field.
|
|
1105
|
+
const options: i32 = connectionFields > 0 ? http1ConnectionOptions(p) : 0
|
|
1106
|
+
const close: boolean = (options & HTTP1_CONNECTION_CLOSE) !== 0
|
|
1107
|
+
const keep: boolean = (options & HTTP1_CONNECTION_KEEP_ALIVE) !== 0
|
|
1108
|
+
const upgradeAsked: boolean = (options & HTTP1_CONNECTION_UPGRADE) !== 0
|
|
936
1109
|
p.keepAlive = p.minor === 1 ? !close : keep && !close
|
|
937
1110
|
// The fields of one name joined with ", " are empty only when there is one
|
|
938
1111
|
// field and it is empty: the same test, without the join.
|
|
@@ -1092,6 +1265,11 @@ const http1Next = (p: Http1Parser): i32 => {
|
|
|
1092
1265
|
while (true) {
|
|
1093
1266
|
switch (p.state) {
|
|
1094
1267
|
case HTTP1_S_REQUEST_LINE: {
|
|
1268
|
+
// The request that ended was answered while its head was held; the
|
|
1269
|
+
// pipelined bytes behind it are only read once it is cleared.
|
|
1270
|
+
if (p.headHeld) {
|
|
1271
|
+
http1StartRequest(p)
|
|
1272
|
+
}
|
|
1095
1273
|
// A method too long is a 400, decided before the line's length can
|
|
1096
1274
|
// call it a long target and answer 414.
|
|
1097
1275
|
if (http1MethodOverrun(p)) {
|
|
@@ -1162,7 +1340,11 @@ const http1Next = (p: Http1Parser): i32 => {
|
|
|
1162
1340
|
} else if (!p.keepAlive) {
|
|
1163
1341
|
p.state = HTTP1_S_CLOSED
|
|
1164
1342
|
} else {
|
|
1165
|
-
|
|
1343
|
+
// The head stays this request's until the next call, which clears
|
|
1344
|
+
// it before reading on: a response written at HTTP1_END reads its
|
|
1345
|
+
// own method, version and fields, and no copy is made.
|
|
1346
|
+
p.state = HTTP1_S_REQUEST_LINE
|
|
1347
|
+
p.headHeld = true
|
|
1166
1348
|
}
|
|
1167
1349
|
return HTTP1_END
|
|
1168
1350
|
}
|
|
@@ -1385,9 +1567,18 @@ export const http1BytesAre = (bytes: u8[], word: string): boolean =>
|
|
|
1385
1567
|
|
|
1386
1568
|
/** How many decimal digits `n`, which is not negative, takes. */
|
|
1387
1569
|
const http1DecimalDigits = (n: i32): i32 => {
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1570
|
+
if (n < 10) {
|
|
1571
|
+
return 1
|
|
1572
|
+
}
|
|
1573
|
+
if (n < 100) {
|
|
1574
|
+
return 2
|
|
1575
|
+
}
|
|
1576
|
+
if (n < 1000) {
|
|
1577
|
+
return 3
|
|
1578
|
+
}
|
|
1579
|
+
let digits: i32 = 4
|
|
1580
|
+
let v: i32 = n / 10000
|
|
1581
|
+
while (v > 0) {
|
|
1391
1582
|
v = v / 10
|
|
1392
1583
|
digits += 1
|
|
1393
1584
|
}
|
|
@@ -1405,9 +1596,31 @@ export const http1PutText = (out: u8[], at: i32, text: string): i32 => {
|
|
|
1405
1596
|
return at + n
|
|
1406
1597
|
}
|
|
1407
1598
|
|
|
1408
|
-
/** Writes
|
|
1599
|
+
/** Writes CR LF at `out[at ..)`, likewise: two stores, where `http1PutText` walks a string. */
|
|
1600
|
+
const http1PutCrlf = (out: u8[], at: i32): i32 => {
|
|
1601
|
+
if (at >= 0 && at < toI32(out.length) - 1) {
|
|
1602
|
+
out[at] = 13
|
|
1603
|
+
out[at + 1] = 10
|
|
1604
|
+
}
|
|
1605
|
+
return at + 2
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
/** Writes `: ` at `out[at ..)`, likewise. */
|
|
1609
|
+
const http1PutColonSpace = (out: u8[], at: i32): i32 => {
|
|
1610
|
+
if (at >= 0 && at < toI32(out.length) - 1) {
|
|
1611
|
+
out[at] = 58
|
|
1612
|
+
out[at + 1] = 32
|
|
1613
|
+
}
|
|
1614
|
+
return at + 2
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
/** Writes `bytes` at `out[at ..)`, likewise, as one copy when it fits. */
|
|
1409
1618
|
const http1PutBytes = (out: u8[], at: i32, bytes: u8[]): i32 => {
|
|
1410
1619
|
const n: i32 = toI32(bytes.length)
|
|
1620
|
+
if (at >= 0 && at <= toI32(out.length) - n) {
|
|
1621
|
+
copyBytes(out, at, bytes, 0, n)
|
|
1622
|
+
return at + n
|
|
1623
|
+
}
|
|
1411
1624
|
for (let k: i32 = 0; k < n && at + k < toI32(out.length); k += 1) {
|
|
1412
1625
|
if (at + k >= 0) {
|
|
1413
1626
|
out[at + k] = bytes[k]
|
|
@@ -1505,32 +1718,42 @@ export const http1WriteResponseHead = (
|
|
|
1505
1718
|
return HTTP1_NO_ROOM
|
|
1506
1719
|
}
|
|
1507
1720
|
let end: i32 = http1PutText(out, at, "HTTP/1.1 ")
|
|
1508
|
-
|
|
1509
|
-
end
|
|
1721
|
+
// A status is three digits, checked above: no division loop for them.
|
|
1722
|
+
if (end >= 0 && end < toI32(out.length) - 3) {
|
|
1723
|
+
const hundreds: i32 = status / 100
|
|
1724
|
+
const tens: i32 = status / 10
|
|
1725
|
+
const tensDigit: i32 = tens % 10
|
|
1726
|
+
const ones: i32 = status % 10
|
|
1727
|
+
out[end] = toU8(48 + hundreds)
|
|
1728
|
+
out[end + 1] = toU8(48 + tensDigit)
|
|
1729
|
+
out[end + 2] = toU8(48 + ones)
|
|
1730
|
+
out[end + 3] = 32
|
|
1731
|
+
}
|
|
1732
|
+
end = end + 4
|
|
1510
1733
|
end = http1PutText(out, end, reason)
|
|
1511
|
-
end =
|
|
1734
|
+
end = http1PutCrlf(out, end)
|
|
1512
1735
|
for (let i: i32 = 0; i < count && i < toI32(values.length); i += 1) {
|
|
1513
1736
|
end = http1PutBytes(out, end, names[i])
|
|
1514
|
-
end =
|
|
1737
|
+
end = http1PutColonSpace(out, end)
|
|
1515
1738
|
end = http1PutBytes(out, end, values[i])
|
|
1516
|
-
end =
|
|
1739
|
+
end = http1PutCrlf(out, end)
|
|
1517
1740
|
}
|
|
1518
1741
|
if (sendDate) {
|
|
1519
1742
|
end = http1PutText(out, end, "Date: ")
|
|
1520
1743
|
end = http1PutBytes(out, end, date)
|
|
1521
|
-
end =
|
|
1744
|
+
end = http1PutCrlf(out, end)
|
|
1522
1745
|
}
|
|
1523
1746
|
if (bodyLength >= 0) {
|
|
1524
1747
|
end = http1PutText(out, end, "Content-Length: ")
|
|
1525
1748
|
end = http1PutDecimal(out, end, bodyLength)
|
|
1526
|
-
end =
|
|
1749
|
+
end = http1PutCrlf(out, end)
|
|
1527
1750
|
} else if (bodyLength === HTTP1_CHUNKED) {
|
|
1528
1751
|
end = http1PutText(out, end, "Transfer-Encoding: chunked\r\n")
|
|
1529
1752
|
}
|
|
1530
1753
|
if (toI32(connection.length) > 0) {
|
|
1531
1754
|
end = http1PutText(out, end, "Connection: ")
|
|
1532
1755
|
end = http1PutText(out, end, connection)
|
|
1533
|
-
end =
|
|
1756
|
+
end = http1PutCrlf(out, end)
|
|
1534
1757
|
}
|
|
1535
|
-
return
|
|
1758
|
+
return http1PutCrlf(out, end)
|
|
1536
1759
|
}
|