@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/http-client.ts
CHANGED
|
@@ -83,6 +83,7 @@ import { Url, urlDecode, urlDefaultPort, urlOrigin, urlParse, urlResolve } from
|
|
|
83
83
|
import { resolveHost } from "nish/net/dns"
|
|
84
84
|
import { DNS_ADDRESS_BYTES } from "nish/net/dns-message"
|
|
85
85
|
import { DNS_RESOLVE_MAX_ADDRESSES, DNS_RESOLVE_TIMEOUT } from "nish/net/dns-resolver"
|
|
86
|
+
import { OutboundPolicy, outboundAddressText, outboundFilter, outboundHostAllowed } from "nish/net/outbound"
|
|
86
87
|
import {
|
|
87
88
|
connectResult,
|
|
88
89
|
netClose,
|
|
@@ -136,6 +137,8 @@ export const HTTP_E_REQUEST: i32 = 14
|
|
|
136
137
|
export const HTTP_E_IO: i32 = 15
|
|
137
138
|
/** A proxy setting that is not an `http://` URL with a host. */
|
|
138
139
|
export const HTTP_E_PROXY: i32 = 16
|
|
140
|
+
/** The outbound policy refused the URL's host or every address it resolved to. */
|
|
141
|
+
export const HTTP_E_BLOCKED: i32 = 17
|
|
139
142
|
|
|
140
143
|
/** A typed zero: a bare literal is an `f64` under `--number-mode f64`. */
|
|
141
144
|
const HTTP_CLIENT_ZERO: i32 = 0
|
|
@@ -185,6 +188,7 @@ export const httpErrorName = (code: i32): string => {
|
|
|
185
188
|
"request",
|
|
186
189
|
"io",
|
|
187
190
|
"proxy",
|
|
191
|
+
"blocked",
|
|
188
192
|
]
|
|
189
193
|
return code >= 0 && code < toI32(names.length) ? names[code] : "unknown"
|
|
190
194
|
}
|
|
@@ -230,6 +234,12 @@ export class HttpOptions {
|
|
|
230
234
|
maxBodyBytes: i64 = 268435456
|
|
231
235
|
/** A PEM file of the roots an https server's chain must end at; null for the system's. */
|
|
232
236
|
rootsPemPath: string | null = null
|
|
237
|
+
/**
|
|
238
|
+
* For a URL the program did not choose: only the addresses and hosts the
|
|
239
|
+
* policy allows are reached, no proxy is used, and its caps apply (see
|
|
240
|
+
* `nish/net/outbound`). Null for none.
|
|
241
|
+
*/
|
|
242
|
+
outbound: OutboundPolicy | null = null
|
|
233
243
|
/** The budget for the lookup and the connect together; 0 or less for none but the total. */
|
|
234
244
|
connectTimeoutMs: i32 = 10000
|
|
235
245
|
/** The longest wait for the server to send or take a byte; 0 or less for none. */
|
|
@@ -371,6 +381,8 @@ export class HttpClient {
|
|
|
371
381
|
noProxy: string = ""
|
|
372
382
|
/** Where `fd` leads: an origin, or the proxy an http request went through. */
|
|
373
383
|
route: string = ""
|
|
384
|
+
/** The outbound policy `fd` was dialled under; a connection is reused only under the same one. */
|
|
385
|
+
routePolicy: OutboundPolicy | null = null
|
|
374
386
|
parser: Http1ResponseParser
|
|
375
387
|
/** The TLS connection over `fd` to an https origin; null over plain http. */
|
|
376
388
|
tls: TlsRecordClient | null = null
|
|
@@ -442,13 +454,18 @@ export class HttpClient {
|
|
|
442
454
|
this.drop()
|
|
443
455
|
}
|
|
444
456
|
this.generation += 1
|
|
445
|
-
const total: i32 = this.options
|
|
457
|
+
const total: i32 = httpClientTotalMs(this.options)
|
|
446
458
|
this.deadline = total > 0 ? monotonicNanos() + toI64(total) * HTTP_CLIENT_NS_PER_MS : HTTP_CLIENT_NEVER
|
|
447
459
|
let url: Url | null = urlParse(req.url)
|
|
448
460
|
if (url === null || (url.scheme !== "http" && url.scheme !== "https")) {
|
|
449
461
|
this.fail(HTTP_E_URL, `not an absolute http or https URL: ${httpClientRedact(url)}`)
|
|
450
462
|
return null
|
|
451
463
|
}
|
|
464
|
+
const refusal: string = this.hostRefusal(url)
|
|
465
|
+
if (refusal !== "") {
|
|
466
|
+
this.fail(HTTP_E_BLOCKED, refusal)
|
|
467
|
+
return null
|
|
468
|
+
}
|
|
452
469
|
let method: string = req.method
|
|
453
470
|
let body: u8[] | null = req.body
|
|
454
471
|
const names: string[] = []
|
|
@@ -468,10 +485,11 @@ export class HttpClient {
|
|
|
468
485
|
return res
|
|
469
486
|
}
|
|
470
487
|
const next: Url | null = urlResolve(url, location)
|
|
471
|
-
|
|
488
|
+
const maxRedirects: i32 = httpClientMaxRedirects(this.options)
|
|
489
|
+
if (hops >= maxRedirects) {
|
|
472
490
|
return this.refuse(
|
|
473
491
|
HTTP_E_TOO_MANY_REDIRECTS,
|
|
474
|
-
`more than ${
|
|
492
|
+
`more than ${maxRedirects} redirects, the last to ${httpClientRedact(next)}`
|
|
475
493
|
)
|
|
476
494
|
}
|
|
477
495
|
if (next === null || (next.scheme !== "http" && next.scheme !== "https")) {
|
|
@@ -486,6 +504,10 @@ export class HttpClient {
|
|
|
486
504
|
`a redirect from https to http (${httpClientRedact(next)})`
|
|
487
505
|
)
|
|
488
506
|
}
|
|
507
|
+
const hopRefusal: string = this.hostRefusal(next)
|
|
508
|
+
if (hopRefusal !== "") {
|
|
509
|
+
return this.refuse(HTTP_E_BLOCKED, hopRefusal)
|
|
510
|
+
}
|
|
489
511
|
const from: string | null = urlOrigin(url)
|
|
490
512
|
const to: string | null = urlOrigin(next)
|
|
491
513
|
if (from === null || to === null || from !== to) {
|
|
@@ -517,6 +539,15 @@ export class HttpClient {
|
|
|
517
539
|
return null
|
|
518
540
|
}
|
|
519
541
|
|
|
542
|
+
/** Why the outbound policy refuses to let a URL name `url`'s host; "" when there is no policy or it allows the host. */
|
|
543
|
+
hostRefusal(url: Url): string {
|
|
544
|
+
const policy: OutboundPolicy | null = this.options.outbound
|
|
545
|
+
if (policy === null || outboundHostAllowed(policy, url.host)) {
|
|
546
|
+
return ""
|
|
547
|
+
}
|
|
548
|
+
return `${httpClientRedact(url)} names a host the outbound policy does not allow`
|
|
549
|
+
}
|
|
550
|
+
|
|
520
551
|
/** Closes the connection; the client can still fetch, on a new one. */
|
|
521
552
|
close(): void {
|
|
522
553
|
this.drop()
|
|
@@ -564,14 +595,11 @@ export class HttpClient {
|
|
|
564
595
|
*/
|
|
565
596
|
open(url: Url): boolean {
|
|
566
597
|
const port: i32 = url.port >= 0 ? url.port : urlDefaultPort(url.scheme)
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
this.httpsProxy,
|
|
573
|
-
this.noProxy
|
|
574
|
-
)
|
|
598
|
+
// A proxy would look the name up itself, past the policy's address check.
|
|
599
|
+
const proxyText: string =
|
|
600
|
+
this.options.outbound !== null
|
|
601
|
+
? ""
|
|
602
|
+
: httpProxyFor(url.scheme, url.host, port, this.httpProxy, this.httpsProxy, this.noProxy)
|
|
575
603
|
let proxy: Url | null = null
|
|
576
604
|
if (proxyText !== "") {
|
|
577
605
|
proxy = httpClientProxyUrl(proxyText)
|
|
@@ -592,7 +620,12 @@ export class HttpClient {
|
|
|
592
620
|
} else if (proxy !== null) {
|
|
593
621
|
route = `${origin} via ${httpClientHostText(proxy.host)}:${proxy.port}`
|
|
594
622
|
}
|
|
595
|
-
if (
|
|
623
|
+
if (
|
|
624
|
+
this.fd >= 0 &&
|
|
625
|
+
this.route === route &&
|
|
626
|
+
httpClientSamePolicy(this.routePolicy, this.options.outbound) &&
|
|
627
|
+
this.quiet()
|
|
628
|
+
) {
|
|
596
629
|
this.reused = true
|
|
597
630
|
return true
|
|
598
631
|
}
|
|
@@ -610,6 +643,7 @@ export class HttpClient {
|
|
|
610
643
|
}
|
|
611
644
|
this.fd = fd
|
|
612
645
|
this.route = route
|
|
646
|
+
this.routePolicy = this.options.outbound
|
|
613
647
|
this.parser = httpClientParser(this.options)
|
|
614
648
|
if (url.scheme === "https") {
|
|
615
649
|
let early: u8[] = []
|
|
@@ -863,7 +897,7 @@ export class HttpClient {
|
|
|
863
897
|
if (code === HTTP1_E_BODY_LIMIT) {
|
|
864
898
|
this.fail(
|
|
865
899
|
HTTP_E_BODY_TOO_LARGE,
|
|
866
|
-
`the body is larger than maxBodyBytes (${this.options
|
|
900
|
+
`the body is larger than maxBodyBytes (${httpClientBodyCap(this.options)})`
|
|
867
901
|
)
|
|
868
902
|
} else {
|
|
869
903
|
// A response the server closed part way through is a connection lost,
|
|
@@ -1080,9 +1114,22 @@ export class HttpClient {
|
|
|
1080
1114
|
)
|
|
1081
1115
|
return -1
|
|
1082
1116
|
}
|
|
1117
|
+
let usable: i32 = count < DNS_RESOLVE_MAX_ADDRESSES ? count : DNS_RESOLVE_MAX_ADDRESSES
|
|
1118
|
+
const policy: OutboundPolicy | null = this.options.outbound
|
|
1119
|
+
if (policy !== null) {
|
|
1120
|
+
// Only the bytes checked here are connected to: no second lookup a rebinding answer could change.
|
|
1121
|
+
usable = outboundFilter(policy, addresses, usable)
|
|
1122
|
+
if (usable === 0) {
|
|
1123
|
+
this.fail(
|
|
1124
|
+
HTTP_E_BLOCKED,
|
|
1125
|
+
`${host} resolves to ${outboundAddressText(addresses, 0)}, which the outbound policy does not allow`
|
|
1126
|
+
)
|
|
1127
|
+
return -1
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1083
1130
|
const one: u8[] = new Array<u8>(DNS_ADDRESS_BYTES)
|
|
1084
1131
|
let last: i32 = 0
|
|
1085
|
-
for (let i: i32 = 0; i <
|
|
1132
|
+
for (let i: i32 = 0; i < usable && i < DNS_RESOLVE_MAX_ADDRESSES; i += 1) {
|
|
1086
1133
|
copyBytes(one, 0, addresses, i * DNS_ADDRESS_BYTES, DNS_ADDRESS_BYTES)
|
|
1087
1134
|
const fd: i32 = tcpConnect(one)
|
|
1088
1135
|
if (fd < 0) {
|
|
@@ -1320,7 +1367,7 @@ export class HttpClient {
|
|
|
1320
1367
|
/** Sets the error for a wait that ended with `code`, `text` saying what timed out. */
|
|
1321
1368
|
failWait(code: i32, text: string): void {
|
|
1322
1369
|
if (code === HTTP_E_TOTAL_TIMEOUT) {
|
|
1323
|
-
this.fail(code, `the fetch ran past ${this.options
|
|
1370
|
+
this.fail(code, `the fetch ran past ${httpClientTotalMs(this.options)} ms`)
|
|
1324
1371
|
} else if (code === HTTP_E_IO) {
|
|
1325
1372
|
this.fail(code, "the poll loop failed")
|
|
1326
1373
|
} else {
|
|
@@ -1468,10 +1515,44 @@ const httpClientParser = (options: HttpOptions): Http1ResponseParser =>
|
|
|
1468
1515
|
options.maxHeaderBytes,
|
|
1469
1516
|
options.maxHeaderBytes,
|
|
1470
1517
|
HTTP_CLIENT_MAX_HEADERS,
|
|
1471
|
-
options
|
|
1518
|
+
httpClientBodyCap(options),
|
|
1472
1519
|
options.maxHeaderBytes
|
|
1473
1520
|
)
|
|
1474
1521
|
|
|
1522
|
+
/** Whether `a` and `b` are the same outbound policy, or both none. */
|
|
1523
|
+
const httpClientSamePolicy = (a: OutboundPolicy | null, b: OutboundPolicy | null): boolean => {
|
|
1524
|
+
if (a === null || b === null) {
|
|
1525
|
+
return a === null && b === null
|
|
1526
|
+
}
|
|
1527
|
+
return a === b
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/** `maxBodyBytes`, or the outbound policy's cap when it is lower. */
|
|
1531
|
+
const httpClientBodyCap = (options: HttpOptions): i64 => {
|
|
1532
|
+
const policy: OutboundPolicy | null = options.outbound
|
|
1533
|
+
return policy !== null && policy.maxBodyBytes < options.maxBodyBytes
|
|
1534
|
+
? policy.maxBodyBytes
|
|
1535
|
+
: options.maxBodyBytes
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
/** `totalTimeoutMs`, or the outbound policy's budget when it is set and the options' is not, or is longer. */
|
|
1539
|
+
const httpClientTotalMs = (options: HttpOptions): i32 => {
|
|
1540
|
+
const policy: OutboundPolicy | null = options.outbound
|
|
1541
|
+
const own: i32 = options.totalTimeoutMs
|
|
1542
|
+
if (policy === null || policy.totalTimeoutMs <= 0) {
|
|
1543
|
+
return own
|
|
1544
|
+
}
|
|
1545
|
+
return own > 0 && own < policy.totalTimeoutMs ? own : policy.totalTimeoutMs
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
/** `maxRedirects`, or the outbound policy's limit when it is lower. */
|
|
1549
|
+
const httpClientMaxRedirects = (options: HttpOptions): i32 => {
|
|
1550
|
+
const policy: OutboundPolicy | null = options.outbound
|
|
1551
|
+
return policy !== null && policy.maxRedirects < options.maxRedirects
|
|
1552
|
+
? policy.maxRedirects
|
|
1553
|
+
: options.maxRedirects
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1475
1556
|
/** The statuses a redirect is followed for: 301, 302, 303, 307 and 308. */
|
|
1476
1557
|
const httpClientIsRedirect = (status: i32): boolean =>
|
|
1477
1558
|
status === 301 || status === 302 || status === 303 || status === 307 || status === 308
|
package/std/net/http-compress.ts
CHANGED
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
* default): a 1300-byte body that gzips to 700 still fits in one segment
|
|
33
33
|
* either way. After each call `decision` says why (`HTTP_COMPRESS_SENT`,
|
|
34
34
|
* `_NOT_ACCEPTED`, `_TOO_SMALL`, `_TOO_LARGE`, `_TOO_LITTLE_SAVED`,
|
|
35
|
-
* `_NO_FEWER_SEGMENTS`), for a program to count.
|
|
35
|
+
* `_NO_FEWER_SEGMENTS`), for a program to count. A response whose identity
|
|
36
|
+
* form already fits one segment is `_NO_FEWER_SEGMENTS` without running gzip
|
|
37
|
+
* at all, as no compression could take fewer.
|
|
36
38
|
*
|
|
37
39
|
* **Two encoders.** Bodies up to `fixedLimit` (16 KB) go through
|
|
38
40
|
* `GzipFixedCompressor`: one fixed-Huffman block, a 16 KB window, no state
|
|
@@ -69,6 +71,7 @@
|
|
|
69
71
|
*/
|
|
70
72
|
import { DEFLATE_FAST, compressBound } from "nish/compress/deflate"
|
|
71
73
|
import { GzipCompressor, GzipFixedCompressor } from "nish/compress/gzip"
|
|
74
|
+
import { wrappingAdd } from "nish:unsafe"
|
|
72
75
|
|
|
73
76
|
/** No content coding: the body as it is. */
|
|
74
77
|
export const HTTP_CODING_IDENTITY: i32 = 0
|
|
@@ -92,7 +95,8 @@ export const HTTP_OFFER_DEFLATE: i32 = 2
|
|
|
92
95
|
* went out compressed; the client did not take gzip; it was shorter than
|
|
93
96
|
* `minLength`; longer than `maxLength`; gzip did not save `minSaving` bytes
|
|
94
97
|
* and `minSavingPercent` percent; or the compressed response would not have
|
|
95
|
-
* taken fewer TCP segments (`segmentMode`)
|
|
98
|
+
* taken fewer TCP segments (`segmentMode`), which includes an identity
|
|
99
|
+
* response that already fits one segment, decided without compressing.
|
|
96
100
|
*/
|
|
97
101
|
export const HTTP_COMPRESS_SENT: i32 = 0
|
|
98
102
|
export const HTTP_COMPRESS_NOT_ACCEPTED: i32 = 1
|
|
@@ -374,12 +378,6 @@ export class HttpCompressor {
|
|
|
374
378
|
/** The shortest body worth compressing, and the longest the output can hold. */
|
|
375
379
|
readonly minLength: i32
|
|
376
380
|
readonly maxLength: i32
|
|
377
|
-
/**
|
|
378
|
-
* Whether `addFields` adds `Vary: Accept-Encoding` (RFC 9110 §12.5.5), which
|
|
379
|
-
* a cache needs whenever the same resource may be sent either way. On by
|
|
380
|
-
* default; turn it off when the program sets its own `Vary`.
|
|
381
|
-
*/
|
|
382
|
-
vary: boolean
|
|
383
381
|
/**
|
|
384
382
|
* The least a compressed body must save to be sent: `minSaving` bytes and
|
|
385
383
|
* `minSavingPercent` percent of the body, whichever is more. 1 and 0 by
|
|
@@ -387,6 +385,12 @@ export class HttpCompressor {
|
|
|
387
385
|
*/
|
|
388
386
|
minSaving: i32
|
|
389
387
|
minSavingPercent: i32
|
|
388
|
+
/**
|
|
389
|
+
* Whether `addFields` adds `Vary: Accept-Encoding` (RFC 9110 §12.5.5), which
|
|
390
|
+
* a cache needs whenever the same resource may be sent either way. On by
|
|
391
|
+
* default; turn it off when the program sets its own `Vary`.
|
|
392
|
+
*/
|
|
393
|
+
vary: boolean
|
|
390
394
|
/**
|
|
391
395
|
* Whether `respondEncoded` also sends the compressed response only when,
|
|
392
396
|
* head included, it takes fewer TCP segments of `mss` bytes than the body
|
|
@@ -401,6 +405,13 @@ export class HttpCompressor {
|
|
|
401
405
|
fixedLimit: i32
|
|
402
406
|
/** The last decision, an `HTTP_COMPRESS_*`; `HTTP_COMPRESS_NOT_ACCEPTED` before any. */
|
|
403
407
|
decision: i32
|
|
408
|
+
/**
|
|
409
|
+
* How many times `compress` and `compressAnyway` ran an encoder, from 0: a
|
|
410
|
+
* count for a test or a profile to read, never consulted by a decision. It
|
|
411
|
+
* wraps past 2^31 - 1 to -2^31 rather than panic, as a server can run that
|
|
412
|
+
* many; read it as a difference between two reads.
|
|
413
|
+
*/
|
|
414
|
+
encoderRuns: i32
|
|
404
415
|
|
|
405
416
|
/**
|
|
406
417
|
* A compressor at `level` (`DEFLATE_FAST` suits a server) for bodies of
|
|
@@ -430,6 +441,7 @@ export class HttpCompressor {
|
|
|
430
441
|
this.encoder = HTTP_ENCODER_AUTO
|
|
431
442
|
this.fixedLimit = 16384
|
|
432
443
|
this.decision = HTTP_COMPRESS_NOT_ACCEPTED
|
|
444
|
+
this.encoderRuns = 0
|
|
433
445
|
}
|
|
434
446
|
|
|
435
447
|
/**
|
|
@@ -462,6 +474,7 @@ export class HttpCompressor {
|
|
|
462
474
|
if (keep > 0) {
|
|
463
475
|
const fixed: boolean =
|
|
464
476
|
this.encoder === HTTP_ENCODER_FIXED || (this.encoder !== HTTP_ENCODER_LEVEL && len <= this.fixedLimit)
|
|
477
|
+
this.encoderRuns = wrappingAdd(this.encoderRuns, 1)
|
|
465
478
|
n = fixed
|
|
466
479
|
? this.fixed.compress(body, off, len, this.output, toI32(0), keep)
|
|
467
480
|
: this.gzip.compress(body, off, len, this.output, toI32(0))
|
|
@@ -493,6 +506,7 @@ export class HttpCompressor {
|
|
|
493
506
|
this.decision = HTTP_COMPRESS_NOT_ACCEPTED
|
|
494
507
|
return -1
|
|
495
508
|
}
|
|
509
|
+
this.encoderRuns = wrappingAdd(this.encoderRuns, 1)
|
|
496
510
|
let n: i32 = -1
|
|
497
511
|
if (
|
|
498
512
|
this.encoder === HTTP_ENCODER_FIXED ||
|
|
@@ -532,10 +546,7 @@ export class HttpCompressor {
|
|
|
532
546
|
* driving HTTP/2 or HTTP/3 by hand may with its own head sizes.
|
|
533
547
|
*/
|
|
534
548
|
fewerSegments(identityBytes: i32, encodedBytes: i32, mss: i32): boolean {
|
|
535
|
-
|
|
536
|
-
if (size <= 0) {
|
|
537
|
-
size = this.mss > 0 ? this.mss : HTTP_DEFAULT_MSS
|
|
538
|
-
}
|
|
549
|
+
const size: i32 = this.segmentSize(mss)
|
|
539
550
|
if (httpSegments(encodedBytes, size) < httpSegments(identityBytes, size)) {
|
|
540
551
|
return true
|
|
541
552
|
}
|
|
@@ -543,6 +554,37 @@ export class HttpCompressor {
|
|
|
543
554
|
return false
|
|
544
555
|
}
|
|
545
556
|
|
|
557
|
+
/** The segment `segmentMode` counts in: `mss` when above 0, else `this.mss` when above 0, else `HTTP_DEFAULT_MSS`. */
|
|
558
|
+
segmentSize(mss: i32): i32 {
|
|
559
|
+
if (mss > 0) {
|
|
560
|
+
return mss
|
|
561
|
+
}
|
|
562
|
+
return this.mss > 0 ? this.mss : HTTP_DEFAULT_MSS
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* `segmentMode`'s shortcut, before any encoder runs: whether a `len`-byte
|
|
567
|
+
* body that `compress` would try (its length in `[minLength, maxLength]`
|
|
568
|
+
* and `coding` gzip) is no longer than one segment of `mss` bytes (as
|
|
569
|
+
* `fewerSegments` reads it). Sent as it is, such a response takes one
|
|
570
|
+
* segment once its head fits beside it, and no compression takes fewer, so
|
|
571
|
+
* `decision` becomes `HTTP_COMPRESS_NO_FEWER_SEGMENTS` and the caller checks
|
|
572
|
+
* the head: when it does not fit, the caller goes on to `compress`, which
|
|
573
|
+
* decides again. Anything else answers false and leaves `decision` alone.
|
|
574
|
+
*/
|
|
575
|
+
oneSegment(coding: i32, len: i32, mss: i32): boolean {
|
|
576
|
+
if (
|
|
577
|
+
coding !== HTTP_CODING_GZIP ||
|
|
578
|
+
len < this.minLength ||
|
|
579
|
+
len > this.maxLength ||
|
|
580
|
+
len > this.segmentSize(mss)
|
|
581
|
+
) {
|
|
582
|
+
return false
|
|
583
|
+
}
|
|
584
|
+
this.decision = HTTP_COMPRESS_NO_FEWER_SEGMENTS
|
|
585
|
+
return true
|
|
586
|
+
}
|
|
587
|
+
|
|
546
588
|
/**
|
|
547
589
|
* The bytes of the HTTP/1.1 head a `length`-byte body sent as it is would
|
|
548
590
|
* get, from `encodedHead`, that of the same response sent compressed as
|