tunnelfetch 1.0.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/LICENSE +28 -0
- package/README.md +617 -0
- package/README.zh-CN.md +470 -0
- package/package.json +74 -0
- package/src/client/cookies.js +429 -0
- package/src/client/decode.js +346 -0
- package/src/client/redirect.js +249 -0
- package/src/client.js +704 -0
- package/src/errors.js +181 -0
- package/src/http1/chunked.js +289 -0
- package/src/http1/index.js +10 -0
- package/src/http1/request.js +143 -0
- package/src/http1/response.js +493 -0
- package/src/http2/connection.js +1170 -0
- package/src/http2/constants.js +129 -0
- package/src/http2/frames.js +291 -0
- package/src/http2/hpack.js +420 -0
- package/src/http2/huffman.js +203 -0
- package/src/http2/index.js +21 -0
- package/src/index.js +46 -0
- package/src/pool.js +256 -0
- package/src/proxy/direct.js +62 -0
- package/src/proxy/http-connect.js +206 -0
- package/src/proxy/index.js +197 -0
- package/src/proxy/socks5.js +344 -0
- package/src/tls/aead.js +263 -0
- package/src/tls/connect.js +407 -0
- package/src/tls/constants.js +334 -0
- package/src/tls/extensions.js +376 -0
- package/src/tls/handshake-messages.js +901 -0
- package/src/tls/handshake.js +568 -0
- package/src/tls/handshake12.js +507 -0
- package/src/tls/index.js +44 -0
- package/src/tls/keyschedule.js +473 -0
- package/src/tls/record.js +872 -0
- package/src/tls/tickets.js +145 -0
- package/src/tls/transcript.js +101 -0
- package/src/tls/wire.js +224 -0
- package/src/transport.js +296 -0
- package/src/trust/der.js +551 -0
- package/src/trust/index.js +375 -0
- package/src/trust/name.js +235 -0
- package/src/trust/ocsp.js +759 -0
- package/src/trust/path.js +595 -0
- package/src/trust/roots.js +454 -0
- package/src/trust/x509.js +902 -0
- package/src/util/bytes.js +470 -0
- package/src/util/deadline.js +266 -0
- package/src/warmup-fixture.js +85 -0
- package/src/warmup.js +243 -0
- package/types/client/cookies.d.ts +159 -0
- package/types/client/decode.d.ts +54 -0
- package/types/client/redirect.d.ts +96 -0
- package/types/client.d.ts +323 -0
- package/types/errors.d.ts +141 -0
- package/types/http1/chunked.d.ts +48 -0
- package/types/http1/index.d.ts +3 -0
- package/types/http1/request.d.ts +44 -0
- package/types/http1/response.d.ts +183 -0
- package/types/http2/connection.d.ts +282 -0
- package/types/http2/constants.d.ts +95 -0
- package/types/http2/frames.d.ts +116 -0
- package/types/http2/hpack.d.ts +99 -0
- package/types/http2/huffman.d.ts +21 -0
- package/types/http2/index.d.ts +5 -0
- package/types/index.d.ts +17 -0
- package/types/pool.d.ts +135 -0
- package/types/proxy/direct.d.ts +26 -0
- package/types/proxy/http-connect.d.ts +37 -0
- package/types/proxy/index.d.ts +62 -0
- package/types/proxy/socks5.d.ts +47 -0
- package/types/tls/aead.d.ts +67 -0
- package/types/tls/connect.d.ts +280 -0
- package/types/tls/constants.d.ts +275 -0
- package/types/tls/extensions.d.ts +195 -0
- package/types/tls/handshake-messages.d.ts +430 -0
- package/types/tls/handshake.d.ts +90 -0
- package/types/tls/handshake12.d.ts +35 -0
- package/types/tls/index.d.ts +9 -0
- package/types/tls/keyschedule.d.ts +272 -0
- package/types/tls/record.d.ts +361 -0
- package/types/tls/tickets.d.ts +66 -0
- package/types/tls/transcript.d.ts +52 -0
- package/types/tls/wire.d.ts +106 -0
- package/types/transport.d.ts +222 -0
- package/types/trust/der.d.ts +239 -0
- package/types/trust/index.d.ts +194 -0
- package/types/trust/name.d.ts +33 -0
- package/types/trust/ocsp.d.ts +138 -0
- package/types/trust/path.d.ts +139 -0
- package/types/trust/roots.d.ts +36 -0
- package/types/trust/x509.d.ts +401 -0
- package/types/util/bytes.d.ts +183 -0
- package/types/util/deadline.d.ts +133 -0
- package/types/warmup-fixture.d.ts +11 -0
- package/types/warmup.d.ts +45 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One of the stable machine-readable codes in {@link codes}. Exported as a named union so a
|
|
3
|
+
* caller can exhaustively switch on `err.code` and have the compiler point at the case they
|
|
4
|
+
* forgot when a release adds one.
|
|
5
|
+
* @typedef {(typeof codes)[keyof typeof codes]} ErrorCode
|
|
6
|
+
*/
|
|
7
|
+
/** Base for everything this package throws. Never thrown directly. */
|
|
8
|
+
export class TunnelFetchError extends Error {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} code stable machine-readable code, normally an {@link ErrorCode}. Typed as
|
|
11
|
+
* string because one internal code (`UNEXPECTED_EOF`, from the byte layer) deliberately
|
|
12
|
+
* lives outside the public table.
|
|
13
|
+
* @param {string} message human-readable, must name concrete values
|
|
14
|
+
* @param {Record<string, unknown>} [detail]
|
|
15
|
+
*/
|
|
16
|
+
constructor(code: string, message: string, detail?: Record<string, unknown>);
|
|
17
|
+
code: string;
|
|
18
|
+
detail: Record<string, unknown> | undefined;
|
|
19
|
+
}
|
|
20
|
+
/** Proxy handshake failed: CONNECT refused, SOCKS5 rejected, auth required. */
|
|
21
|
+
export class ProxyError extends TunnelFetchError {
|
|
22
|
+
}
|
|
23
|
+
/** HTTP/1.1 wire format: malformed message, ambiguous framing, truncated body. */
|
|
24
|
+
export class HttpError extends TunnelFetchError {
|
|
25
|
+
}
|
|
26
|
+
/** TLS record layer, handshake, or a peer alert. */
|
|
27
|
+
export class TlsError extends TunnelFetchError {
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* HTTP/2 framing, streams, flow control, HPACK, or a peer GOAWAY/RST_STREAM. Separate from
|
|
31
|
+
* HttpError because the wire format and its failure modes are entirely different: an HTTP/1.1
|
|
32
|
+
* message is text framed by lengths and CRLFs, an HTTP/2 stream is a state machine over binary
|
|
33
|
+
* frames sharing one connection, and a caller distinguishing "the /1.1 parser choked" from "an
|
|
34
|
+
* h2 stream was reset" wants two codes, not one.
|
|
35
|
+
*/
|
|
36
|
+
export class Http2Error extends TunnelFetchError {
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The peer offered something we deliberately do not implement.
|
|
40
|
+
* Separate from TlsError so "we cannot talk to this server" is distinguishable from
|
|
41
|
+
* "the connection broke", which is the difference between a feature request and a bug report.
|
|
42
|
+
*/
|
|
43
|
+
export class TlsUnsupportedError extends TlsError {
|
|
44
|
+
}
|
|
45
|
+
/** Certificate chain, trust anchor, name matching, validity, or constraint failure. */
|
|
46
|
+
export class CertificateError extends TunnelFetchError {
|
|
47
|
+
}
|
|
48
|
+
/** A deadline elapsed. `phase` says which one, because the fix differs per phase. */
|
|
49
|
+
export class TimeoutError extends TunnelFetchError {
|
|
50
|
+
}
|
|
51
|
+
/** A configured limit was exceeded (response size, header size, redirect count). */
|
|
52
|
+
export class LimitError extends TunnelFetchError {
|
|
53
|
+
}
|
|
54
|
+
/** The caller asked for something the chosen transport cannot honour. */
|
|
55
|
+
export class ConfigError extends TunnelFetchError {
|
|
56
|
+
}
|
|
57
|
+
export namespace codes {
|
|
58
|
+
let PROXY_CONNECT_REFUSED: "PROXY_CONNECT_REFUSED";
|
|
59
|
+
let PROXY_AUTH_REQUIRED: "PROXY_AUTH_REQUIRED";
|
|
60
|
+
let PROXY_AUTH_FAILED: "PROXY_AUTH_FAILED";
|
|
61
|
+
let PROXY_PROTOCOL: "PROXY_PROTOCOL";
|
|
62
|
+
let PROXY_UNREACHABLE: "PROXY_UNREACHABLE";
|
|
63
|
+
let SOCKS5_NO_ACCEPTABLE_AUTH: "SOCKS5_NO_ACCEPTABLE_AUTH";
|
|
64
|
+
let SOCKS5_REPLY: "SOCKS5_REPLY";
|
|
65
|
+
let SOCKS5_ADDR_TYPE: "SOCKS5_ADDR_TYPE";
|
|
66
|
+
let HTTP_REQUEST_LINE: "HTTP_REQUEST_LINE";
|
|
67
|
+
let HTTP_STATUS_LINE: "HTTP_STATUS_LINE";
|
|
68
|
+
let HTTP_HEADER: "HTTP_HEADER";
|
|
69
|
+
let HTTP_FRAMING_AMBIGUOUS: "HTTP_FRAMING_AMBIGUOUS";
|
|
70
|
+
let HTTP_CHUNK: "HTTP_CHUNK";
|
|
71
|
+
let HTTP_BODY_TRUNCATED: "HTTP_BODY_TRUNCATED";
|
|
72
|
+
let HTTP_TRAILER: "HTTP_TRAILER";
|
|
73
|
+
let HTTP_UPGRADE_UNEXPECTED: "HTTP_UPGRADE_UNEXPECTED";
|
|
74
|
+
let TLS_RECORD: "TLS_RECORD";
|
|
75
|
+
let TLS_ALERT: "TLS_ALERT";
|
|
76
|
+
let TLS_HANDSHAKE: "TLS_HANDSHAKE";
|
|
77
|
+
let TLS_TRUNCATED: "TLS_TRUNCATED";
|
|
78
|
+
let TLS_VERSION_UNSUPPORTED: "TLS_VERSION_UNSUPPORTED";
|
|
79
|
+
let TLS_CIPHER_UNSUPPORTED: "TLS_CIPHER_UNSUPPORTED";
|
|
80
|
+
let TLS_GROUP_UNSUPPORTED: "TLS_GROUP_UNSUPPORTED";
|
|
81
|
+
let TLS_SIGALG_UNSUPPORTED: "TLS_SIGALG_UNSUPPORTED";
|
|
82
|
+
let TLS_EXTENSION_UNSUPPORTED: "TLS_EXTENSION_UNSUPPORTED";
|
|
83
|
+
let TLS_ALPN: "TLS_ALPN";
|
|
84
|
+
let TLS_PSK: "TLS_PSK";
|
|
85
|
+
let TLS_TICKET: "TLS_TICKET";
|
|
86
|
+
let HTTP2_PROTOCOL: "HTTP2_PROTOCOL";
|
|
87
|
+
let HTTP2_FRAME_SIZE: "HTTP2_FRAME_SIZE";
|
|
88
|
+
let HTTP2_SETTINGS: "HTTP2_SETTINGS";
|
|
89
|
+
let HTTP2_FLOW_CONTROL: "HTTP2_FLOW_CONTROL";
|
|
90
|
+
let HTTP2_STREAM_STATE: "HTTP2_STREAM_STATE";
|
|
91
|
+
let HTTP2_STREAM_CLOSED: "HTTP2_STREAM_CLOSED";
|
|
92
|
+
let HTTP2_GOAWAY: "HTTP2_GOAWAY";
|
|
93
|
+
let HTTP2_COMPRESSION: "HTTP2_COMPRESSION";
|
|
94
|
+
let HTTP2_HEADER: "HTTP2_HEADER";
|
|
95
|
+
let HTTP2_PUSH_UNEXPECTED: "HTTP2_PUSH_UNEXPECTED";
|
|
96
|
+
let HTTP2_TRAILER: "HTTP2_TRAILER";
|
|
97
|
+
let CERT_PARSE: "CERT_PARSE";
|
|
98
|
+
let CERT_CHAIN_INCOMPLETE: "CERT_CHAIN_INCOMPLETE";
|
|
99
|
+
let CERT_UNTRUSTED_ROOT: "CERT_UNTRUSTED_ROOT";
|
|
100
|
+
let CERT_SIGNATURE_INVALID: "CERT_SIGNATURE_INVALID";
|
|
101
|
+
let CERT_SIGNATURE_WEAK: "CERT_SIGNATURE_WEAK";
|
|
102
|
+
let CERT_SIGNATURE_UNSUPPORTED: "CERT_SIGNATURE_UNSUPPORTED";
|
|
103
|
+
let CERT_EXPIRED: "CERT_EXPIRED";
|
|
104
|
+
let CERT_NOT_YET_VALID: "CERT_NOT_YET_VALID";
|
|
105
|
+
let CERT_NAME_MISMATCH: "CERT_NAME_MISMATCH";
|
|
106
|
+
let CERT_CONSTRAINT: "CERT_CONSTRAINT";
|
|
107
|
+
let CERT_PIN_MISMATCH: "CERT_PIN_MISMATCH";
|
|
108
|
+
let OCSP_PARSE: "OCSP_PARSE";
|
|
109
|
+
let OCSP_UNVERIFIED: "OCSP_UNVERIFIED";
|
|
110
|
+
let OCSP_MISMATCH: "OCSP_MISMATCH";
|
|
111
|
+
let OCSP_STALE: "OCSP_STALE";
|
|
112
|
+
let OCSP_REVOKED: "OCSP_REVOKED";
|
|
113
|
+
let OCSP_UNKNOWN: "OCSP_UNKNOWN";
|
|
114
|
+
let OCSP_REQUIRED: "OCSP_REQUIRED";
|
|
115
|
+
let HTTP_CONTENT_ENCODING: "HTTP_CONTENT_ENCODING";
|
|
116
|
+
let HTTP_CHARSET: "HTTP_CHARSET";
|
|
117
|
+
let REDIRECT_INVALID_LOCATION: "REDIRECT_INVALID_LOCATION";
|
|
118
|
+
let REDIRECT_LOOP: "REDIRECT_LOOP";
|
|
119
|
+
let REDIRECT_SCHEME: "REDIRECT_SCHEME";
|
|
120
|
+
let COOKIE_INVALID: "COOKIE_INVALID";
|
|
121
|
+
let POOL_CLOSED: "POOL_CLOSED";
|
|
122
|
+
let CONNECTION_CLOSED: "CONNECTION_CLOSED";
|
|
123
|
+
let TIMEOUT_CONNECT: "TIMEOUT_CONNECT";
|
|
124
|
+
let TIMEOUT_HANDSHAKE: "TIMEOUT_HANDSHAKE";
|
|
125
|
+
let TIMEOUT_HEADERS: "TIMEOUT_HEADERS";
|
|
126
|
+
let TIMEOUT_IDLE: "TIMEOUT_IDLE";
|
|
127
|
+
let TIMEOUT_TOTAL: "TIMEOUT_TOTAL";
|
|
128
|
+
let LIMIT_BODY: "LIMIT_BODY";
|
|
129
|
+
let LIMIT_HEADER: "LIMIT_HEADER";
|
|
130
|
+
let LIMIT_REDIRECTS: "LIMIT_REDIRECTS";
|
|
131
|
+
let CONFIG_UNSATISFIABLE: "CONFIG_UNSATISFIABLE";
|
|
132
|
+
let CONFIG_INVALID: "CONFIG_INVALID";
|
|
133
|
+
}
|
|
134
|
+
export function hex8(n: number): string;
|
|
135
|
+
export function hex16(n: number): string;
|
|
136
|
+
/**
|
|
137
|
+
* One of the stable machine-readable codes in {@link codes}. Exported as a named union so a
|
|
138
|
+
* caller can exhaustively switch on `err.code` and have the compiler point at the case they
|
|
139
|
+
* forgot when a release adds one.
|
|
140
|
+
*/
|
|
141
|
+
export type ErrorCode = (typeof codes)[keyof typeof codes];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every cap is fail-closed: the peer controls chunk sizes and counts, so each one bounds what
|
|
3
|
+
* a hostile sender can make us buffer.
|
|
4
|
+
* @typedef {object} ChunkedOptions
|
|
5
|
+
* @property {number} [maxBytes] total payload cap, default unlimited
|
|
6
|
+
* @property {number} [maxChunkSize] per-chunk cap, default 64 MiB
|
|
7
|
+
* @property {number} [maxTrailerBytes] trailer-section cap, default 8192
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Decode a chunked body from `reader`.
|
|
11
|
+
*
|
|
12
|
+
* Returns `{ stream, trailers }`:
|
|
13
|
+
* - `stream`: ReadableStream<Uint8Array> of the decoded payload octets.
|
|
14
|
+
* - `trailers`: Promise<Headers|null>. Resolves with the trailer fields once the terminal
|
|
15
|
+
* chunk and its trailing CRLF have been fully consumed — i.e. the reader is positioned
|
|
16
|
+
* exactly after the body, which is the signal a connection pool needs before reuse.
|
|
17
|
+
* Resolves null if the stream is cancelled before that point (position unknown, do not
|
|
18
|
+
* reuse). Rejects with the same error the stream errors with on a protocol violation.
|
|
19
|
+
*
|
|
20
|
+
* Reads only what the chunked grammar covers; any bytes after the terminal CRLF stay in
|
|
21
|
+
* `reader` for the next message.
|
|
22
|
+
*
|
|
23
|
+
* @param {import('../util/bytes.js').ByteReader} reader
|
|
24
|
+
* @param {ChunkedOptions} [opts]
|
|
25
|
+
* @returns {{ stream: ReadableStream<Uint8Array>, trailers: Promise<Headers | null> }}
|
|
26
|
+
*/
|
|
27
|
+
export function decodeChunked(reader: import("../util/bytes.js").ByteReader, { maxBytes, maxChunkSize, maxTrailerBytes }?: ChunkedOptions): {
|
|
28
|
+
stream: ReadableStream<Uint8Array>;
|
|
29
|
+
trailers: Promise<Headers | null>;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Every cap is fail-closed: the peer controls chunk sizes and counts, so each one bounds what
|
|
33
|
+
* a hostile sender can make us buffer.
|
|
34
|
+
*/
|
|
35
|
+
export type ChunkedOptions = {
|
|
36
|
+
/**
|
|
37
|
+
* total payload cap, default unlimited
|
|
38
|
+
*/
|
|
39
|
+
maxBytes?: number | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* per-chunk cap, default 64 MiB
|
|
42
|
+
*/
|
|
43
|
+
maxChunkSize?: number | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* trailer-section cap, default 8192
|
|
46
|
+
*/
|
|
47
|
+
maxTrailerBytes?: number | undefined;
|
|
48
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A request head as this serialiser consumes it. Nothing optional is invented: what is absent
|
|
3
|
+
* here is absent on the wire.
|
|
4
|
+
* @typedef {object} RequestHead
|
|
5
|
+
* @property {string} method RFC 9110 token, sent verbatim (no case-folding)
|
|
6
|
+
* @property {string} target request-target, already encoded: origin-form (`/path?q`),
|
|
7
|
+
* absolute-form, authority-form (CONNECT) or `*`
|
|
8
|
+
* @property {Headers | Iterable<[string, string]>} [headers]
|
|
9
|
+
* @property {'1.0' | '1.1'} [httpVersion] default '1.1'
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Serialise a request head: request line, header fields, terminating blank line.
|
|
13
|
+
* Throws HttpError on any input the RFC 9110 grammar rejects — notably CR/LF in a value,
|
|
14
|
+
* which is header injection, never something to sanitise.
|
|
15
|
+
*
|
|
16
|
+
* `headers` may be a WHATWG Headers instance or any iterable of [name, value] pairs.
|
|
17
|
+
* Pair iterables are written in caller order, unsorted — order can matter to real servers.
|
|
18
|
+
* Note that a Headers instance has already lost the caller's order by spec (it iterates
|
|
19
|
+
* lowercased and sorted); we serialise its iteration order as-is.
|
|
20
|
+
*
|
|
21
|
+
* @param {RequestHead} req
|
|
22
|
+
* @returns {Uint8Array}
|
|
23
|
+
*/
|
|
24
|
+
export function serializeRequestHead({ method, target, headers, httpVersion }: RequestHead): Uint8Array;
|
|
25
|
+
/**
|
|
26
|
+
* A request head as this serialiser consumes it. Nothing optional is invented: what is absent
|
|
27
|
+
* here is absent on the wire.
|
|
28
|
+
*/
|
|
29
|
+
export type RequestHead = {
|
|
30
|
+
/**
|
|
31
|
+
* RFC 9110 token, sent verbatim (no case-folding)
|
|
32
|
+
*/
|
|
33
|
+
method: string;
|
|
34
|
+
/**
|
|
35
|
+
* request-target, already encoded: origin-form (`/path?q`),
|
|
36
|
+
* absolute-form, authority-form (CONNECT) or `*`
|
|
37
|
+
*/
|
|
38
|
+
target: string;
|
|
39
|
+
headers?: Headers | Iterable<[string, string]> | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* default '1.1'
|
|
42
|
+
*/
|
|
43
|
+
httpVersion?: "1.0" | "1.1" | undefined;
|
|
44
|
+
};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One skipped 1xx head. Kept because Early Hints (103) carry Link headers a caller may want;
|
|
3
|
+
* everything else about a 1xx is noise by definition.
|
|
4
|
+
* @typedef {object} InformationalHead
|
|
5
|
+
* @property {'1.0' | '1.1'} httpVersion
|
|
6
|
+
* @property {number} status
|
|
7
|
+
* @property {string} statusText
|
|
8
|
+
* @property {Headers} headers
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The parsed response head. `setCookie` repeats the raw Set-Cookie values because the Headers
|
|
12
|
+
* class folds duplicates with ", ", which destroys cookie dates — no jar can be built from the
|
|
13
|
+
* folded form.
|
|
14
|
+
* @typedef {object} ResponseHead
|
|
15
|
+
* @property {'1.0' | '1.1'} httpVersion only versions the status-line grammar admits
|
|
16
|
+
* @property {number} status
|
|
17
|
+
* @property {string} statusText may be empty; `HTTP/1.1 200` is a legal status line
|
|
18
|
+
* @property {Headers} headers
|
|
19
|
+
* @property {string[]} setCookie one entry per Set-Cookie header, unfolded
|
|
20
|
+
* @property {InformationalHead[]} informational 1xx heads skipped before the real response
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} ReadHeadOptions
|
|
24
|
+
* @property {number} [maxHeaderBytes] budget for the ENTIRE head phase, default 65536
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Read the response head: status line and header fields, plus any preceding informational
|
|
28
|
+
* (1xx) responses, which are legal noise before the real response (100 Continue, 103 Early
|
|
29
|
+
* Hints). They are skipped — 1xx never has a body — and returned in `informational` so a
|
|
30
|
+
* caller can surface Early Hints. 101 is fatal: this client never offers an upgrade, so a
|
|
31
|
+
* peer switching protocols means the bytes that follow are not HTTP and cannot be framed.
|
|
32
|
+
* Malformed heads throw HttpError; an oversized head throws LimitError.
|
|
33
|
+
*
|
|
34
|
+
* `maxHeaderBytes` bounds the ENTIRE head phase, informational heads included; a per-head
|
|
35
|
+
* budget would let a peer stream 1xx responses forever.
|
|
36
|
+
*
|
|
37
|
+
* @param {import('../util/bytes.js').ByteReader} reader
|
|
38
|
+
* @param {ReadHeadOptions} [opts]
|
|
39
|
+
* @returns {Promise<ResponseHead>}
|
|
40
|
+
*/
|
|
41
|
+
export function readResponseHead(reader: import("../util/bytes.js").ByteReader, { maxHeaderBytes }?: ReadHeadOptions): Promise<ResponseHead>;
|
|
42
|
+
/**
|
|
43
|
+
* How a response body is delimited. The four kinds are exhaustive: RFC 9112 §6.3 admits no
|
|
44
|
+
* fifth, and everything ambiguous throws before a kind is chosen.
|
|
45
|
+
* @typedef {'none' | 'content-length' | 'chunked' | 'until-close'} FramingKind
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* The framing decision. `length` is present exactly when `kind` is 'content-length'.
|
|
49
|
+
* @typedef {object} Framing
|
|
50
|
+
* @property {FramingKind} kind
|
|
51
|
+
* @property {number} [length] declared byte count, content-length framing only
|
|
52
|
+
* @property {boolean} keepAliveEligible whether the socket MAY be reused after the body ends
|
|
53
|
+
* as framed; see bodyFraming for why this is the load-bearing bit
|
|
54
|
+
*/
|
|
55
|
+
/**
|
|
56
|
+
* Decide how the response body is delimited, per RFC 9112 §6.3, in its order. Ambiguous
|
|
57
|
+
* framing (TE and CL together, disagreeing duplicate CLs, an undecodable transfer coding)
|
|
58
|
+
* throws HttpError with HTTP_FRAMING_AMBIGUOUS — every one of those is a smuggling vector.
|
|
59
|
+
*
|
|
60
|
+
* `keepAliveEligible` is the load-bearing bit: it is true only when the body has a determinate
|
|
61
|
+
* end (`none`, `content-length`, `chunked`). A connection pool must never reuse a socket after
|
|
62
|
+
* an `until-close` body — with no marked end, "the body" is just "whatever arrived", and the
|
|
63
|
+
* next request on that socket would read the previous response's tail as its own head. That is
|
|
64
|
+
* the response-to-the-wrong-request bug, and this flag is the only thing standing between the
|
|
65
|
+
* pool and it. (A 2xx CONNECT reply is also ineligible: the socket is a tunnel now, not HTTP.)
|
|
66
|
+
*
|
|
67
|
+
* @param {{ status: number, method?: string, headers: Headers }} res the head fields framing
|
|
68
|
+
* depends on; a full ResponseHead satisfies it
|
|
69
|
+
* @returns {Framing}
|
|
70
|
+
*/
|
|
71
|
+
export function bodyFraming({ status, method, headers }: {
|
|
72
|
+
status: number;
|
|
73
|
+
method?: string;
|
|
74
|
+
headers: Headers;
|
|
75
|
+
}): Framing;
|
|
76
|
+
/**
|
|
77
|
+
* A response body stream plus the completion contract a connection pool needs. The two extra
|
|
78
|
+
* properties are documented on readResponseBody, which is the only producer.
|
|
79
|
+
* @typedef {ReadableStream<Uint8Array> & { completed: Promise<boolean>,
|
|
80
|
+
* trailers: Promise<Headers | null> }} BodyStream
|
|
81
|
+
*/
|
|
82
|
+
/**
|
|
83
|
+
* @typedef {object} ReadBodyOptions
|
|
84
|
+
* @property {number} [maxBytes] fail-closed cap on total payload bytes, default unlimited
|
|
85
|
+
*/
|
|
86
|
+
/**
|
|
87
|
+
* Stream the response body according to `framing`.
|
|
88
|
+
*
|
|
89
|
+
* The returned ReadableStream<Uint8Array> carries two extra properties — the completion
|
|
90
|
+
* contract a connection pool needs:
|
|
91
|
+
*
|
|
92
|
+
* - `completed`: Promise<boolean>. Resolves `true` only when the body ended exactly as framed
|
|
93
|
+
* (the reader is positioned at the first byte after the body). Resolves `false` if the
|
|
94
|
+
* consumer cancelled early (position unknown). Rejects with the stream's error on a protocol
|
|
95
|
+
* violation (truncation, over-limit). The pool must await `completed === true` AND require
|
|
96
|
+
* `framing.keepAliveEligible` before reusing the socket; anything else and the next request
|
|
97
|
+
* reads this response's tail. Note it settles as the body is CONSUMED — an unread stream
|
|
98
|
+
* settles nothing (except for bodies that are complete at creation: `none` and length 0).
|
|
99
|
+
* - `trailers`: Promise<Headers|null>. Chunked trailers, or null for other framings / cancel.
|
|
100
|
+
*
|
|
101
|
+
* Bytes are streamed through, never buffered whole; `maxBytes` bounds the total.
|
|
102
|
+
*
|
|
103
|
+
* @param {import('../util/bytes.js').ByteReader} reader
|
|
104
|
+
* @param {Framing} framing
|
|
105
|
+
* @param {ReadBodyOptions} [opts]
|
|
106
|
+
* @returns {BodyStream}
|
|
107
|
+
*/
|
|
108
|
+
export function readResponseBody(reader: import("../util/bytes.js").ByteReader, framing: Framing, { maxBytes }?: ReadBodyOptions): BodyStream;
|
|
109
|
+
/**
|
|
110
|
+
* One skipped 1xx head. Kept because Early Hints (103) carry Link headers a caller may want;
|
|
111
|
+
* everything else about a 1xx is noise by definition.
|
|
112
|
+
*/
|
|
113
|
+
export type InformationalHead = {
|
|
114
|
+
httpVersion: "1.0" | "1.1";
|
|
115
|
+
status: number;
|
|
116
|
+
statusText: string;
|
|
117
|
+
headers: Headers;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* The parsed response head. `setCookie` repeats the raw Set-Cookie values because the Headers
|
|
121
|
+
* class folds duplicates with ", ", which destroys cookie dates — no jar can be built from the
|
|
122
|
+
* folded form.
|
|
123
|
+
*/
|
|
124
|
+
export type ResponseHead = {
|
|
125
|
+
/**
|
|
126
|
+
* only versions the status-line grammar admits
|
|
127
|
+
*/
|
|
128
|
+
httpVersion: "1.0" | "1.1";
|
|
129
|
+
status: number;
|
|
130
|
+
/**
|
|
131
|
+
* may be empty; `HTTP/1.1 200` is a legal status line
|
|
132
|
+
*/
|
|
133
|
+
statusText: string;
|
|
134
|
+
headers: Headers;
|
|
135
|
+
/**
|
|
136
|
+
* one entry per Set-Cookie header, unfolded
|
|
137
|
+
*/
|
|
138
|
+
setCookie: string[];
|
|
139
|
+
/**
|
|
140
|
+
* 1xx heads skipped before the real response
|
|
141
|
+
*/
|
|
142
|
+
informational: InformationalHead[];
|
|
143
|
+
};
|
|
144
|
+
export type ReadHeadOptions = {
|
|
145
|
+
/**
|
|
146
|
+
* budget for the ENTIRE head phase, default 65536
|
|
147
|
+
*/
|
|
148
|
+
maxHeaderBytes?: number | undefined;
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* How a response body is delimited. The four kinds are exhaustive: RFC 9112 §6.3 admits no
|
|
152
|
+
* fifth, and everything ambiguous throws before a kind is chosen.
|
|
153
|
+
*/
|
|
154
|
+
export type FramingKind = "none" | "content-length" | "chunked" | "until-close";
|
|
155
|
+
/**
|
|
156
|
+
* The framing decision. `length` is present exactly when `kind` is 'content-length'.
|
|
157
|
+
*/
|
|
158
|
+
export type Framing = {
|
|
159
|
+
kind: FramingKind;
|
|
160
|
+
/**
|
|
161
|
+
* declared byte count, content-length framing only
|
|
162
|
+
*/
|
|
163
|
+
length?: number | undefined;
|
|
164
|
+
/**
|
|
165
|
+
* whether the socket MAY be reused after the body ends
|
|
166
|
+
* as framed; see bodyFraming for why this is the load-bearing bit
|
|
167
|
+
*/
|
|
168
|
+
keepAliveEligible: boolean;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* A response body stream plus the completion contract a connection pool needs. The two extra
|
|
172
|
+
* properties are documented on readResponseBody, which is the only producer.
|
|
173
|
+
*/
|
|
174
|
+
export type BodyStream = ReadableStream<Uint8Array> & {
|
|
175
|
+
completed: Promise<boolean>;
|
|
176
|
+
trailers: Promise<Headers | null>;
|
|
177
|
+
};
|
|
178
|
+
export type ReadBodyOptions = {
|
|
179
|
+
/**
|
|
180
|
+
* fail-closed cap on total payload bytes, default unlimited
|
|
181
|
+
*/
|
|
182
|
+
maxBytes?: number | undefined;
|
|
183
|
+
};
|