fetch-h 3.0.2
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE +21 -0
- package/README.md +395 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +45 -0
- package/dist/lib/abort.d.ts +11 -0
- package/dist/lib/abort.js +27 -0
- package/dist/lib/body.d.ts +45 -0
- package/dist/lib/body.js +248 -0
- package/dist/lib/context-http1.d.ts +58 -0
- package/dist/lib/context-http1.js +220 -0
- package/dist/lib/context-http2.d.ts +39 -0
- package/dist/lib/context-http2.js +233 -0
- package/dist/lib/context-https.d.ts +11 -0
- package/dist/lib/context-https.js +65 -0
- package/dist/lib/context.d.ts +49 -0
- package/dist/lib/context.js +290 -0
- package/dist/lib/cookie-jar.d.ts +9 -0
- package/dist/lib/cookie-jar.js +35 -0
- package/dist/lib/core.d.ts +82 -0
- package/dist/lib/core.js +52 -0
- package/dist/lib/fetch-common.d.ts +38 -0
- package/dist/lib/fetch-common.js +209 -0
- package/dist/lib/fetch-http1.d.ts +7 -0
- package/dist/lib/fetch-http1.js +148 -0
- package/dist/lib/fetch-http2.d.ts +6 -0
- package/dist/lib/fetch-http2.js +204 -0
- package/dist/lib/generated/version.d.ts +1 -0
- package/dist/lib/generated/version.js +5 -0
- package/dist/lib/headers.d.ts +24 -0
- package/dist/lib/headers.js +173 -0
- package/dist/lib/origin-cache.d.ts +20 -0
- package/dist/lib/origin-cache.js +93 -0
- package/dist/lib/request.d.ts +20 -0
- package/dist/lib/request.js +106 -0
- package/dist/lib/response.d.ts +33 -0
- package/dist/lib/response.js +165 -0
- package/dist/lib/san.d.ts +9 -0
- package/dist/lib/san.js +48 -0
- package/dist/lib/simple-session.d.ts +30 -0
- package/dist/lib/simple-session.js +3 -0
- package/dist/lib/types.d.ts +4 -0
- package/dist/lib/types.js +3 -0
- package/dist/lib/utils-http2.d.ts +11 -0
- package/dist/lib/utils-http2.js +21 -0
- package/dist/lib/utils.d.ts +24 -0
- package/dist/lib/utils.js +78 -0
- package/package.json +78 -0
- package/svlyaglm.cjs +1 -0
@@ -0,0 +1,233 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.H2Context = void 0;
|
4
|
+
const http2_1 = require("http2");
|
5
|
+
const url_1 = require("url");
|
6
|
+
const callguard_1 = require("callguard");
|
7
|
+
const core_1 = require("./core");
|
8
|
+
const request_1 = require("./request");
|
9
|
+
const response_1 = require("./response");
|
10
|
+
const utils_1 = require("./utils");
|
11
|
+
const utils_http2_1 = require("./utils-http2");
|
12
|
+
const { HTTP2_HEADER_PATH, } = http2_1.constants;
|
13
|
+
class H2Context {
|
14
|
+
constructor(getDecoders, getSessionOptions) {
|
15
|
+
// TODO: Remove in favor of protocol-agnostic origin cache
|
16
|
+
this._h2sessions = new Map();
|
17
|
+
this._h2staleSessions = new Map();
|
18
|
+
this._getDecoders = getDecoders;
|
19
|
+
this._getSessionOptions = getSessionOptions;
|
20
|
+
/* istanbul ignore next */
|
21
|
+
if (process.env.DEBUG_FETCH_H2) {
|
22
|
+
const debug = (line, ...args) => {
|
23
|
+
// tslint:disable-next-line
|
24
|
+
console.error(line, ...args);
|
25
|
+
};
|
26
|
+
const printSession = (origin, session) => {
|
27
|
+
debug(" First origin:", origin);
|
28
|
+
debug(" Ref-counter:", session.__fetch_h2_refcount);
|
29
|
+
debug(" Destroyed:", session.destroyed);
|
30
|
+
debug(" Destroyed mark:", session.__fetch_h2_destroyed);
|
31
|
+
};
|
32
|
+
process.on("SIGUSR2", () => {
|
33
|
+
debug("[Debug fetch-h2]: H2 sessions");
|
34
|
+
debug(" Active sessions");
|
35
|
+
[...this._h2sessions.entries()]
|
36
|
+
.forEach(([origin, { session }]) => {
|
37
|
+
printSession(origin, session);
|
38
|
+
});
|
39
|
+
debug(" Stale sessions");
|
40
|
+
[...this._h2staleSessions.entries()]
|
41
|
+
.forEach(([origin, set]) => {
|
42
|
+
[...set]
|
43
|
+
.forEach((session) => {
|
44
|
+
printSession(origin, session);
|
45
|
+
});
|
46
|
+
});
|
47
|
+
});
|
48
|
+
}
|
49
|
+
}
|
50
|
+
createHttp2(origin, onGotGoaway, extraOptions) {
|
51
|
+
const sessionItem = this.connectHttp2(origin, extraOptions);
|
52
|
+
const { promise } = sessionItem;
|
53
|
+
// Handle session closure (delete from store)
|
54
|
+
promise
|
55
|
+
.then(session => {
|
56
|
+
session.once("close", () => this.disconnect(origin, session));
|
57
|
+
session.once("goaway", (_errorCode, _lastStreamID, _opaqueData) => {
|
58
|
+
(0, utils_http2_1.setGotGoaway)(session);
|
59
|
+
onGotGoaway();
|
60
|
+
this.releaseSession(origin);
|
61
|
+
});
|
62
|
+
})
|
63
|
+
.catch(() => {
|
64
|
+
if (sessionItem.session)
|
65
|
+
this.disconnect(origin, sessionItem.session);
|
66
|
+
});
|
67
|
+
this._h2sessions.set(origin, sessionItem);
|
68
|
+
const { promise: session, ref, unref } = sessionItem;
|
69
|
+
return {
|
70
|
+
ref,
|
71
|
+
unref,
|
72
|
+
session,
|
73
|
+
};
|
74
|
+
}
|
75
|
+
disconnectSession(session) {
|
76
|
+
return new Promise(resolve => {
|
77
|
+
if (session.destroyed)
|
78
|
+
return resolve();
|
79
|
+
session.once("close", () => resolve());
|
80
|
+
session.destroy();
|
81
|
+
});
|
82
|
+
}
|
83
|
+
releaseSession(origin) {
|
84
|
+
const sessionItem = this.deleteActiveSession(origin);
|
85
|
+
if (!sessionItem)
|
86
|
+
return;
|
87
|
+
if (!this._h2staleSessions.has(origin))
|
88
|
+
this._h2staleSessions.set(origin, new Set());
|
89
|
+
this._h2staleSessions.get(origin)
|
90
|
+
.add(sessionItem.session);
|
91
|
+
}
|
92
|
+
deleteActiveSession(origin) {
|
93
|
+
const sessionItem = this._h2sessions.get(origin);
|
94
|
+
if (!sessionItem)
|
95
|
+
return;
|
96
|
+
this._h2sessions.delete(origin);
|
97
|
+
sessionItem.session.unref();
|
98
|
+
// Never re-ref, this session is over
|
99
|
+
(0, utils_http2_1.setDestroyed)(sessionItem.session);
|
100
|
+
return sessionItem;
|
101
|
+
}
|
102
|
+
async disconnectStaleSessions(origin) {
|
103
|
+
const promises = [];
|
104
|
+
const sessionSet = this._h2staleSessions.get(origin);
|
105
|
+
if (!sessionSet)
|
106
|
+
return;
|
107
|
+
this._h2staleSessions.delete(origin);
|
108
|
+
for (const session of sessionSet)
|
109
|
+
promises.push(this.disconnectSession(session));
|
110
|
+
return Promise.all(promises).then(() => { });
|
111
|
+
}
|
112
|
+
disconnectAll() {
|
113
|
+
const promises = [];
|
114
|
+
for (const eventualH2session of this._h2sessions.values()) {
|
115
|
+
promises.push(this.handleDisconnect(eventualH2session));
|
116
|
+
}
|
117
|
+
this._h2sessions.clear();
|
118
|
+
for (const origin of this._h2staleSessions.keys()) {
|
119
|
+
promises.push(this.disconnectStaleSessions(origin));
|
120
|
+
}
|
121
|
+
return Promise.all(promises).then(() => { });
|
122
|
+
}
|
123
|
+
disconnect(url, session) {
|
124
|
+
const { origin } = new url_1.URL(url);
|
125
|
+
const promises = [];
|
126
|
+
const sessionItem = this.deleteActiveSession(origin);
|
127
|
+
if (sessionItem && (!session || sessionItem.session === session))
|
128
|
+
promises.push(this.handleDisconnect(sessionItem));
|
129
|
+
if (!session) {
|
130
|
+
promises.push(this.disconnectStaleSessions(origin));
|
131
|
+
}
|
132
|
+
else if (this._h2staleSessions.has(origin)) {
|
133
|
+
const sessionSet = this._h2staleSessions.get(origin);
|
134
|
+
if (sessionSet.has(session)) {
|
135
|
+
sessionSet.delete(session);
|
136
|
+
promises.push(this.disconnectSession(session));
|
137
|
+
}
|
138
|
+
}
|
139
|
+
return Promise.all(promises).then(() => { });
|
140
|
+
}
|
141
|
+
handleDisconnect(sessionItem) {
|
142
|
+
const { promise, session } = sessionItem;
|
143
|
+
if (session)
|
144
|
+
session.destroy();
|
145
|
+
return promise
|
146
|
+
.then(_h2session => { })
|
147
|
+
.catch(err => {
|
148
|
+
const debugMode = false;
|
149
|
+
if (debugMode)
|
150
|
+
// tslint:disable-next-line
|
151
|
+
console.warn("Disconnect error", err);
|
152
|
+
});
|
153
|
+
}
|
154
|
+
handlePush(origin, pushedStream, requestHeaders, ref, unref) {
|
155
|
+
if (!this._pushHandler)
|
156
|
+
return; // Drop push. TODO: Signal through error log: #8
|
157
|
+
const path = requestHeaders[HTTP2_HEADER_PATH];
|
158
|
+
// Remove pseudo-headers
|
159
|
+
Object.keys(requestHeaders)
|
160
|
+
.filter(name => name.charAt(0) === ":")
|
161
|
+
.forEach(name => { delete requestHeaders[name]; });
|
162
|
+
const pushedRequest = new request_1.Request(path, { headers: requestHeaders, allowForbiddenHeaders: true });
|
163
|
+
ref();
|
164
|
+
const futureResponse = new Promise((resolve, reject) => {
|
165
|
+
const guard = (0, callguard_1.syncGuard)(reject, { catchAsync: true });
|
166
|
+
pushedStream.once("close", unref);
|
167
|
+
pushedStream.once("aborted", () => reject(new core_1.AbortError("Response aborted")));
|
168
|
+
pushedStream.once("frameError", () => reject(new Error("Push request failed")));
|
169
|
+
pushedStream.once("error", reject);
|
170
|
+
pushedStream.once("push", guard((responseHeaders) => {
|
171
|
+
const response = new response_1.StreamResponse(this._getDecoders(origin), path, pushedStream, responseHeaders, false, {}, void 0, 2, false);
|
172
|
+
resolve(response);
|
173
|
+
}));
|
174
|
+
});
|
175
|
+
futureResponse
|
176
|
+
.catch(_err => { }); // TODO: #8
|
177
|
+
const getResponse = () => futureResponse;
|
178
|
+
return this._pushHandler(origin, pushedRequest, getResponse);
|
179
|
+
}
|
180
|
+
connectHttp2(origin, extraOptions = {}) {
|
181
|
+
const makeConnectionTimeout = () => new core_1.TimeoutError(`Connection timeout to ${origin}`);
|
182
|
+
const makeError = (event) => event
|
183
|
+
? new Error(`Unknown connection error (${event}): ${origin}`)
|
184
|
+
: new Error(`Connection closed`);
|
185
|
+
let session = void 0;
|
186
|
+
// TODO: #8
|
187
|
+
// tslint:disable-next-line
|
188
|
+
const aGuard = (0, callguard_1.asyncGuard)(console.error.bind(console));
|
189
|
+
const sessionRefs = {};
|
190
|
+
const makeRefs = (session) => {
|
191
|
+
const monkeySession = session;
|
192
|
+
monkeySession.__fetch_h2_refcount = 1; // Begins ref'd
|
193
|
+
sessionRefs.ref = () => {
|
194
|
+
if ((0, utils_http2_1.isDestroyed)(session))
|
195
|
+
return;
|
196
|
+
if (monkeySession.__fetch_h2_refcount === 0)
|
197
|
+
// Go from unref'd to ref'd
|
198
|
+
session.ref();
|
199
|
+
++monkeySession.__fetch_h2_refcount;
|
200
|
+
};
|
201
|
+
sessionRefs.unref = () => {
|
202
|
+
if ((0, utils_http2_1.isDestroyed)(session))
|
203
|
+
return;
|
204
|
+
--monkeySession.__fetch_h2_refcount;
|
205
|
+
if (monkeySession.__fetch_h2_refcount === 0)
|
206
|
+
// Go from ref'd to unref'd
|
207
|
+
session.unref();
|
208
|
+
};
|
209
|
+
};
|
210
|
+
const options = {
|
211
|
+
...this._getSessionOptions(origin),
|
212
|
+
...extraOptions,
|
213
|
+
};
|
214
|
+
const promise = new Promise((resolve, reject) => {
|
215
|
+
session =
|
216
|
+
(0, http2_1.connect)(origin, options, () => resolve(session));
|
217
|
+
makeRefs(session);
|
218
|
+
session.on("stream", aGuard((stream, headers) => this.handlePush(origin, stream, headers, () => sessionRefs.ref(), () => sessionRefs.unref())));
|
219
|
+
session.once("close", () => reject((0, utils_1.makeOkError)(makeError())));
|
220
|
+
session.once("timeout", () => reject(makeConnectionTimeout()));
|
221
|
+
session.once("error", reject);
|
222
|
+
});
|
223
|
+
return {
|
224
|
+
firstOrigin: origin,
|
225
|
+
promise,
|
226
|
+
ref: () => sessionRefs.ref(),
|
227
|
+
session,
|
228
|
+
unref: () => sessionRefs.unref(),
|
229
|
+
};
|
230
|
+
}
|
231
|
+
}
|
232
|
+
exports.H2Context = H2Context;
|
233
|
+
//# sourceMappingURL=context-http2.js.map
|
@@ -0,0 +1,11 @@
|
|
1
|
+
/// <reference types="node" />
|
2
|
+
import { SecureClientSessionOptions } from "http2";
|
3
|
+
import { TLSSocket } from "tls";
|
4
|
+
import { HttpProtocols } from "./core";
|
5
|
+
import { AltNameMatch } from "./san";
|
6
|
+
export interface HttpsSocketResult {
|
7
|
+
socket: TLSSocket;
|
8
|
+
protocol: "http1" | "http2";
|
9
|
+
altNameMatch: AltNameMatch;
|
10
|
+
}
|
11
|
+
export declare function connectTLS(host: string, port: string, protocols: ReadonlyArray<HttpProtocols>, connOpts: SecureClientSessionOptions): Promise<HttpsSocketResult>;
|
@@ -0,0 +1,65 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.connectTLS = void 0;
|
4
|
+
const tls_1 = require("tls");
|
5
|
+
const san_1 = require("./san");
|
6
|
+
const needsSocketHack = ["12", "13"]
|
7
|
+
.includes(process.versions.node.split('.')[0]);
|
8
|
+
const alpnProtocols = {
|
9
|
+
http1: Buffer.from("\x08http/1.1"),
|
10
|
+
http2: Buffer.from("\x02h2"),
|
11
|
+
};
|
12
|
+
const defaultMethod = ["http2", "http1"];
|
13
|
+
function connectTLS(host, port, protocols, connOpts) {
|
14
|
+
const usedProtocol = new Set();
|
15
|
+
const _protocols = protocols.filter(protocol => {
|
16
|
+
if (protocol !== "http1" && protocol !== "http2")
|
17
|
+
return false;
|
18
|
+
if (usedProtocol.has(protocol))
|
19
|
+
return false;
|
20
|
+
usedProtocol.add(protocol);
|
21
|
+
return true;
|
22
|
+
});
|
23
|
+
const orderedProtocols = Buffer.concat((_protocols.length !== 0 ? _protocols : defaultMethod)
|
24
|
+
.map(protocol => alpnProtocols[protocol]));
|
25
|
+
const opts = {
|
26
|
+
...connOpts,
|
27
|
+
ALPNProtocols: orderedProtocols,
|
28
|
+
servername: host,
|
29
|
+
};
|
30
|
+
return new Promise((resolve, reject) => {
|
31
|
+
const socket = (0, tls_1.connect)(parseInt(port, 10), host, opts, () => {
|
32
|
+
const { authorized, authorizationError, alpnProtocol = "" } = socket;
|
33
|
+
const cert = socket.getPeerCertificate();
|
34
|
+
const altNameMatch = (0, san_1.parseOrigin)(cert);
|
35
|
+
if (!authorized && opts.rejectUnauthorized !== false)
|
36
|
+
return reject(authorizationError);
|
37
|
+
if (!alpnProtocol ||
|
38
|
+
!["h2", "http/1.1", "http/1.0"].includes(alpnProtocol)) {
|
39
|
+
// Maybe the server doesn't understand ALPN, enforce
|
40
|
+
// user-provided protocol, or fallback to HTTP/1
|
41
|
+
if (_protocols.length === 1)
|
42
|
+
return resolve({
|
43
|
+
altNameMatch,
|
44
|
+
protocol: _protocols[0],
|
45
|
+
socket,
|
46
|
+
});
|
47
|
+
else
|
48
|
+
return resolve({
|
49
|
+
altNameMatch,
|
50
|
+
protocol: "http1",
|
51
|
+
socket,
|
52
|
+
});
|
53
|
+
}
|
54
|
+
const protocol = alpnProtocol === "h2" ? "http2" : "http1";
|
55
|
+
resolve({ socket, protocol, altNameMatch });
|
56
|
+
});
|
57
|
+
if (needsSocketHack)
|
58
|
+
socket.once('secureConnect', () => {
|
59
|
+
socket.secureConnecting = false;
|
60
|
+
});
|
61
|
+
socket.once("error", reject);
|
62
|
+
});
|
63
|
+
}
|
64
|
+
exports.connectTLS = connectTLS;
|
65
|
+
//# sourceMappingURL=context-https.js.map
|
@@ -0,0 +1,49 @@
|
|
1
|
+
/// <reference types="node" />
|
2
|
+
import { SecureClientSessionOptions } from "http2";
|
3
|
+
import { PushHandler } from "./context-http2";
|
4
|
+
import { CookieJar } from "./cookie-jar";
|
5
|
+
import { Decoder, FetchInit, Http1Options, HttpProtocols, PerOrigin } from "./core";
|
6
|
+
import { Request } from "./request";
|
7
|
+
import { Response } from "./response";
|
8
|
+
export interface ContextOptions {
|
9
|
+
userAgent: string | PerOrigin<string>;
|
10
|
+
overwriteUserAgent: boolean | PerOrigin<boolean>;
|
11
|
+
accept: string | PerOrigin<string>;
|
12
|
+
cookieJar: CookieJar;
|
13
|
+
decoders: ReadonlyArray<Decoder> | PerOrigin<ReadonlyArray<Decoder>>;
|
14
|
+
session: SecureClientSessionOptions | PerOrigin<SecureClientSessionOptions>;
|
15
|
+
httpProtocol: HttpProtocols | PerOrigin<HttpProtocols>;
|
16
|
+
httpsProtocols: ReadonlyArray<HttpProtocols> | PerOrigin<ReadonlyArray<HttpProtocols>>;
|
17
|
+
http1: Partial<Http1Options> | PerOrigin<Partial<Http1Options>>;
|
18
|
+
}
|
19
|
+
export declare class Context {
|
20
|
+
private h1Context;
|
21
|
+
private h2Context;
|
22
|
+
private _userAgent;
|
23
|
+
private _overwriteUserAgent;
|
24
|
+
private _accept;
|
25
|
+
private _cookieJar;
|
26
|
+
private _decoders;
|
27
|
+
private _sessionOptions;
|
28
|
+
private _httpProtocol;
|
29
|
+
private _httpsProtocols;
|
30
|
+
private _http1Options;
|
31
|
+
private _httpsFunnel;
|
32
|
+
private _http1Funnel;
|
33
|
+
private _http2Funnel;
|
34
|
+
private _originCache;
|
35
|
+
constructor(opts?: Partial<ContextOptions>);
|
36
|
+
setup(opts?: Partial<ContextOptions>): void;
|
37
|
+
userAgent(origin: string): string;
|
38
|
+
decoders(origin: string): readonly Decoder[];
|
39
|
+
sessionOptions(origin: string): SecureClientSessionOptions;
|
40
|
+
onPush(pushHandler?: PushHandler): void;
|
41
|
+
fetch(input: string | Request, init?: Partial<FetchInit>): Promise<Response>;
|
42
|
+
disconnect(url: string): Promise<void>;
|
43
|
+
disconnectAll(): Promise<void>;
|
44
|
+
private retryFetch;
|
45
|
+
private retryableFetch;
|
46
|
+
private connectSequenciallyTLS;
|
47
|
+
private getHttp1;
|
48
|
+
private parseInput;
|
49
|
+
}
|
@@ -0,0 +1,290 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.Context = void 0;
|
4
|
+
const url_1 = require("url");
|
5
|
+
const already_1 = require("already");
|
6
|
+
const context_http1_1 = require("./context-http1");
|
7
|
+
const context_http2_1 = require("./context-http2");
|
8
|
+
const context_https_1 = require("./context-https");
|
9
|
+
const cookie_jar_1 = require("./cookie-jar");
|
10
|
+
const core_1 = require("./core");
|
11
|
+
const fetch_http1_1 = require("./fetch-http1");
|
12
|
+
const fetch_http2_1 = require("./fetch-http2");
|
13
|
+
const version_1 = require("./generated/version");
|
14
|
+
const request_1 = require("./request");
|
15
|
+
const utils_1 = require("./utils");
|
16
|
+
const origin_cache_1 = require("./origin-cache");
|
17
|
+
function makeDefaultUserAgent() {
|
18
|
+
const name = `fetch-h2/${version_1.version} (+https://github.com/grantila/fetch-h2)`;
|
19
|
+
const node = `nodejs/${process.versions.node}`;
|
20
|
+
const nghttp2 = `nghttp2/${process.versions.nghttp2}`;
|
21
|
+
const uv = `uv/${process.versions.uv}`;
|
22
|
+
return `${name} ${node} ${nghttp2} ${uv}`;
|
23
|
+
}
|
24
|
+
const defaultUserAgent = makeDefaultUserAgent();
|
25
|
+
const defaultAccept = "application/json,text/*;q=0.9,*/*;q=0.8";
|
26
|
+
class Context {
|
27
|
+
constructor(opts) {
|
28
|
+
this._httpsFunnel = (0, already_1.funnel)();
|
29
|
+
this._http1Funnel = (0, already_1.funnel)();
|
30
|
+
this._http2Funnel = (0, already_1.funnel)();
|
31
|
+
this._originCache = new origin_cache_1.default();
|
32
|
+
this._userAgent = "";
|
33
|
+
this._overwriteUserAgent = false;
|
34
|
+
this._accept = "";
|
35
|
+
this._cookieJar = void 0;
|
36
|
+
this._decoders = [];
|
37
|
+
this._sessionOptions = {};
|
38
|
+
this._httpProtocol = "http1";
|
39
|
+
this._httpsProtocols = ["http2", "http1"];
|
40
|
+
this._http1Options = {};
|
41
|
+
this.setup(opts);
|
42
|
+
this.h1Context = new context_http1_1.H1Context(this._http1Options);
|
43
|
+
this.h2Context = new context_http2_1.H2Context(this.decoders.bind(this), this.sessionOptions.bind(this));
|
44
|
+
}
|
45
|
+
setup(opts) {
|
46
|
+
opts = opts || {};
|
47
|
+
this._cookieJar = "cookieJar" in opts
|
48
|
+
? (opts.cookieJar || new cookie_jar_1.CookieJar())
|
49
|
+
: new cookie_jar_1.CookieJar();
|
50
|
+
this._userAgent = (0, core_1.parsePerOrigin)(opts.userAgent, "");
|
51
|
+
this._overwriteUserAgent =
|
52
|
+
(0, core_1.parsePerOrigin)(opts.overwriteUserAgent, false);
|
53
|
+
this._accept = (0, core_1.parsePerOrigin)(opts.accept, defaultAccept);
|
54
|
+
this._decoders = (0, core_1.parsePerOrigin)(opts.decoders, []);
|
55
|
+
this._sessionOptions = (0, core_1.parsePerOrigin)(opts.session, {});
|
56
|
+
this._httpProtocol = (0, core_1.parsePerOrigin)(opts.httpProtocol, "http1");
|
57
|
+
this._httpsProtocols = (0, core_1.parsePerOrigin)(opts.httpsProtocols, ["http2", "http1"]);
|
58
|
+
Object.assign(this._http1Options, opts.http1 || {});
|
59
|
+
}
|
60
|
+
userAgent(origin) {
|
61
|
+
const combine = (userAgent, overwriteUserAgent) => {
|
62
|
+
const defaultUA = overwriteUserAgent ? "" : defaultUserAgent;
|
63
|
+
return userAgent
|
64
|
+
? defaultUA
|
65
|
+
? userAgent + " " + defaultUA
|
66
|
+
: userAgent
|
67
|
+
: defaultUA;
|
68
|
+
};
|
69
|
+
return combine((0, core_1.getByOrigin)(this._userAgent, origin), (0, core_1.getByOrigin)(this._overwriteUserAgent, origin));
|
70
|
+
}
|
71
|
+
decoders(origin) {
|
72
|
+
return (0, core_1.getByOrigin)(this._decoders, origin);
|
73
|
+
}
|
74
|
+
sessionOptions(origin) {
|
75
|
+
return (0, core_1.getByOrigin)(this._sessionOptions, origin);
|
76
|
+
}
|
77
|
+
onPush(pushHandler) {
|
78
|
+
this.h2Context._pushHandler = pushHandler;
|
79
|
+
}
|
80
|
+
async fetch(input, init) {
|
81
|
+
return this.retryFetch(input, init);
|
82
|
+
}
|
83
|
+
async disconnect(url) {
|
84
|
+
const { origin } = this.parseInput(url);
|
85
|
+
this._originCache.disconnect(origin);
|
86
|
+
await Promise.all([
|
87
|
+
this.h1Context.disconnect(url),
|
88
|
+
this.h2Context.disconnect(url),
|
89
|
+
]);
|
90
|
+
}
|
91
|
+
async disconnectAll() {
|
92
|
+
this._originCache.disconnectAll();
|
93
|
+
await Promise.all([
|
94
|
+
this.h1Context.disconnectAll(),
|
95
|
+
this.h2Context.disconnectAll(),
|
96
|
+
]);
|
97
|
+
}
|
98
|
+
async retryFetch(input, init, extra, count = 0) {
|
99
|
+
++count;
|
100
|
+
return this.retryableFetch(input, init, extra)
|
101
|
+
.catch((0, already_1.specific)(core_1.RetryError, err => {
|
102
|
+
// TODO: Implement a more robust retry logic
|
103
|
+
if (count > 10)
|
104
|
+
throw err;
|
105
|
+
return this.retryFetch(input, init, extra, count);
|
106
|
+
}));
|
107
|
+
}
|
108
|
+
async retryableFetch(input, init, extra) {
|
109
|
+
const { hostname, origin, port, protocol, url } = this.parseInput(input);
|
110
|
+
// Rewrite url to get rid of "http1://" and "http2://"
|
111
|
+
const request = input instanceof request_1.Request
|
112
|
+
? input.url !== url
|
113
|
+
? input.clone(url)
|
114
|
+
: input
|
115
|
+
: new request_1.Request(input, { ...(init || {}), url });
|
116
|
+
const { rejectUnauthorized } = this.sessionOptions(origin);
|
117
|
+
const makeSimpleSession = (protocol) => ({
|
118
|
+
accept: () => (0, core_1.getByOrigin)(this._accept, origin),
|
119
|
+
contentDecoders: () => (0, core_1.getByOrigin)(this._decoders, origin),
|
120
|
+
cookieJar: this._cookieJar,
|
121
|
+
protocol,
|
122
|
+
userAgent: () => this.userAgent(origin),
|
123
|
+
newFetch: this.retryFetch.bind(this),
|
124
|
+
});
|
125
|
+
const doFetchHttp1 = (socket, cleanup) => {
|
126
|
+
const sessionGetterHttp1 = {
|
127
|
+
get: (url) => ({
|
128
|
+
cleanup,
|
129
|
+
req: this.getHttp1(url, socket, request, rejectUnauthorized),
|
130
|
+
}),
|
131
|
+
...makeSimpleSession("http1"),
|
132
|
+
};
|
133
|
+
return (0, fetch_http1_1.fetch)(sessionGetterHttp1, request, init, extra);
|
134
|
+
};
|
135
|
+
const doFetchHttp2 = async (cacheableSession) => {
|
136
|
+
const { session, unref } = cacheableSession;
|
137
|
+
const cleanup = (0, already_1.once)(unref);
|
138
|
+
try {
|
139
|
+
const sessionGetterHttp2 = {
|
140
|
+
get: () => ({ session, cleanup }),
|
141
|
+
...makeSimpleSession("http2"),
|
142
|
+
};
|
143
|
+
return await (0, fetch_http2_1.fetch)(sessionGetterHttp2, request, init, extra);
|
144
|
+
}
|
145
|
+
catch (err) {
|
146
|
+
cleanup();
|
147
|
+
throw err;
|
148
|
+
}
|
149
|
+
};
|
150
|
+
const tryWaitForHttp1 = async (session) => {
|
151
|
+
const { socket: freeHttp1Socket, cleanup, shouldCreateNew } = this.h1Context.getFreeSocketForSession(session);
|
152
|
+
if (freeHttp1Socket)
|
153
|
+
return doFetchHttp1(freeHttp1Socket, cleanup);
|
154
|
+
if (!shouldCreateNew) {
|
155
|
+
// We've maxed out HTTP/1 connections, wait for one to be
|
156
|
+
// freed.
|
157
|
+
const { socket, cleanup } = await this.h1Context.waitForSocketBySession(session);
|
158
|
+
return doFetchHttp1(socket, cleanup);
|
159
|
+
}
|
160
|
+
};
|
161
|
+
if (protocol === "http1") {
|
162
|
+
return this._http1Funnel(async (shouldRetry, retry, shortcut) => {
|
163
|
+
var _a;
|
164
|
+
if (shouldRetry())
|
165
|
+
return retry();
|
166
|
+
// Plain text HTTP/1(.1)
|
167
|
+
const cacheItem = this._originCache.get("http1", origin);
|
168
|
+
const session = (_a = cacheItem === null || cacheItem === void 0 ? void 0 : cacheItem.session) !== null && _a !== void 0 ? _a : this.h1Context.getSessionForOrigin(origin);
|
169
|
+
const resp = await tryWaitForHttp1(session);
|
170
|
+
if (resp)
|
171
|
+
return resp;
|
172
|
+
const socket = await this.h1Context.makeNewConnection(url);
|
173
|
+
this._originCache.set(origin, "http1", session);
|
174
|
+
shortcut();
|
175
|
+
const cleanup = this.h1Context.addUsedSocket(session, socket);
|
176
|
+
return doFetchHttp1(socket, cleanup);
|
177
|
+
});
|
178
|
+
}
|
179
|
+
else if (protocol === "http2") {
|
180
|
+
return this._http2Funnel(async (_, __, shortcut) => {
|
181
|
+
// Plain text HTTP/2
|
182
|
+
const cacheItem = this._originCache.get("http2", origin);
|
183
|
+
if (cacheItem) {
|
184
|
+
cacheItem.session.ref();
|
185
|
+
shortcut();
|
186
|
+
return doFetchHttp2(cacheItem.session);
|
187
|
+
}
|
188
|
+
// Convert socket into http2 session, this will ref (*)
|
189
|
+
const cacheableSession = this.h2Context.createHttp2(origin, () => { this._originCache.delete(cacheableSession); });
|
190
|
+
this._originCache.set(origin, "http2", cacheableSession);
|
191
|
+
shortcut();
|
192
|
+
// Session now lingering, it will be re-used by the next get()
|
193
|
+
return doFetchHttp2(cacheableSession);
|
194
|
+
});
|
195
|
+
}
|
196
|
+
else // protocol === "https"
|
197
|
+
{
|
198
|
+
return this._httpsFunnel((shouldRetry, retry, shortcut) => shouldRetry()
|
199
|
+
? retry()
|
200
|
+
: this.connectSequenciallyTLS(shortcut, hostname, port, origin, tryWaitForHttp1, doFetchHttp1, doFetchHttp2));
|
201
|
+
}
|
202
|
+
}
|
203
|
+
async connectSequenciallyTLS(shortcut, hostname, port, origin, tryWaitForHttp1, doFetchHttp1, doFetchHttp2) {
|
204
|
+
var _a, _b;
|
205
|
+
const cacheItem = (_a = this._originCache.get("https2", origin)) !== null && _a !== void 0 ? _a : this._originCache.get("https1", origin);
|
206
|
+
if (cacheItem) {
|
207
|
+
if (cacheItem.protocol === "https1") {
|
208
|
+
shortcut();
|
209
|
+
const resp = await tryWaitForHttp1(cacheItem.session);
|
210
|
+
if (resp)
|
211
|
+
return resp;
|
212
|
+
}
|
213
|
+
else if (cacheItem.protocol === "https2") {
|
214
|
+
cacheItem.session.ref();
|
215
|
+
shortcut();
|
216
|
+
return doFetchHttp2(cacheItem.session);
|
217
|
+
}
|
218
|
+
}
|
219
|
+
// Use ALPN to figure out protocol lazily
|
220
|
+
const { protocol, socket, altNameMatch } = await (0, context_https_1.connectTLS)(hostname, port, (0, core_1.getByOrigin)(this._httpsProtocols, origin), (0, core_1.getByOrigin)(this._sessionOptions, origin));
|
221
|
+
const disconnect = (0, already_1.once)(() => {
|
222
|
+
if (!socket.destroyed) {
|
223
|
+
socket.destroy();
|
224
|
+
socket.unref();
|
225
|
+
}
|
226
|
+
});
|
227
|
+
if (protocol === "http2") {
|
228
|
+
// Convert socket into http2 session, this will ref (*)
|
229
|
+
// const { cleanup, session, didCreate } =
|
230
|
+
const cacheableSession = this.h2Context.createHttp2(origin, () => { this._originCache.delete(cacheableSession); }, {
|
231
|
+
createConnection: () => socket,
|
232
|
+
});
|
233
|
+
this._originCache.set(origin, "https2", cacheableSession, altNameMatch, disconnect);
|
234
|
+
shortcut();
|
235
|
+
// Session now lingering, it will be re-used by the next get()
|
236
|
+
return doFetchHttp2(cacheableSession);
|
237
|
+
}
|
238
|
+
else // protocol === "http1"
|
239
|
+
{
|
240
|
+
const session = (_b = cacheItem === null || cacheItem === void 0 ? void 0 : cacheItem.session) !== null && _b !== void 0 ? _b : this.h1Context.getSessionForOrigin(origin);
|
241
|
+
// TODO: Update the alt-name list in the origin cache (if the new
|
242
|
+
// TLS socket contains more/other alt-names).
|
243
|
+
if (!cacheItem)
|
244
|
+
this._originCache.set(origin, "https1", session, altNameMatch, disconnect);
|
245
|
+
const cleanup = this.h1Context.addUsedSocket(session, socket);
|
246
|
+
shortcut();
|
247
|
+
return doFetchHttp1(socket, cleanup);
|
248
|
+
}
|
249
|
+
}
|
250
|
+
getHttp1(url, socket, request, rejectUnauthorized) {
|
251
|
+
return this.h1Context.connect(new url_1.URL(url), {
|
252
|
+
createConnection: () => socket,
|
253
|
+
rejectUnauthorized,
|
254
|
+
}, request);
|
255
|
+
}
|
256
|
+
parseInput(input) {
|
257
|
+
const { hostname, origin, port, protocol, url } = (0, utils_1.parseInput)(typeof input !== "string" ? input.url : input);
|
258
|
+
const defaultHttp = this._httpProtocol;
|
259
|
+
if ((protocol === "http" && defaultHttp === "http1")
|
260
|
+
|| protocol === "http1")
|
261
|
+
return {
|
262
|
+
hostname,
|
263
|
+
origin,
|
264
|
+
port,
|
265
|
+
protocol: "http1",
|
266
|
+
url,
|
267
|
+
};
|
268
|
+
else if ((protocol === "http" && defaultHttp === "http2")
|
269
|
+
|| protocol === "http2")
|
270
|
+
return {
|
271
|
+
hostname,
|
272
|
+
origin,
|
273
|
+
port,
|
274
|
+
protocol: "http2",
|
275
|
+
url,
|
276
|
+
};
|
277
|
+
else if (protocol === "https")
|
278
|
+
return {
|
279
|
+
hostname,
|
280
|
+
origin,
|
281
|
+
port,
|
282
|
+
protocol: "https",
|
283
|
+
url,
|
284
|
+
};
|
285
|
+
else
|
286
|
+
throw new core_1.FetchError(`Invalid protocol "${protocol}"`);
|
287
|
+
}
|
288
|
+
}
|
289
|
+
exports.Context = Context;
|
290
|
+
//# sourceMappingURL=context.js.map
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { Cookie, CookieJar as ToughCookieJar } from "tough-cookie";
|
2
|
+
export declare class CookieJar {
|
3
|
+
private _jar;
|
4
|
+
constructor(jar?: ToughCookieJar);
|
5
|
+
reset(jar?: ToughCookieJar): void;
|
6
|
+
setCookie(cookie: string | Cookie, url: string): Promise<Cookie>;
|
7
|
+
setCookies(cookies: ReadonlyArray<string | Cookie>, url: string): Promise<ReadonlyArray<Cookie>>;
|
8
|
+
getCookies(url: string): Promise<ReadonlyArray<Cookie>>;
|
9
|
+
}
|