tunnelfetch 1.0.1 → 1.1.1
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/README.md +146 -10
- package/README.zh-CN.md +86 -5
- package/package.json +2 -2
- package/src/client/decode.js +103 -5
- package/src/client.js +144 -9
- package/src/http2/connection.js +112 -3
- package/src/pool.js +20 -4
- package/types/client/decode.d.ts +32 -2
- package/types/client.d.ts +37 -94
- package/types/http2/connection.d.ts +38 -31
package/types/client.d.ts
CHANGED
|
@@ -54,8 +54,16 @@ export function install(options?: ClientOptions): () => void;
|
|
|
54
54
|
* one across Clients or to persist it.
|
|
55
55
|
* @property {number} [maxRedirects] default 20.
|
|
56
56
|
* @property {number} [maxBodyBytes] enforced from Content-Length before a byte is read.
|
|
57
|
-
* @property {boolean} [decompress] gzip/deflate. Default true.
|
|
58
|
-
*
|
|
57
|
+
* @property {boolean} [decompress] gzip/deflate. Default true.
|
|
58
|
+
* @property {Record<string, import('./client/decode.js').BodyDecoder>} [decoders] extra
|
|
59
|
+
* content-codings this client can read, e.g. `{ br: (s) => ... }`. Registering one is what
|
|
60
|
+
* makes advertising it honest, so each name is appended to Accept-Encoding — a client that
|
|
61
|
+
* asked for a coding it cannot decode would turn every such response into garbage. `br` and
|
|
62
|
+
* `zstd` are not built in because the runtime's DecompressionStream has neither and this
|
|
63
|
+
* package takes no dependencies; supply your own and the cost, and the supply chain, are
|
|
64
|
+
* yours and visible. Measured on the edge: WASM brotli decodes at about 2x native gzip, and
|
|
65
|
+
* the wire bytes it saves do not pay that back — see the README. The reason to turn it on is
|
|
66
|
+
* matching a browser's Accept-Encoding, not saving CPU.
|
|
59
67
|
* @property {boolean} [keepAlive] default true.
|
|
60
68
|
* @property {boolean} [http2] offer HTTP/2 via ALPN and speak it when the server selects it.
|
|
61
69
|
* Default true. The goal is ACCESS, not speed — some sites treat HTTP/1.1 as a bot signal — and
|
|
@@ -75,95 +83,7 @@ export class Client {
|
|
|
75
83
|
* @param {ClientOptions} [options]
|
|
76
84
|
*/
|
|
77
85
|
constructor(options?: ClientOptions);
|
|
78
|
-
options:
|
|
79
|
-
/**
|
|
80
|
-
* Socket factory. Required for any
|
|
81
|
-
* request the platform's own `fetch` cannot serve — which is every proxied request, and every
|
|
82
|
-
* request asking for a trust policy `fetch` cannot express.
|
|
83
|
-
*/
|
|
84
|
-
connect?: import("./proxy/index.js").ConnectFn | undefined;
|
|
85
|
-
/**
|
|
86
|
-
* URL string or
|
|
87
|
-
* config object; `http:`, `https:`, `socks5:` and `socks5h:`.
|
|
88
|
-
*/
|
|
89
|
-
proxy?: string | import("./proxy/index.js").ProxyConfig | null | undefined;
|
|
90
|
-
/**
|
|
91
|
-
* certificate policy, httpx's
|
|
92
|
-
* `verify=`. Default `{ mode: 'system' }`.
|
|
93
|
-
*/
|
|
94
|
-
trust?: import("./trust/index.js").TrustConfig | undefined;
|
|
95
|
-
/**
|
|
96
|
-
* handshake knobs.
|
|
97
|
-
*/
|
|
98
|
-
tls?: import("./tls/connect.js").TlsOptions | undefined;
|
|
99
|
-
/**
|
|
100
|
-
* connect / handshake /
|
|
101
|
-
* headers / idle / total, in ms. The idle gap is the control; total is a backstop.
|
|
102
|
-
*/
|
|
103
|
-
timeouts?: import("./util/deadline.js").DeadlineOptions | undefined;
|
|
104
|
-
/**
|
|
105
|
-
* enable a per-Client cookie jar.
|
|
106
|
-
*/
|
|
107
|
-
cookies?: boolean | undefined;
|
|
108
|
-
/**
|
|
109
|
-
* supply a jar directly, e.g. to share
|
|
110
|
-
* one across Clients or to persist it.
|
|
111
|
-
*/
|
|
112
|
-
jar?: CookieJar | undefined;
|
|
113
|
-
/**
|
|
114
|
-
* default 20.
|
|
115
|
-
*/
|
|
116
|
-
maxRedirects?: number | undefined;
|
|
117
|
-
/**
|
|
118
|
-
* enforced from Content-Length before a byte is read.
|
|
119
|
-
*/
|
|
120
|
-
maxBodyBytes?: number | undefined;
|
|
121
|
-
/**
|
|
122
|
-
* gzip/deflate. Default true. Never `br`; the runtime cannot
|
|
123
|
-
* decompress it, so it is never advertised either.
|
|
124
|
-
*/
|
|
125
|
-
decompress?: boolean | undefined;
|
|
126
|
-
/**
|
|
127
|
-
* default true.
|
|
128
|
-
*/
|
|
129
|
-
keepAlive?: boolean | undefined;
|
|
130
|
-
/**
|
|
131
|
-
* offer HTTP/2 via ALPN and speak it when the server selects it.
|
|
132
|
-
* Default true. The goal is ACCESS, not speed — some sites treat HTTP/1.1 as a bot signal — and
|
|
133
|
-
* on a CPU-billed runtime h2 costs MORE than h1 (HPACK is extra work). Set false to offer only
|
|
134
|
-
* `http/1.1`. There is no fallback-and-retry either way: the server's ALPN pick is followed.
|
|
135
|
-
*/
|
|
136
|
-
http2?: boolean | undefined;
|
|
137
|
-
/**
|
|
138
|
-
* never delegate to the platform's fetch, even when it could
|
|
139
|
-
* serve the request. Mainly for exercising this stack against origins that do not need it.
|
|
140
|
-
*/
|
|
141
|
-
forceTunnel?: boolean | undefined;
|
|
142
|
-
/**
|
|
143
|
-
* delegation target; defaults to `globalThis.fetch`.
|
|
144
|
-
*/
|
|
145
|
-
nativeFetch?: FetchLike | undefined;
|
|
146
|
-
/**
|
|
147
|
-
* connection pool sizing.
|
|
148
|
-
*/
|
|
149
|
-
pool?: {
|
|
150
|
-
maxPerKey?: number;
|
|
151
|
-
maxTotal?: number;
|
|
152
|
-
} | undefined;
|
|
153
|
-
limits?: Limits | undefined;
|
|
154
|
-
/**
|
|
155
|
-
* injectable randomness and key generation.
|
|
156
|
-
*/
|
|
157
|
-
deps?: import("./tls/connect.js").TlsDeps | undefined;
|
|
158
|
-
/**
|
|
159
|
-
* aborts every request this Client makes.
|
|
160
|
-
*/
|
|
161
|
-
signal?: AbortSignal | undefined;
|
|
162
|
-
/**
|
|
163
|
-
* epoch ms override, for certificate validity in tests.
|
|
164
|
-
*/
|
|
165
|
-
now?: number | undefined;
|
|
166
|
-
};
|
|
86
|
+
options: Readonly<ClientOptions>;
|
|
167
87
|
pool: ConnectionPool;
|
|
168
88
|
/** @type {Map<string, import('./http2/connection.js').Http2Connection>} */
|
|
169
89
|
_h2: Map<string, import("./http2/connection.js").Http2Connection>;
|
|
@@ -172,6 +92,8 @@ export class Client {
|
|
|
172
92
|
jar: CookieJar | null;
|
|
173
93
|
tickets: TicketStore;
|
|
174
94
|
_closed: boolean;
|
|
95
|
+
/** @type {Set<Promise<void>>} */
|
|
96
|
+
_inflight: Set<Promise<void>>;
|
|
175
97
|
/**
|
|
176
98
|
* @param {RequestInfo | URL} input
|
|
177
99
|
* @param {RequestInit} [init]
|
|
@@ -180,6 +102,16 @@ export class Client {
|
|
|
180
102
|
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response & {
|
|
181
103
|
tunnelfetch?: ResponseDetail;
|
|
182
104
|
}>;
|
|
105
|
+
/**
|
|
106
|
+
* Resolve once every response body handed out by this Client has finished, one way or another —
|
|
107
|
+
* read to the end, cancelled, or failed. Deliberately NOT folded into close(): close() is the
|
|
108
|
+
* forceful teardown, and a teardown that waits on the streams it is tearing down would hang on
|
|
109
|
+
* any body the caller abandoned. This is for callers that want the graceful order.
|
|
110
|
+
*
|
|
111
|
+
* Looping rather than a single Promise.all because a body settling can start another (a redirect
|
|
112
|
+
* drains its predecessor), and a set sampled once would miss the successor.
|
|
113
|
+
*/
|
|
114
|
+
idle(): Promise<void>;
|
|
183
115
|
/** Release every pooled socket and shared HTTP/2 connection. A Client that is finished must be
|
|
184
116
|
* closed or sockets leak for the isolate's lifetime. */
|
|
185
117
|
close(): Promise<void>;
|
|
@@ -271,10 +203,21 @@ export type ClientOptions = {
|
|
|
271
203
|
*/
|
|
272
204
|
maxBodyBytes?: number | undefined;
|
|
273
205
|
/**
|
|
274
|
-
* gzip/deflate. Default true.
|
|
275
|
-
* decompress it, so it is never advertised either.
|
|
206
|
+
* gzip/deflate. Default true.
|
|
276
207
|
*/
|
|
277
208
|
decompress?: boolean | undefined;
|
|
209
|
+
/**
|
|
210
|
+
* extra
|
|
211
|
+
* content-codings this client can read, e.g. `{ br: (s) => ... }`. Registering one is what
|
|
212
|
+
* makes advertising it honest, so each name is appended to Accept-Encoding — a client that
|
|
213
|
+
* asked for a coding it cannot decode would turn every such response into garbage. `br` and
|
|
214
|
+
* `zstd` are not built in because the runtime's DecompressionStream has neither and this
|
|
215
|
+
* package takes no dependencies; supply your own and the cost, and the supply chain, are
|
|
216
|
+
* yours and visible. Measured on the edge: WASM brotli decodes at about 2x native gzip, and
|
|
217
|
+
* the wire bytes it saves do not pay that back — see the README. The reason to turn it on is
|
|
218
|
+
* matching a browser's Accept-Encoding, not saving CPU.
|
|
219
|
+
*/
|
|
220
|
+
decoders?: Record<string, import("./client/decode.js").BodyDecoder> | undefined;
|
|
278
221
|
/**
|
|
279
222
|
* default true.
|
|
280
223
|
*/
|
|
@@ -316,8 +259,8 @@ export type ClientOptions = {
|
|
|
316
259
|
*/
|
|
317
260
|
now?: number | undefined;
|
|
318
261
|
};
|
|
319
|
-
import { CookieJar } from './client/cookies.js';
|
|
320
262
|
import { ConnectionPool } from './pool.js';
|
|
263
|
+
import { CookieJar } from './client/cookies.js';
|
|
321
264
|
import { TicketStore } from './tls/tickets.js';
|
|
322
265
|
import { utf8 } from './util/bytes.js';
|
|
323
266
|
export { CookieJar, ConnectionPool, utf8 };
|
|
@@ -34,19 +34,6 @@ export function buildRequestFields({ method, scheme, authority, path, headers }:
|
|
|
34
34
|
*/
|
|
35
35
|
export class Http2Retryable extends Http2Error {
|
|
36
36
|
}
|
|
37
|
-
/**
|
|
38
|
-
* @typedef {object} Http2ConnectionOptions
|
|
39
|
-
* @property {import('../transport.js').ConnectionInfo} [info] provenance attached to responses
|
|
40
|
-
* @property {number} [initialWindowSize] our SETTINGS_INITIAL_WINDOW_SIZE (receive window per
|
|
41
|
-
* stream). Defaults to curl's 10 MiB; tests lower it to exercise flow control.
|
|
42
|
-
* @property {number} [connectionWindow] the connection receive window we open with a WINDOW_UPDATE
|
|
43
|
-
* right after SETTINGS. Defaults to curl's 1000 MiB.
|
|
44
|
-
* @property {number} [maxConcurrentStreams] our advertised SETTINGS_MAX_CONCURRENT_STREAMS.
|
|
45
|
-
* @property {number} [maxHeaderTableSize] our advertised SETTINGS_HEADER_TABLE_SIZE.
|
|
46
|
-
* @property {number} [maxHeaderListSize] self-protection cap on a decoded response header list.
|
|
47
|
-
* @property {(err: Error | null) => void} [onClose] called once when the connection dies, so a
|
|
48
|
-
* registry can drop it.
|
|
49
|
-
*/
|
|
50
37
|
export class Http2Connection {
|
|
51
38
|
/**
|
|
52
39
|
* @param {import('../tls/connect.js').ByteDuplex | { readable: ReadableStream<Uint8Array>,
|
|
@@ -83,8 +70,10 @@ export class Http2Connection {
|
|
|
83
70
|
_continuation: {
|
|
84
71
|
streamId: any;
|
|
85
72
|
fragments: Uint8Array<ArrayBuffer>[];
|
|
73
|
+
bytes: number;
|
|
86
74
|
endStream: boolean;
|
|
87
75
|
} | null;
|
|
76
|
+
_maxHeaderBlockBytes: number;
|
|
88
77
|
_expectFirstSettings: boolean;
|
|
89
78
|
_fatal: any;
|
|
90
79
|
_goaway: {
|
|
@@ -137,6 +126,9 @@ export class Http2Connection {
|
|
|
137
126
|
/** @type {Uint8Array[]} */
|
|
138
127
|
recvQueue: Uint8Array[];
|
|
139
128
|
recvEnded: boolean;
|
|
129
|
+
/** @type {number | null} declared content-length, null when absent */
|
|
130
|
+
declaredLength: number | null;
|
|
131
|
+
receivedLength: number;
|
|
140
132
|
/** @type {Error | null} */
|
|
141
133
|
bodyError: Error | null;
|
|
142
134
|
/** @type {(() => void) | null} */
|
|
@@ -187,6 +179,14 @@ export class Http2Connection {
|
|
|
187
179
|
_dispatchFrame(frame: any): void;
|
|
188
180
|
_onSettings(flags: any, streamId: any, payload: any): void;
|
|
189
181
|
_onHeaders(flags: any, streamId: any, payload: any): void;
|
|
182
|
+
/**
|
|
183
|
+
* Enforce the raw header-block cap, killing the connection when it is passed.
|
|
184
|
+
* Connection-level rather than stream-level on purpose: the fragments are HPACK input, and
|
|
185
|
+
* abandoning a partial block would leave the shared decoder desynchronised for every other
|
|
186
|
+
* stream — which RFC 9113 s4.3 makes a connection error in its own right.
|
|
187
|
+
* @returns {boolean} true when assembly may continue
|
|
188
|
+
*/
|
|
189
|
+
_headerBlockWithinCap(): boolean;
|
|
190
190
|
_onContinuation(flags: any, payload: any): void;
|
|
191
191
|
/** A full header block has been assembled: HPACK-decode it (connection-fatal on failure, since
|
|
192
192
|
* HPACK state is shared) and route it to the stream as a response head or as trailers. */
|
|
@@ -225,24 +225,6 @@ export class Http2Connection {
|
|
|
225
225
|
_settleResolve(d: any, value: any): void;
|
|
226
226
|
_settleReject(d: any, err: any): void;
|
|
227
227
|
}
|
|
228
|
-
export type BodyStream = ReadableStream<Uint8Array> & {
|
|
229
|
-
completed: Promise<boolean>;
|
|
230
|
-
trailers: Promise<Headers | null>;
|
|
231
|
-
};
|
|
232
|
-
export type Http2ResponseHead = {
|
|
233
|
-
status: number;
|
|
234
|
-
/**
|
|
235
|
-
* always '' — HTTP/2 has no reason phrase
|
|
236
|
-
*/
|
|
237
|
-
statusText: string;
|
|
238
|
-
headers: Headers;
|
|
239
|
-
/**
|
|
240
|
-
* one entry per set-cookie field, kept separate like the h1 path
|
|
241
|
-
*/
|
|
242
|
-
setCookie: string[];
|
|
243
|
-
httpVersion: "2";
|
|
244
|
-
body: BodyStream;
|
|
245
|
-
};
|
|
246
228
|
export type Http2ConnectionOptions = {
|
|
247
229
|
/**
|
|
248
230
|
* provenance attached to responses
|
|
@@ -270,12 +252,37 @@ export type Http2ConnectionOptions = {
|
|
|
270
252
|
* self-protection cap on a decoded response header list.
|
|
271
253
|
*/
|
|
272
254
|
maxHeaderListSize?: number | undefined;
|
|
255
|
+
/**
|
|
256
|
+
* cap on the RAW bytes of one HEADERS+CONTINUATION run,
|
|
257
|
+
* before HPACK decoding. Default 262144, matching the decoded cap. This is the bound that stops
|
|
258
|
+
* a CONTINUATION flood; `maxHeaderListSize` cannot, because it is only reachable once the whole
|
|
259
|
+
* block has been assembled in memory.
|
|
260
|
+
*/
|
|
261
|
+
maxHeaderBlockBytes?: number | undefined;
|
|
273
262
|
/**
|
|
274
263
|
* called once when the connection dies, so a
|
|
275
264
|
* registry can drop it.
|
|
276
265
|
*/
|
|
277
266
|
onClose?: ((err: Error | null) => void) | undefined;
|
|
278
267
|
};
|
|
268
|
+
export type BodyStream = ReadableStream<Uint8Array> & {
|
|
269
|
+
completed: Promise<boolean>;
|
|
270
|
+
trailers: Promise<Headers | null>;
|
|
271
|
+
};
|
|
272
|
+
export type Http2ResponseHead = {
|
|
273
|
+
status: number;
|
|
274
|
+
/**
|
|
275
|
+
* always '' — HTTP/2 has no reason phrase
|
|
276
|
+
*/
|
|
277
|
+
statusText: string;
|
|
278
|
+
headers: Headers;
|
|
279
|
+
/**
|
|
280
|
+
* one entry per set-cookie field, kept separate like the h1 path
|
|
281
|
+
*/
|
|
282
|
+
setCookie: string[];
|
|
283
|
+
httpVersion: "2";
|
|
284
|
+
body: BodyStream;
|
|
285
|
+
};
|
|
279
286
|
import { Http2Error } from '../errors.js';
|
|
280
287
|
import { ByteReader } from '../util/bytes.js';
|
|
281
288
|
import { ByteWriter } from '../util/bytes.js';
|