@amritk/nish-x86_64-linux 0.29.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 +3 -3
- package/bin/nish +0 -0
- package/docs/AI.md +15 -0
- package/package.json +1 -1
- package/runtime/nish.h +22 -2
- package/runtime/runtime-region.c +231 -0
- package/runtime/runtime.c +12 -2
- package/scripts/build.sh +19 -6
- package/std/README.md +2 -0
- package/std/net/http1-client.ts +270 -41
- package/std/net/http1-server.ts +68 -9
- package/std/net/http1.ts +250 -47
- package/std/net/postgres-carrier.ts +985 -0
- package/std/net/postgres.ts +297 -4
- package/std/net/upstream.ts +759 -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
|
|
@@ -218,7 +219,13 @@ export class Http1Parser {
|
|
|
218
219
|
/** The line `http1TakeLine` found is `buf[lineStart .. lineEnd)`, its CRLF dropped. */
|
|
219
220
|
lineStart: i32 = 0
|
|
220
221
|
lineEnd: i32 = 0
|
|
221
|
-
|
|
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
|
|
222
229
|
chunked: boolean = false
|
|
223
230
|
keepAlive: boolean = false
|
|
224
231
|
/** Whether the request asked to switch protocols (`upgrade` is its value when `keepText`). */
|
|
@@ -498,6 +505,60 @@ const http1StartRequest = (p: Http1Parser): void => {
|
|
|
498
505
|
p.headHeld = false
|
|
499
506
|
}
|
|
500
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
|
+
)
|
|
560
|
+
}
|
|
561
|
+
|
|
501
562
|
/** `http1TakeLine`'s answer when a whole line is ready; never an event. */
|
|
502
563
|
const HTTP1_LINE: i32 = -1
|
|
503
564
|
|
|
@@ -505,14 +566,42 @@ const HTTP1_LINE: i32 = -1
|
|
|
505
566
|
* Finds the next line: answers `HTTP1_LINE` with it in `lineStart ..
|
|
506
567
|
* lineEnd`, `HTTP1_NEED_MORE` when no LF has arrived yet, or the refusal. A line ends in CRLF and in
|
|
507
568
|
* nothing else; a LF without its CR is a 400. A line that has not ended by
|
|
508
|
-
* `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.
|
|
509
572
|
*/
|
|
510
573
|
const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
|
|
511
574
|
const buf: u8[] = p.buf
|
|
512
575
|
const end: i32 = p.end
|
|
513
|
-
let i: i32 = p.
|
|
514
|
-
|
|
515
|
-
|
|
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) {
|
|
516
605
|
if (i === p.start || buf[i - 1] !== 13) {
|
|
517
606
|
return http1Fail(p, 400, "a line ended in a bare LF")
|
|
518
607
|
}
|
|
@@ -525,31 +614,49 @@ const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
|
|
|
525
614
|
p.scan = i + 1
|
|
526
615
|
return HTTP1_LINE
|
|
527
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
|
+
}
|
|
528
625
|
i += 1
|
|
529
626
|
}
|
|
530
|
-
p.scan =
|
|
627
|
+
p.scan = i
|
|
531
628
|
if (end - p.start > limit + 1) {
|
|
532
629
|
return http1Fail(p, overStatus, "a line over its limit")
|
|
533
630
|
}
|
|
534
631
|
return HTTP1_NEED_MORE
|
|
535
632
|
}
|
|
536
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
|
+
|
|
537
647
|
/** Whether `c` is a tchar of RFC 9110 §5.6.2, the bytes a token is made of. */
|
|
538
|
-
export const http1IsTokenByte = (c: i32): boolean =>
|
|
539
|
-
(c
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
(c
|
|
544
|
-
|
|
545
|
-
c
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
c
|
|
549
|
-
|
|
550
|
-
c === 96 ||
|
|
551
|
-
c === 124 ||
|
|
552
|
-
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
|
+
}
|
|
553
660
|
|
|
554
661
|
/** Whether `c` may appear in a field value (RFC 9110 §5.5): HTAB, SP, VCHAR or obs-text. */
|
|
555
662
|
export const http1IsValueByte = (c: i32): boolean => c === 9 || (c >= 32 && c !== 127)
|
|
@@ -720,6 +827,57 @@ const http1Tally = (p: Http1Parser, name: string, word: string): Http1ListTally
|
|
|
720
827
|
return tally
|
|
721
828
|
}
|
|
722
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
|
+
|
|
723
881
|
/**
|
|
724
882
|
* Whether the buffered start of a request line holds more than
|
|
725
883
|
* `HTTP1_MAX_METHOD` bytes before its first space (or line end).
|
|
@@ -828,10 +986,10 @@ const http1FieldLine = (p: Http1Parser, keep: boolean): i32 => {
|
|
|
828
986
|
return http1Fail(p, 400, "a malformed field name")
|
|
829
987
|
}
|
|
830
988
|
const nameEnd: i32 = i
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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")
|
|
835
993
|
}
|
|
836
994
|
p.fieldCount += 1
|
|
837
995
|
if (p.fieldCount > p.maxHeaders) {
|
|
@@ -856,9 +1014,13 @@ const http1FieldLine = (p: Http1Parser, keep: boolean): i32 => {
|
|
|
856
1014
|
}
|
|
857
1015
|
const base: i32 = 4 * p.headerCount
|
|
858
1016
|
http1SpanSet(p, base + 3, at + (valueEnd - a))
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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
|
+
}
|
|
862
1024
|
p.headerCount += 1
|
|
863
1025
|
if (p.keepText) {
|
|
864
1026
|
p.names.push(http1Text(buf, a, nameEnd, true))
|
|
@@ -938,12 +1100,12 @@ const http1EndOfHead = (p: Http1Parser): i32 => {
|
|
|
938
1100
|
}
|
|
939
1101
|
p.contentLength = size
|
|
940
1102
|
}
|
|
941
|
-
//
|
|
942
|
-
// for a request without a Connection field
|
|
943
|
-
const
|
|
944
|
-
const close: boolean =
|
|
945
|
-
const keep: boolean =
|
|
946
|
-
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
|
|
947
1109
|
p.keepAlive = p.minor === 1 ? !close : keep && !close
|
|
948
1110
|
// The fields of one name joined with ", " are empty only when there is one
|
|
949
1111
|
// field and it is empty: the same test, without the join.
|
|
@@ -1405,9 +1567,18 @@ export const http1BytesAre = (bytes: u8[], word: string): boolean =>
|
|
|
1405
1567
|
|
|
1406
1568
|
/** How many decimal digits `n`, which is not negative, takes. */
|
|
1407
1569
|
const http1DecimalDigits = (n: i32): i32 => {
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
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) {
|
|
1411
1582
|
v = v / 10
|
|
1412
1583
|
digits += 1
|
|
1413
1584
|
}
|
|
@@ -1425,9 +1596,31 @@ export const http1PutText = (out: u8[], at: i32, text: string): i32 => {
|
|
|
1425
1596
|
return at + n
|
|
1426
1597
|
}
|
|
1427
1598
|
|
|
1428
|
-
/** 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. */
|
|
1429
1618
|
const http1PutBytes = (out: u8[], at: i32, bytes: u8[]): i32 => {
|
|
1430
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
|
+
}
|
|
1431
1624
|
for (let k: i32 = 0; k < n && at + k < toI32(out.length); k += 1) {
|
|
1432
1625
|
if (at + k >= 0) {
|
|
1433
1626
|
out[at + k] = bytes[k]
|
|
@@ -1525,32 +1718,42 @@ export const http1WriteResponseHead = (
|
|
|
1525
1718
|
return HTTP1_NO_ROOM
|
|
1526
1719
|
}
|
|
1527
1720
|
let end: i32 = http1PutText(out, at, "HTTP/1.1 ")
|
|
1528
|
-
|
|
1529
|
-
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
|
|
1530
1733
|
end = http1PutText(out, end, reason)
|
|
1531
|
-
end =
|
|
1734
|
+
end = http1PutCrlf(out, end)
|
|
1532
1735
|
for (let i: i32 = 0; i < count && i < toI32(values.length); i += 1) {
|
|
1533
1736
|
end = http1PutBytes(out, end, names[i])
|
|
1534
|
-
end =
|
|
1737
|
+
end = http1PutColonSpace(out, end)
|
|
1535
1738
|
end = http1PutBytes(out, end, values[i])
|
|
1536
|
-
end =
|
|
1739
|
+
end = http1PutCrlf(out, end)
|
|
1537
1740
|
}
|
|
1538
1741
|
if (sendDate) {
|
|
1539
1742
|
end = http1PutText(out, end, "Date: ")
|
|
1540
1743
|
end = http1PutBytes(out, end, date)
|
|
1541
|
-
end =
|
|
1744
|
+
end = http1PutCrlf(out, end)
|
|
1542
1745
|
}
|
|
1543
1746
|
if (bodyLength >= 0) {
|
|
1544
1747
|
end = http1PutText(out, end, "Content-Length: ")
|
|
1545
1748
|
end = http1PutDecimal(out, end, bodyLength)
|
|
1546
|
-
end =
|
|
1749
|
+
end = http1PutCrlf(out, end)
|
|
1547
1750
|
} else if (bodyLength === HTTP1_CHUNKED) {
|
|
1548
1751
|
end = http1PutText(out, end, "Transfer-Encoding: chunked\r\n")
|
|
1549
1752
|
}
|
|
1550
1753
|
if (toI32(connection.length) > 0) {
|
|
1551
1754
|
end = http1PutText(out, end, "Connection: ")
|
|
1552
1755
|
end = http1PutText(out, end, connection)
|
|
1553
|
-
end =
|
|
1756
|
+
end = http1PutCrlf(out, end)
|
|
1554
1757
|
}
|
|
1555
|
-
return
|
|
1758
|
+
return http1PutCrlf(out, end)
|
|
1556
1759
|
}
|