@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-client.ts
CHANGED
|
@@ -538,9 +538,22 @@ export class Http1ResponseParser {
|
|
|
538
538
|
// --- the response: valid from its HEAD or INFORMATIONAL until the next ----
|
|
539
539
|
/** The reason phrase, which RFC 9112 §4 says a client ignores; for a log. */
|
|
540
540
|
reason: string = ""
|
|
541
|
-
/**
|
|
541
|
+
/**
|
|
542
|
+
* The field names, in lowercase, in the order they arrived; `values` beside
|
|
543
|
+
* them, OWS trimmed. These and `reason` are filled only while the parser
|
|
544
|
+
* keeps strings, which it does until `fieldsAsSpans()`; `fields()`,
|
|
545
|
+
* `fieldName`, `fieldValue`, `reasonText` and `header` answer in either mode.
|
|
546
|
+
*/
|
|
542
547
|
names: string[]
|
|
543
548
|
values: string[]
|
|
549
|
+
/**
|
|
550
|
+
* The response's reason phrase and kept fields, copied: `head[reasonStart ..
|
|
551
|
+
* reasonEnd)` is the reason, and field `i` is `head[spans[4i] ..
|
|
552
|
+
* spans[4i + 1])`, its name in lowercase, then `head[spans[4i + 2] ..
|
|
553
|
+
* spans[4i + 3])`, its value. Both arrays are reused from response to response.
|
|
554
|
+
*/
|
|
555
|
+
head: u8[]
|
|
556
|
+
spans: i32[]
|
|
544
557
|
/** The buffer the last `HTTP1_BODY`'s bytes are in, at `bodyOff`, `bodyLen` long. */
|
|
545
558
|
body: u8[]
|
|
546
559
|
/** What was wrong, once `next()` said `HTTP1_ERROR`; for a log. */
|
|
@@ -595,6 +608,13 @@ export class Http1ResponseParser {
|
|
|
595
608
|
wasUpgrade: boolean = false
|
|
596
609
|
/** Whether `finish()` said the server closed the connection. */
|
|
597
610
|
closed: boolean = false
|
|
611
|
+
/** Whether `names`, `values` and `reason` are filled; `fieldsAsSpans()` clears it. */
|
|
612
|
+
keepStrings: boolean = true
|
|
613
|
+
/** Bytes of `head` in use, and the fields recorded in `spans`. */
|
|
614
|
+
headUsed: i32 = 0
|
|
615
|
+
spanCount: i32 = 0
|
|
616
|
+
reasonStart: i32 = 0
|
|
617
|
+
reasonEnd: i32 = 0
|
|
598
618
|
|
|
599
619
|
/**
|
|
600
620
|
* Each `i32` limit is clamped into 0 to 2^30 and `maxBody` into 0 to 2^53,
|
|
@@ -614,6 +634,46 @@ export class Http1ResponseParser {
|
|
|
614
634
|
this.body = this.buf
|
|
615
635
|
this.names = []
|
|
616
636
|
this.values = []
|
|
637
|
+
this.head = []
|
|
638
|
+
this.spans = []
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Stops filling `names`, `values` and `reason`, which then stay empty: a
|
|
643
|
+
* response's fields are read through `fields()`, `fieldName`, `fieldValue`,
|
|
644
|
+
* `reasonText` and `header`, which make a string only when asked. A parser
|
|
645
|
+
* kept for many responses allocates nothing per response this way.
|
|
646
|
+
*/
|
|
647
|
+
fieldsAsSpans(): void {
|
|
648
|
+
this.keepStrings = false
|
|
649
|
+
this.names = []
|
|
650
|
+
this.values = []
|
|
651
|
+
this.reason = ""
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Makes the parser as it was new, for the next connection, keeping its
|
|
656
|
+
* limits, its mode and its buffers: what was fed and not read is dropped,
|
|
657
|
+
* and an error or a close is forgotten.
|
|
658
|
+
*/
|
|
659
|
+
reset(): void {
|
|
660
|
+
this.state = HTTP1_CLIENT_S_STATUS_LINE
|
|
661
|
+
this.start = 0
|
|
662
|
+
this.end = 0
|
|
663
|
+
this.scan = 0
|
|
664
|
+
this.lineStart = 0
|
|
665
|
+
this.lineEnd = 0
|
|
666
|
+
this.closed = false
|
|
667
|
+
this.error = 0
|
|
668
|
+
this.errorText = ""
|
|
669
|
+
this.informational = 0
|
|
670
|
+
this.wasHead = false
|
|
671
|
+
this.wasConnect = false
|
|
672
|
+
this.wasUpgrade = false
|
|
673
|
+
http1ClientStartHead(this)
|
|
674
|
+
this.left = toI64(0)
|
|
675
|
+
this.bodyOff = 0
|
|
676
|
+
this.body = this.buf
|
|
617
677
|
}
|
|
618
678
|
|
|
619
679
|
/**
|
|
@@ -704,16 +764,43 @@ export class Http1ResponseParser {
|
|
|
704
764
|
|
|
705
765
|
/** The value of the first field named `name` (any case), or `null`. */
|
|
706
766
|
header(name: string): string | null {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
if (http1SameWord(name, 0, n, names[i])) {
|
|
711
|
-
return this.values[i]
|
|
767
|
+
for (let i: i32 = 0; i < this.spanCount; i += 1) {
|
|
768
|
+
if (http1ClientHeadSameWord(this.head, this.spans[i * 4], this.spans[i * 4 + 1], name)) {
|
|
769
|
+
return http1Text(this.head, this.spans[i * 4 + 2], this.spans[i * 4 + 3], false)
|
|
712
770
|
}
|
|
713
771
|
}
|
|
714
772
|
return null
|
|
715
773
|
}
|
|
716
774
|
|
|
775
|
+
/** How many fields the response last read has; trailers are not kept. */
|
|
776
|
+
fields(): i32 {
|
|
777
|
+
return this.spanCount
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/** Field `i`'s name, in lowercase, as a fresh string; "" past the last field. */
|
|
781
|
+
fieldName(i: i32): string {
|
|
782
|
+
if (i < 0 || i >= this.spanCount) {
|
|
783
|
+
return ""
|
|
784
|
+
}
|
|
785
|
+
return http1Text(this.head, this.spans[i * 4], this.spans[i * 4 + 1], false)
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/** Field `i`'s value, OWS trimmed, as a fresh string; "" past the last field. */
|
|
789
|
+
fieldValue(i: i32): string {
|
|
790
|
+
if (i < 0 || i >= this.spanCount) {
|
|
791
|
+
return ""
|
|
792
|
+
}
|
|
793
|
+
return http1Text(this.head, this.spans[i * 4 + 2], this.spans[i * 4 + 3], false)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/** The reason phrase, as a fresh string. */
|
|
797
|
+
reasonText(): string {
|
|
798
|
+
if (this.reasonEnd <= this.reasonStart) {
|
|
799
|
+
return ""
|
|
800
|
+
}
|
|
801
|
+
return http1Text(this.head, this.reasonStart, this.reasonEnd, false)
|
|
802
|
+
}
|
|
803
|
+
|
|
717
804
|
/** How many bytes were fed and not consumed yet. */
|
|
718
805
|
buffered(): i32 {
|
|
719
806
|
return this.end - this.start
|
|
@@ -777,10 +864,17 @@ const http1ClientFail = (p: Http1ResponseParser, code: i32, text: string): i32 =
|
|
|
777
864
|
/** Clears the per-response fields for the status line just taken. */
|
|
778
865
|
const http1ClientStartHead = (p: Http1ResponseParser): void => {
|
|
779
866
|
p.status = 0
|
|
780
|
-
p.reason = ""
|
|
781
867
|
p.minor = 1
|
|
782
|
-
|
|
783
|
-
p.
|
|
868
|
+
// Fresh arrays, not cleared ones: a caller may hold the last response's.
|
|
869
|
+
if (p.keepStrings) {
|
|
870
|
+
p.reason = ""
|
|
871
|
+
p.names = []
|
|
872
|
+
p.values = []
|
|
873
|
+
}
|
|
874
|
+
p.headUsed = 0
|
|
875
|
+
p.spanCount = 0
|
|
876
|
+
p.reasonStart = 0
|
|
877
|
+
p.reasonEnd = 0
|
|
784
878
|
p.contentLength = toI64(-1)
|
|
785
879
|
p.chunked = false
|
|
786
880
|
p.persistent = false
|
|
@@ -872,7 +966,13 @@ const http1ClientStatusLine = (p: Http1ResponseParser): i32 => {
|
|
|
872
966
|
}
|
|
873
967
|
p.minor = buf[a + 7] === 48 ? 0 : 1
|
|
874
968
|
p.status = status
|
|
875
|
-
|
|
969
|
+
if (b - a > 13) {
|
|
970
|
+
p.reasonStart = http1ClientKeep(p, buf, a + 13, b, false)
|
|
971
|
+
p.reasonEnd = p.headUsed
|
|
972
|
+
if (p.keepStrings) {
|
|
973
|
+
p.reason = http1Text(buf, a + 13, b, false)
|
|
974
|
+
}
|
|
975
|
+
}
|
|
876
976
|
p.state = HTTP1_CLIENT_S_FIELDS
|
|
877
977
|
return HTTP1_NEED_MORE
|
|
878
978
|
}
|
|
@@ -925,8 +1025,13 @@ const http1ClientFieldLine = (p: Http1ResponseParser, keep: boolean): i32 => {
|
|
|
925
1025
|
) {
|
|
926
1026
|
valueEnd -= 1
|
|
927
1027
|
}
|
|
928
|
-
p
|
|
929
|
-
p
|
|
1028
|
+
const name: i32 = http1ClientKeep(p, buf, a, nameEnd, true)
|
|
1029
|
+
const value: i32 = http1ClientKeep(p, buf, valueStart, valueEnd, false)
|
|
1030
|
+
http1ClientSpan(p, name, value, value + (valueEnd - valueStart))
|
|
1031
|
+
if (p.keepStrings) {
|
|
1032
|
+
p.names.push(http1Text(buf, a, nameEnd, true))
|
|
1033
|
+
p.values.push(http1Text(buf, valueStart, valueEnd, false))
|
|
1034
|
+
}
|
|
930
1035
|
}
|
|
931
1036
|
return HTTP1_NEED_MORE
|
|
932
1037
|
}
|
|
@@ -940,20 +1045,20 @@ const http1ClientFieldLine = (p: Http1ResponseParser, keep: boolean): i32 => {
|
|
|
940
1045
|
* value past 2^53 is saturated to 2^53 + 1: over any `maxBody`. Saturated
|
|
941
1046
|
* values cannot be told apart, so one seen with any other value is -2.
|
|
942
1047
|
*/
|
|
943
|
-
const http1ClientLengthList = (
|
|
944
|
-
const n: i32 = toI32(
|
|
1048
|
+
const http1ClientLengthList = (head: u8[], from: i32, to: i32, seen: i64): i64 => {
|
|
1049
|
+
const n: i32 = to < toI32(head.length) ? to : toI32(head.length)
|
|
945
1050
|
let found: i64 = seen
|
|
946
|
-
let start: i32 =
|
|
947
|
-
for (let k: i32 =
|
|
948
|
-
if (k === n ||
|
|
949
|
-
const a: i32 =
|
|
950
|
-
const b: i32 =
|
|
1051
|
+
let start: i32 = from
|
|
1052
|
+
for (let k: i32 = from; k <= n; k += 1) {
|
|
1053
|
+
if (k === n || (k >= 0 && k < toI32(head.length) && head[k] === 44)) {
|
|
1054
|
+
const a: i32 = http1ClientHeadTrimStart(head, start, k)
|
|
1055
|
+
const b: i32 = http1ClientHeadTrimEnd(head, a, k)
|
|
951
1056
|
if (a === b) {
|
|
952
1057
|
return toI64(-1)
|
|
953
1058
|
}
|
|
954
1059
|
let v: i64 = toI64(0)
|
|
955
|
-
for (let j: i32 = a; j >= 0 && j < b && j <
|
|
956
|
-
const c: i32 = toI32(
|
|
1060
|
+
for (let j: i32 = a; j >= 0 && j < b && j < toI32(head.length); j += 1) {
|
|
1061
|
+
const c: i32 = toI32(head[j])
|
|
957
1062
|
if (c < 48 || c > 57) {
|
|
958
1063
|
return toI64(-1)
|
|
959
1064
|
}
|
|
@@ -982,20 +1087,19 @@ const http1ClientTally = (p: Http1ResponseParser, name: string, word: string): i
|
|
|
982
1087
|
let elements: i32 = 0
|
|
983
1088
|
let matches: i32 = 0
|
|
984
1089
|
let last: boolean = false
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
let
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const
|
|
995
|
-
const b: i32 = http1ClientTrimEnd(value, a, k)
|
|
1090
|
+
const head: u8[] = p.head
|
|
1091
|
+
for (let i: i32 = 0; i < p.spanCount; i += 1) {
|
|
1092
|
+
if (http1ClientHeadIs(head, p.spans[i * 4], p.spans[i * 4 + 1], name)) {
|
|
1093
|
+
const from: i32 = p.spans[i * 4 + 2]
|
|
1094
|
+
const n: i32 = p.spans[i * 4 + 3] < toI32(head.length) ? p.spans[i * 4 + 3] : toI32(head.length)
|
|
1095
|
+
let start: i32 = from
|
|
1096
|
+
for (let k: i32 = from; k <= n; k += 1) {
|
|
1097
|
+
if (k === n || (k >= 0 && k < toI32(head.length) && head[k] === 44)) {
|
|
1098
|
+
const a: i32 = http1ClientHeadTrimStart(head, start, k)
|
|
1099
|
+
const b: i32 = http1ClientHeadTrimEnd(head, a, k)
|
|
996
1100
|
if (b > a) {
|
|
997
1101
|
elements += 1
|
|
998
|
-
last =
|
|
1102
|
+
last = http1ClientHeadSameWord(head, a, b, word)
|
|
999
1103
|
if (last) {
|
|
1000
1104
|
matches += 1
|
|
1001
1105
|
}
|
|
@@ -1010,15 +1114,139 @@ const http1ClientTally = (p: Http1ResponseParser, name: string, word: string): i
|
|
|
1010
1114
|
|
|
1011
1115
|
/** Whether a `Connection` field of the response lists `word`. */
|
|
1012
1116
|
const http1ClientConnectionHas = (p: Http1ResponseParser, word: string): boolean => {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1117
|
+
for (let i: i32 = 0; i < p.spanCount; i += 1) {
|
|
1118
|
+
if (
|
|
1119
|
+
http1ClientHeadIs(p.head, p.spans[i * 4], p.spans[i * 4 + 1], "connection") &&
|
|
1120
|
+
http1ClientHeadListHas(p.head, p.spans[i * 4 + 2], p.spans[i * 4 + 3], word)
|
|
1121
|
+
) {
|
|
1016
1122
|
return true
|
|
1017
1123
|
}
|
|
1018
1124
|
}
|
|
1019
1125
|
return false
|
|
1020
1126
|
}
|
|
1021
1127
|
|
|
1128
|
+
/**
|
|
1129
|
+
* Copies `buf[a .. b)` to the end of `p.head`, lowercased when `lower`, and
|
|
1130
|
+
* answers where the copy starts. `head` grows by doubling and is kept, so a
|
|
1131
|
+
* parser reused for many responses stops growing at the largest head.
|
|
1132
|
+
*/
|
|
1133
|
+
const http1ClientKeep = (p: Http1ResponseParser, buf: u8[], a: i32, b: i32, lower: boolean): i32 => {
|
|
1134
|
+
const n: i32 = b - a
|
|
1135
|
+
const at: i32 = p.headUsed
|
|
1136
|
+
if (n > toI32(p.head.length) - at) {
|
|
1137
|
+
let size: i32 = toI32(p.head.length) < 256 ? 256 : toI32(p.head.length)
|
|
1138
|
+
while (size - at < n) {
|
|
1139
|
+
if (size > 1073741823) {
|
|
1140
|
+
panic("Http1ResponseParser: more than 2^30 bytes of head")
|
|
1141
|
+
}
|
|
1142
|
+
size = size * 2
|
|
1143
|
+
}
|
|
1144
|
+
const grown: u8[] = new Array<u8>(size)
|
|
1145
|
+
copyBytes(grown, 0, p.head, 0, at)
|
|
1146
|
+
p.head = grown
|
|
1147
|
+
}
|
|
1148
|
+
copyBytes(p.head, at, buf, a, n)
|
|
1149
|
+
if (lower) {
|
|
1150
|
+
const head: u8[] = p.head
|
|
1151
|
+
for (let k: i32 = at; k >= 0 && k < at + n && k < toI32(head.length); k += 1) {
|
|
1152
|
+
const c: i32 = toI32(head[k])
|
|
1153
|
+
if (c >= 65 && c <= 90) {
|
|
1154
|
+
head[k] = toU8(c + 32)
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
p.headUsed = at + n
|
|
1159
|
+
return at
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/** Records the field whose name starts at `name` and whose value is `head[value .. valueEnd)`. */
|
|
1163
|
+
const http1ClientSpan = (p: Http1ResponseParser, name: i32, value: i32, valueEnd: i32): void => {
|
|
1164
|
+
const at: i32 = p.spanCount * 4
|
|
1165
|
+
if (at + 4 > toI32(p.spans.length)) {
|
|
1166
|
+
p.spans.push(name)
|
|
1167
|
+
p.spans.push(value)
|
|
1168
|
+
p.spans.push(value)
|
|
1169
|
+
p.spans.push(valueEnd)
|
|
1170
|
+
} else {
|
|
1171
|
+
p.spans[at] = name
|
|
1172
|
+
p.spans[at + 1] = value
|
|
1173
|
+
p.spans[at + 2] = value
|
|
1174
|
+
p.spans[at + 3] = valueEnd
|
|
1175
|
+
}
|
|
1176
|
+
p.spanCount += 1
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/** Whether `head[a .. b)` is `word`, byte for byte. */
|
|
1180
|
+
const http1ClientHeadIs = (head: u8[], a: i32, b: i32, word: string): boolean => {
|
|
1181
|
+
const n: i32 = toI32(word.length)
|
|
1182
|
+
if (b - a !== n || a < 0 || b > toI32(head.length)) {
|
|
1183
|
+
return false
|
|
1184
|
+
}
|
|
1185
|
+
for (let k: i32 = 0; k < n; k += 1) {
|
|
1186
|
+
if (toI32(head[a + k]) !== toI32(word.charCodeAt(k))) {
|
|
1187
|
+
return false
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return true
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
/** Whether `head[a .. b)` is `word` in any case, ASCII letters folded. */
|
|
1194
|
+
const http1ClientHeadSameWord = (head: u8[], a: i32, b: i32, word: string): boolean => {
|
|
1195
|
+
const n: i32 = toI32(word.length)
|
|
1196
|
+
if (b - a !== n || a < 0 || b > toI32(head.length)) {
|
|
1197
|
+
return false
|
|
1198
|
+
}
|
|
1199
|
+
for (let k: i32 = 0; k < n; k += 1) {
|
|
1200
|
+
let x: i32 = toI32(head[a + k])
|
|
1201
|
+
let y: i32 = toI32(word.charCodeAt(k))
|
|
1202
|
+
if (x >= 65 && x <= 90) {
|
|
1203
|
+
x += 32
|
|
1204
|
+
}
|
|
1205
|
+
if (y >= 65 && y <= 90) {
|
|
1206
|
+
y += 32
|
|
1207
|
+
}
|
|
1208
|
+
if (x !== y) {
|
|
1209
|
+
return false
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
return true
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
/** Where `head[a .. b)` starts once its leading OWS is dropped. */
|
|
1216
|
+
const http1ClientHeadTrimStart = (head: u8[], a: i32, b: i32): i32 => {
|
|
1217
|
+
let i: i32 = a
|
|
1218
|
+
while (i >= 0 && i < b && i < toI32(head.length) && http1IsOws(toI32(head[i]))) {
|
|
1219
|
+
i += 1
|
|
1220
|
+
}
|
|
1221
|
+
return i
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/** Where `head[a .. b)` ends once its trailing OWS is dropped. */
|
|
1225
|
+
const http1ClientHeadTrimEnd = (head: u8[], a: i32, b: i32): i32 => {
|
|
1226
|
+
let i: i32 = b
|
|
1227
|
+
while (i > a && i > 0 && i <= toI32(head.length) && http1IsOws(toI32(head[i - 1]))) {
|
|
1228
|
+
i -= 1
|
|
1229
|
+
}
|
|
1230
|
+
return i
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/** Whether the comma-separated list in `head[from .. to)` holds `word` in any case, each element trimmed of OWS. */
|
|
1234
|
+
const http1ClientHeadListHas = (head: u8[], from: i32, to: i32, word: string): boolean => {
|
|
1235
|
+
const n: i32 = to < toI32(head.length) ? to : toI32(head.length)
|
|
1236
|
+
let start: i32 = from
|
|
1237
|
+
for (let k: i32 = from; k <= n; k += 1) {
|
|
1238
|
+
if (k === n || (k >= 0 && k < toI32(head.length) && head[k] === 44)) {
|
|
1239
|
+
const a: i32 = http1ClientHeadTrimStart(head, start, k)
|
|
1240
|
+
const b: i32 = http1ClientHeadTrimEnd(head, a, k)
|
|
1241
|
+
if (http1ClientHeadSameWord(head, a, b, word)) {
|
|
1242
|
+
return true
|
|
1243
|
+
}
|
|
1244
|
+
start = k + 1
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return false
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1022
1250
|
/**
|
|
1023
1251
|
* A header section has ended: answer `HTTP1_INFORMATIONAL` for a 1xx, or
|
|
1024
1252
|
* decide the final response's framing and the connection's fate (RFC 9112
|
|
@@ -1052,12 +1280,13 @@ const http1ClientEndOfHead = (p: Http1ResponseParser): i32 => {
|
|
|
1052
1280
|
}
|
|
1053
1281
|
let codings: boolean = false
|
|
1054
1282
|
let length: i64 = toI64(-1)
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1283
|
+
for (let i: i32 = 0; i < p.spanCount; i += 1) {
|
|
1284
|
+
const a: i32 = p.spans[i * 4]
|
|
1285
|
+
const b: i32 = p.spans[i * 4 + 1]
|
|
1286
|
+
if (http1ClientHeadIs(p.head, a, b, "transfer-encoding")) {
|
|
1058
1287
|
codings = true
|
|
1059
|
-
} else if (
|
|
1060
|
-
length = http1ClientLengthList(p.
|
|
1288
|
+
} else if (http1ClientHeadIs(p.head, a, b, "content-length")) {
|
|
1289
|
+
length = http1ClientLengthList(p.head, p.spans[i * 4 + 2], p.spans[i * 4 + 3], length)
|
|
1061
1290
|
if (length === toI64(-2)) {
|
|
1062
1291
|
return http1ClientFail(
|
|
1063
1292
|
p,
|
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,
|
|
@@ -275,7 +279,8 @@ const http1ServerCopy = (out: u8[], at: i32, src: u8[], off: i32, n: i32): i32 =
|
|
|
275
279
|
* it calls, `httpDateWrite`, stays in `nish/net/http-fields`, which compiles
|
|
276
280
|
* for wasm32 (the split `nish/crypto/x509` and `x509-random` make). `pin`
|
|
277
281
|
* replaces the clock with one instant, which is how a test asserts a response
|
|
278
|
-
* 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.
|
|
279
284
|
*/
|
|
280
285
|
export class HttpDate {
|
|
281
286
|
/** The IMF-fixdate of `second`: what `current` answers, made once. */
|
|
@@ -284,9 +289,14 @@ export class HttpDate {
|
|
|
284
289
|
second: i64 = -1
|
|
285
290
|
/** The instant `pin` fixed, in milliseconds since 1970, while `pinned`. */
|
|
286
291
|
pinnedMs: i64 = 0
|
|
292
|
+
/** The clock reading `hold` took, in milliseconds since 1970, while `held`. */
|
|
293
|
+
heldMs: i64 = 0
|
|
287
294
|
/** How many times `bytes` has been rewritten. */
|
|
288
295
|
rewrites: i64 = 0
|
|
296
|
+
/** How many times the wall clock has been read. */
|
|
297
|
+
reads: i64 = 0
|
|
289
298
|
pinned: boolean = false
|
|
299
|
+
held: boolean = false
|
|
290
300
|
|
|
291
301
|
/** A date on the wall clock, with nothing formatted yet. */
|
|
292
302
|
constructor() {
|
|
@@ -301,7 +311,10 @@ export class HttpDate {
|
|
|
301
311
|
* or past 9999 answers the nearer of the two ends.
|
|
302
312
|
*/
|
|
303
313
|
current(): u8[] {
|
|
304
|
-
|
|
314
|
+
let ms: i64 = this.pinnedMs
|
|
315
|
+
if (!this.pinned) {
|
|
316
|
+
ms = this.held ? this.heldMs : this.now()
|
|
317
|
+
}
|
|
305
318
|
let second: i64 = ms / 1000
|
|
306
319
|
if (ms < 0) {
|
|
307
320
|
second = 0
|
|
@@ -335,6 +348,34 @@ export class HttpDate {
|
|
|
335
348
|
this.pinned = false
|
|
336
349
|
}
|
|
337
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
|
+
|
|
338
379
|
/** How many times the bytes have been rewritten: one per second that `current` was asked for, at most. */
|
|
339
380
|
formats(): i64 {
|
|
340
381
|
return this.rewrites
|
|
@@ -1573,7 +1614,11 @@ export class Http1Server {
|
|
|
1573
1614
|
* accepted into, and slot 0's once `connection` has asked for it.
|
|
1574
1615
|
*/
|
|
1575
1616
|
connections: Http1Connection[]
|
|
1576
|
-
/**
|
|
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
|
+
*/
|
|
1577
1622
|
date: HttpDate
|
|
1578
1623
|
/**
|
|
1579
1624
|
* Each slot's descriptor, or -1 while it is free, for the slots accepted
|
|
@@ -1729,12 +1774,15 @@ export class Http1Server {
|
|
|
1729
1774
|
|
|
1730
1775
|
/**
|
|
1731
1776
|
* The socket of `slot` is readable: reads it while the connection has room
|
|
1732
|
-
* 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`.
|
|
1733
1780
|
*/
|
|
1734
1781
|
readable(slot: i32): i32 {
|
|
1735
1782
|
if (!this.holds(slot)) {
|
|
1736
1783
|
return H1_DONE
|
|
1737
1784
|
}
|
|
1785
|
+
this.date.hold()
|
|
1738
1786
|
const conn: Http1Connection = this.connections[slot]
|
|
1739
1787
|
const fd: i32 = this.fds[slot]
|
|
1740
1788
|
let moved: boolean = false
|
|
@@ -1768,12 +1816,16 @@ export class Http1Server {
|
|
|
1768
1816
|
conn.lastActive = now
|
|
1769
1817
|
conn.headArrived(now)
|
|
1770
1818
|
}
|
|
1771
|
-
return this.
|
|
1819
|
+
return this.settle(slot)
|
|
1772
1820
|
}
|
|
1773
1821
|
|
|
1774
|
-
/** 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. */
|
|
1775
1823
|
writable(slot: i32): i32 {
|
|
1776
|
-
|
|
1824
|
+
if (!this.holds(slot)) {
|
|
1825
|
+
return H1_DONE
|
|
1826
|
+
}
|
|
1827
|
+
this.date.hold()
|
|
1828
|
+
return this.settle(slot)
|
|
1777
1829
|
}
|
|
1778
1830
|
|
|
1779
1831
|
/**
|
|
@@ -1797,9 +1849,16 @@ export class Http1Server {
|
|
|
1797
1849
|
/**
|
|
1798
1850
|
* Sends what `slot`'s connection holds until the socket would block, and
|
|
1799
1851
|
* answers the interest: `H1_DONE` once the connection is over and sent,
|
|
1800
|
-
* 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.
|
|
1801
1854
|
*/
|
|
1802
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 {
|
|
1803
1862
|
if (!this.holds(slot)) {
|
|
1804
1863
|
return H1_DONE
|
|
1805
1864
|
}
|