fix
This commit is contained in:
21
book/node_modules/http2-wrapper/LICENSE
generated
vendored
Normal file
21
book/node_modules/http2-wrapper/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Szymon Marczak
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
459
book/node_modules/http2-wrapper/README.md
generated
vendored
Normal file
459
book/node_modules/http2-wrapper/README.md
generated
vendored
Normal file
@ -0,0 +1,459 @@
|
||||
# http2-wrapper
|
||||
> HTTP/2 client, just with the familiar `https` API
|
||||
|
||||
[](https://github.com/szmarczak/http2-wrapper/actions)
|
||||
[](https://codecov.io/gh/szmarczak/http2-wrapper)
|
||||
[](https://www.npmjs.com/package/http2-wrapper)
|
||||
[](https://packagephobia.now.sh/result?p=http2-wrapper)
|
||||
|
||||
This package was created to support HTTP/2 without the need to rewrite your code.<br>
|
||||
I recommend adapting to the [`http2`](https://nodejs.org/api/http2.html) module if possible - it's much simpler to use and has many cool features!
|
||||
|
||||
**Tip**: `http2-wrapper` is very useful when you rely on other modules that use the HTTP/1 API and you want to support HTTP/2.
|
||||
|
||||
**Pro Tip**: While the native `http2` doesn't have agents yet, you can use `http2-wrapper` Agents and still operate on the native HTTP/2 streams.
|
||||
|
||||
## Installation
|
||||
|
||||
> `$ npm install http2-wrapper`<br>
|
||||
> `$ yarn add http2-wrapper`
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
const http2 = require('http2-wrapper');
|
||||
|
||||
const options = {
|
||||
hostname: 'nghttp2.org',
|
||||
protocol: 'https:',
|
||||
path: '/httpbin/post',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-length': 6
|
||||
}
|
||||
};
|
||||
|
||||
const request = http2.request(options, response => {
|
||||
console.log('statusCode:', response.statusCode);
|
||||
console.log('headers:', response.headers);
|
||||
|
||||
const body = [];
|
||||
response.on('data', chunk => {
|
||||
body.push(chunk);
|
||||
});
|
||||
response.on('end', () => {
|
||||
console.log('body:', Buffer.concat(body).toString());
|
||||
});
|
||||
});
|
||||
|
||||
request.on('error', console.error);
|
||||
|
||||
request.write('123');
|
||||
request.end('456');
|
||||
|
||||
// statusCode: 200
|
||||
// headers: [Object: null prototype] {
|
||||
// ':status': 200,
|
||||
// date: 'Fri, 27 Sep 2019 19:45:46 GMT',
|
||||
// 'content-type': 'application/json',
|
||||
// 'access-control-allow-origin': '*',
|
||||
// 'access-control-allow-credentials': 'true',
|
||||
// 'content-length': '239',
|
||||
// 'x-backend-header-rtt': '0.002516',
|
||||
// 'strict-transport-security': 'max-age=31536000',
|
||||
// server: 'nghttpx',
|
||||
// via: '1.1 nghttpx',
|
||||
// 'alt-svc': 'h3-23=":4433"; ma=3600',
|
||||
// 'x-frame-options': 'SAMEORIGIN',
|
||||
// 'x-xss-protection': '1; mode=block',
|
||||
// 'x-content-type-options': 'nosniff'
|
||||
// }
|
||||
// body: {
|
||||
// "args": {},
|
||||
// "data": "123456",
|
||||
// "files": {},
|
||||
// "form": {},
|
||||
// "headers": {
|
||||
// "Content-Length": "6",
|
||||
// "Host": "nghttp2.org"
|
||||
// },
|
||||
// "json": 123456,
|
||||
// "origin": "xxx.xxx.xxx.xxx",
|
||||
// "url": "https://nghttp2.org/httpbin/post"
|
||||
// }
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
**Note:** The `session` option was renamed to `tlsSession` for better readability.
|
||||
|
||||
**Note:** The `timeout` option applies to HTTP/2 streams only. In order to set session timeout, pass an Agent with custom `timeout` option set.
|
||||
|
||||
### http2.auto(url, options, callback)
|
||||
|
||||
Performs [ALPN](https://nodejs.org/api/tls.html#tls_alpn_and_sni) negotiation.
|
||||
Returns a Promise giving proper `ClientRequest` instance (depending on the ALPN).
|
||||
|
||||
**Note**: The `agent` option represents an object with `http`, `https` and `http2` properties.
|
||||
|
||||
```js
|
||||
const http2 = require('http2-wrapper');
|
||||
|
||||
const options = {
|
||||
hostname: 'httpbin.org',
|
||||
protocol: 'http:', // Try changing this to https:
|
||||
path: '/post',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-length': 6
|
||||
}
|
||||
};
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const request = await http2.auto(options, response => {
|
||||
console.log('statusCode:', response.statusCode);
|
||||
console.log('headers:', response.headers);
|
||||
|
||||
const body = [];
|
||||
response.on('data', chunk => body.push(chunk));
|
||||
response.on('end', () => {
|
||||
console.log('body:', Buffer.concat(body).toString());
|
||||
});
|
||||
});
|
||||
|
||||
request.on('error', console.error);
|
||||
|
||||
request.write('123');
|
||||
request.end('456');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})();
|
||||
|
||||
// statusCode: 200
|
||||
// headers: { connection: 'close',
|
||||
// server: 'gunicorn/19.9.0',
|
||||
// date: 'Sat, 15 Dec 2018 18:19:32 GMT',
|
||||
// 'content-type': 'application/json',
|
||||
// 'content-length': '259',
|
||||
// 'access-control-allow-origin': '*',
|
||||
// 'access-control-allow-credentials': 'true',
|
||||
// via: '1.1 vegur' }
|
||||
// body: {
|
||||
// "args": {},
|
||||
// "data": "123456",
|
||||
// "files": {},
|
||||
// "form": {},
|
||||
// "headers": {
|
||||
// "Connection": "close",
|
||||
// "Content-Length": "6",
|
||||
// "Host": "httpbin.org"
|
||||
// },
|
||||
// "json": 123456,
|
||||
// "origin": "xxx.xxx.xxx.xxx",
|
||||
// "url": "http://httpbin.org/post"
|
||||
// }
|
||||
```
|
||||
|
||||
### http2.auto.protocolCache
|
||||
|
||||
An instance of [`quick-lru`](https://github.com/sindresorhus/quick-lru) used for ALPN cache.
|
||||
|
||||
There is a maximum of 100 entries. You can modify the limit through `protocolCache.maxSize` - note that the change will be visible globally.
|
||||
|
||||
### http2.auto.createResolveProtocol(cache, queue, connect)
|
||||
|
||||
#### cache
|
||||
|
||||
Type: `Map<string, string>`
|
||||
|
||||
This is the store where cached ALPN protocols are put into.
|
||||
|
||||
#### queue
|
||||
|
||||
Type: `Map<string, Promise>`
|
||||
|
||||
This is the store that contains pending ALPN negotiation promises.
|
||||
|
||||
#### connect
|
||||
|
||||
Type: `(options, callback) => TLSSocket | Promise<TLSSocket>`
|
||||
|
||||
See https://github.com/szmarczak/resolve-alpn#connect
|
||||
|
||||
### http2.auto.resolveProtocol(options)
|
||||
|
||||
Returns a `Promise<{alpnProtocol: string}>`.
|
||||
|
||||
### http2.request(url, options, callback)
|
||||
|
||||
Same as [`https.request`](https://nodejs.org/api/https.html#https_https_request_options_callback).
|
||||
|
||||
##### options.h2session
|
||||
|
||||
Type: `Http2Session`<br>
|
||||
|
||||
The session used to make the actual request. If none provided, it will use `options.agent` to get one.
|
||||
|
||||
### http2.get(url, options, callback)
|
||||
|
||||
Same as [`https.get`](https://nodejs.org/api/https.html#https_https_get_options_callback).
|
||||
|
||||
### new http2.ClientRequest(url, options, callback)
|
||||
|
||||
Same as [`https.ClientRequest`](https://nodejs.org/api/https.html#https_class_https_clientrequest).
|
||||
|
||||
### new http2.IncomingMessage(socket)
|
||||
|
||||
Same as [`https.IncomingMessage`](https://nodejs.org/api/https.html#https_class_https_incomingmessage).
|
||||
|
||||
### new http2.Agent(options)
|
||||
|
||||
**Note:** this is **not** compatible with the classic `http.Agent`.
|
||||
|
||||
Usage example:
|
||||
|
||||
```js
|
||||
const http2 = require('http2-wrapper');
|
||||
|
||||
class MyAgent extends http2.Agent {
|
||||
createConnection(origin, options) {
|
||||
console.log(`Connecting to ${http2.Agent.normalizeOrigin(origin)}`);
|
||||
return http2.Agent.connect(origin, options);
|
||||
}
|
||||
}
|
||||
|
||||
http2.get({
|
||||
hostname: 'google.com',
|
||||
agent: new MyAgent()
|
||||
}, response => {
|
||||
response.on('data', chunk => console.log(`Received chunk of ${chunk.length} bytes`));
|
||||
});
|
||||
```
|
||||
|
||||
#### options
|
||||
|
||||
Each option is an `Agent` property and can be changed later.
|
||||
|
||||
##### timeout
|
||||
|
||||
Type: `number`<br>
|
||||
Default: `0`
|
||||
|
||||
If there's no activity after `timeout` milliseconds, the session will be closed. If `0`, no timeout is applied.
|
||||
|
||||
##### maxSessions
|
||||
|
||||
Type: `number`<br>
|
||||
Default: `Infinity`
|
||||
|
||||
The maximum amount of sessions in total.
|
||||
|
||||
##### maxEmptySessions
|
||||
|
||||
Type: `number`<br>
|
||||
Default: `10`
|
||||
|
||||
The maximum amount of empty sessions in total. An empty session is a session with no pending requests.
|
||||
|
||||
##### maxCachedTlsSessions
|
||||
|
||||
Type: `number`<br>
|
||||
Default: `100`
|
||||
|
||||
The maximum amount of cached TLS sessions.
|
||||
|
||||
#### agent.protocol
|
||||
|
||||
Type: `string`<br>
|
||||
Default: `https:`
|
||||
|
||||
#### agent.settings
|
||||
|
||||
Type: `object`<br>
|
||||
Default: `{enablePush: false}`
|
||||
|
||||
[Settings](https://nodejs.org/api/http2.html#http2_settings_object) used by the current agent instance.
|
||||
|
||||
#### agent.normalizeOptions([options](https://github.com/szmarczak/http2-wrapper/blob/master/source/agent.js))
|
||||
|
||||
Returns a string representing normalized options.
|
||||
|
||||
```js
|
||||
Agent.normalizeOptions({servername: 'example.com'});
|
||||
// => ':::::::::::::::::::::::::::::::::::::'
|
||||
```
|
||||
|
||||
#### agent.getSession(origin, options)
|
||||
|
||||
##### [origin](https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener)
|
||||
|
||||
Type: `string` `URL` `object`
|
||||
|
||||
Origin used to create new session.
|
||||
|
||||
##### [options](https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener)
|
||||
|
||||
Type: `object`
|
||||
|
||||
Options used to create new session.
|
||||
|
||||
Returns a Promise giving free `Http2Session`. If no free sessions are found, a new one is created.
|
||||
|
||||
A session is considered free when pending streams count is less than max concurrent streams settings.
|
||||
|
||||
#### agent.getSession([origin](#origin), [options](options-1), listener)
|
||||
|
||||
##### listener
|
||||
|
||||
Type: `object`
|
||||
|
||||
```
|
||||
{
|
||||
reject: error => void,
|
||||
resolve: session => void
|
||||
}
|
||||
```
|
||||
|
||||
If the `listener` argument is present, the Promise will resolve immediately. It will use the `resolve` function to pass the session.
|
||||
|
||||
#### agent.request([origin](#origin), [options](#options-1), [headers](https://nodejs.org/api/http2.html#http2_headers_object), [streamOptions](https://nodejs.org/api/http2.html#http2_clienthttp2session_request_headers_options))
|
||||
|
||||
Returns a Promise giving `Http2Stream`.
|
||||
|
||||
#### agent.createConnection([origin](#origin), [options](#options-1))
|
||||
|
||||
Returns a new `TLSSocket`. It defaults to `Agent.connect(origin, options)`.
|
||||
|
||||
#### agent.closeEmptySessions(count)
|
||||
|
||||
##### count
|
||||
|
||||
Type: `number`
|
||||
Default: `Number.POSITIVE_INFINITY`
|
||||
|
||||
Makes an attempt to close empty sessions. Only sessions with 0 concurrent streams will be closed.
|
||||
|
||||
#### agent.destroy(reason)
|
||||
|
||||
Destroys **all** sessions.
|
||||
|
||||
#### agent.emptySessionCount
|
||||
|
||||
Type: `number`
|
||||
|
||||
A number of empty sessions.
|
||||
|
||||
#### agent.pendingSessionCount
|
||||
|
||||
Type: `number`
|
||||
|
||||
A number of pending sessions.
|
||||
|
||||
#### agent.sessionCount
|
||||
|
||||
Type: `number`
|
||||
|
||||
A number of all sessions held by the Agent.
|
||||
|
||||
#### Event: 'session'
|
||||
|
||||
```js
|
||||
agent.on('session', session => {
|
||||
// A new session has been created by the Agent.
|
||||
});
|
||||
```
|
||||
|
||||
## Proxy support
|
||||
|
||||
Currently `http2-wrapper` provides support for these proxies:
|
||||
|
||||
- `HttpOverHttp2`
|
||||
- `HttpsOverHttp2`
|
||||
- `Http2OverHttp2`
|
||||
- `Http2OverHttp`
|
||||
- `Http2OverHttps`
|
||||
|
||||
Any of the above can be accessed via `http2wrapper.proxies`. Check out the [`examples/proxies`](examples/proxies) directory to learn more.
|
||||
|
||||
**Note:** If you use the `http2.auto` function, the real IP address will leak. `http2wrapper` is not aware of the context. It will create a connection to the end server using your real IP address to get the ALPN protocol. Then it will create another connection using proxy. To migitate this, you need to pass a custom `resolveProtocol` function as an option:
|
||||
|
||||
```js
|
||||
const resolveAlpnProxy = new URL('https://username:password@localhost:8000');
|
||||
const connect = async (options, callback) => new Promise((resolve, reject) => {
|
||||
const host = `${options.host}:${options.port}`;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const request = await http2.auto(resolveAlpnProxy, {
|
||||
method: 'CONNECT',
|
||||
headers: {
|
||||
host
|
||||
},
|
||||
path: host,
|
||||
|
||||
// For demo purposes only!
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
request.end();
|
||||
|
||||
request.once('error', reject);
|
||||
|
||||
request.once('connect', (response, socket, head) => {
|
||||
if (head.length > 0) {
|
||||
reject(new Error(`Unexpected data before CONNECT tunnel: ${head.length} bytes`));
|
||||
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const tlsSocket = tls.connect({
|
||||
...options,
|
||||
socket
|
||||
}, callback);
|
||||
|
||||
resolve(tlsSocket);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
// This is required to prevent leaking real IP address on ALPN negotiation
|
||||
const resolveProtocol = http2.auto.createResolveProtocol(new Map(), new Map(), connect);
|
||||
|
||||
const request = await http2.auto('https://httpbin.org/anything', {
|
||||
agent: {…},
|
||||
resolveProtocol
|
||||
}, response => {
|
||||
// Read the response here
|
||||
});
|
||||
|
||||
request.end();
|
||||
```
|
||||
|
||||
See [`unknown-over-unknown.js`](examples/proxies/unknown-over-unknown.js) to learn more.
|
||||
|
||||
## Mirroring another server
|
||||
|
||||
See [`examples/proxies/mirror.js`](examples/proxies/mirror.js) for an example.
|
||||
|
||||
## [WebSockets over HTTP/2](https://tools.ietf.org/html/rfc8441)
|
||||
|
||||
See [`examples/ws`](examples/ws) for an example.
|
||||
|
||||
## Push streams
|
||||
|
||||
See [`examples/push-stream`](examples/push-stream) for an example.
|
||||
|
||||
## Related
|
||||
|
||||
- [`got`](https://github.com/sindresorhus/got) - Simplified HTTP requests
|
||||
- [`http2-proxy`](https://github.com/nxtedition/node-http2-proxy) - A simple http/2 & http/1.1 spec compliant proxy helper for Node.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
141
book/node_modules/http2-wrapper/index.d.ts
generated
vendored
Normal file
141
book/node_modules/http2-wrapper/index.d.ts
generated
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
// See https://github.com/facebook/jest/issues/2549
|
||||
// eslint-disable-next-line node/prefer-global/url
|
||||
import {URL} from 'url';
|
||||
import {EventEmitter} from 'events';
|
||||
import tls = require('tls');
|
||||
import http = require('http');
|
||||
import https = require('https');
|
||||
import http2 = require('http2');
|
||||
|
||||
// Note: do not convert this to import from.
|
||||
import QuickLRU = require('quick-lru');
|
||||
|
||||
export interface RequestOptions extends Omit<https.RequestOptions, 'session' | 'agent'> {
|
||||
tlsSession?: tls.ConnectionOptions['session'];
|
||||
h2session?: http2.ClientHttp2Session;
|
||||
agent?: Agent | false;
|
||||
|
||||
// Required because @types/node is missing types
|
||||
ALPNProtocols?: string[];
|
||||
}
|
||||
|
||||
export interface AutoRequestOptions extends Omit<RequestOptions, 'agent' | 'h2session'> {
|
||||
agent?: {
|
||||
http?: http.Agent | false;
|
||||
https?: https.Agent | false;
|
||||
http2?: Agent | false;
|
||||
};
|
||||
resolveProtocol?: ResolveProtocolFunction;
|
||||
}
|
||||
|
||||
export interface EntryFunction {
|
||||
(): Promise<void>;
|
||||
|
||||
completed: boolean;
|
||||
destroyed: boolean;
|
||||
listeners: PromiseListeners;
|
||||
}
|
||||
|
||||
export interface AgentOptions {
|
||||
timeout?: number;
|
||||
maxSessions?: number;
|
||||
maxEmptySessions?: number;
|
||||
maxCachedTlsSessions?: number;
|
||||
}
|
||||
|
||||
export interface PromiseListeners {
|
||||
resolve: (value: unknown) => void;
|
||||
reject: (error: Error) => void;
|
||||
}
|
||||
|
||||
export class Agent extends EventEmitter {
|
||||
sessions: Record<string, http2.ClientHttp2Session[]>;
|
||||
queue: Record<string, Record<string, EntryFunction>>;
|
||||
|
||||
timeout: number;
|
||||
maxSessions: number;
|
||||
maxEmptySessions: number;
|
||||
protocol: string;
|
||||
settings: http2.Settings;
|
||||
|
||||
tlsSessionCache: QuickLRU<string, string>;
|
||||
|
||||
emptySessionCount: number;
|
||||
pendingSessionCount: number;
|
||||
sessionCount: number;
|
||||
|
||||
constructor(options?: AgentOptions);
|
||||
|
||||
static connect(origin: URL, options: http2.SecureClientSessionOptions): tls.TLSSocket;
|
||||
|
||||
normalizeOptions(options: http2.ClientSessionRequestOptions): string;
|
||||
|
||||
getSession(origin: string | URL, options?: http2.SecureClientSessionOptions, listeners?: PromiseListeners): Promise<http2.ClientHttp2Session>;
|
||||
request(origin: string | URL, options?: http2.SecureClientSessionOptions, headers?: http2.OutgoingHttpHeaders, streamOptions?: http2.ClientSessionRequestOptions): Promise<http2.ClientHttp2Stream>;
|
||||
|
||||
createConnection(origin: URL, options: http2.SecureClientSessionOptions): Promise<tls.TLSSocket>;
|
||||
|
||||
closeEmptySessions(count?: number): void;
|
||||
destroy(reason?: Error): void;
|
||||
}
|
||||
|
||||
export interface ProxyOptions {
|
||||
headers?: http2.OutgoingHttpHeaders;
|
||||
raw?: boolean;
|
||||
url: URL | string;
|
||||
}
|
||||
|
||||
export namespace proxies {
|
||||
class HttpOverHttp2 extends http.Agent {
|
||||
constructor(options: http.AgentOptions & {proxyOptions: ProxyOptions});
|
||||
}
|
||||
|
||||
class HttpsOverHttp2 extends https.Agent {
|
||||
constructor(options: https.AgentOptions & {proxyOptions: ProxyOptions});
|
||||
}
|
||||
|
||||
class Http2OverHttp2 extends Agent {
|
||||
constructor(options: AgentOptions & {proxyOptions: ProxyOptions});
|
||||
}
|
||||
|
||||
class Http2OverHttp extends Agent {
|
||||
constructor(options: AgentOptions & {proxyOptions: ProxyOptions});
|
||||
}
|
||||
|
||||
class Http2OverHttps extends Agent {
|
||||
constructor(options: AgentOptions & {proxyOptions: ProxyOptions});
|
||||
}
|
||||
}
|
||||
|
||||
export type RequestFunction<T, O = RequestOptions> =
|
||||
((url: string | URL, options?: O, callback?: (response: http.IncomingMessage) => void) => T) &
|
||||
((url: string | URL, callback?: (response: http.IncomingMessage) => void) => T) &
|
||||
((options: O, callback?: (response: http.IncomingMessage) => void) => T);
|
||||
|
||||
export const globalAgent: Agent;
|
||||
|
||||
export type ResolveProtocolResult = {
|
||||
alpnProtocol: string;
|
||||
socket?: tls.TLSSocket;
|
||||
timeout?: boolean;
|
||||
};
|
||||
export type ResolveProtocolFunction = (options: AutoRequestOptions) => Promise<ResolveProtocolResult>;
|
||||
|
||||
type Promisable<T> = T | Promise<T>;
|
||||
|
||||
export type ResolveProtocolConnectFunction = (options: tls.ConnectionOptions, callback: () => void) => Promisable<tls.TLSSocket>;
|
||||
|
||||
export const request: RequestFunction<http.ClientRequest>;
|
||||
export const get: RequestFunction<http.ClientRequest>;
|
||||
export const auto: RequestFunction<Promise<http.ClientRequest>, AutoRequestOptions> & {
|
||||
protocolCache: QuickLRU<string, string>;
|
||||
resolveProtocol: ResolveProtocolFunction;
|
||||
createResolveProtocol: (cache: Map<string, string>, queue: Map<string, Promise<ResolveProtocolResult>>, connect?: ResolveProtocolConnectFunction) => ResolveProtocolFunction;
|
||||
};
|
||||
|
||||
export {
|
||||
ClientRequest,
|
||||
IncomingMessage
|
||||
} from 'http';
|
||||
|
||||
export * from 'http2';
|
81
book/node_modules/http2-wrapper/package.json
generated
vendored
Normal file
81
book/node_modules/http2-wrapper/package.json
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
{
|
||||
"name": "http2-wrapper",
|
||||
"version": "2.2.1",
|
||||
"description": "HTTP2 client, just with the familiar `https` API",
|
||||
"main": "source",
|
||||
"types": "index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=10.19.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "xo && nyc --reporter=lcovonly --reporter=text --reporter=html ava && tsd"
|
||||
},
|
||||
"files": [
|
||||
"source",
|
||||
"index.d.ts"
|
||||
],
|
||||
"keywords": [
|
||||
"http2",
|
||||
"https",
|
||||
"http",
|
||||
"request"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/szmarczak/http2-wrapper.git"
|
||||
},
|
||||
"author": "Szymon Marczak",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/szmarczak/http2-wrapper/issues"
|
||||
},
|
||||
"homepage": "https://github.com/szmarczak/http2-wrapper#readme",
|
||||
"dependencies": {
|
||||
"quick-lru": "^5.1.1",
|
||||
"resolve-alpn": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sindresorhus/is": "^4.0.1",
|
||||
"ava": "^3.15.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"get-stream": "^6.0.1",
|
||||
"got": "^11.8.2",
|
||||
"http2-proxy": "^5.0.53",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"lolex": "^6.0.0",
|
||||
"many-keys-map": "^1.0.3",
|
||||
"nyc": "^15.1.0",
|
||||
"p-event": "^4.2.0",
|
||||
"tempy": "^1.0.1",
|
||||
"to-readable-stream": "^2.1.0",
|
||||
"tsd": "^0.17.0",
|
||||
"websocket-stream": "^5.5.2",
|
||||
"ws": "^7.5.3",
|
||||
"xo": "0.39.1"
|
||||
},
|
||||
"ava": {
|
||||
"timeout": "10s"
|
||||
},
|
||||
"nyc": {
|
||||
"include": [
|
||||
"source"
|
||||
]
|
||||
},
|
||||
"xo": {
|
||||
"rules": {
|
||||
"unicorn/no-for-loop": "off",
|
||||
"unicorn/prefer-module": "off",
|
||||
"comma-dangle": "off",
|
||||
"@typescript-eslint/comma-dangle": "off",
|
||||
"quotes": [
|
||||
"error",
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
],
|
||||
"operator-linebreak": ["error", "before"]
|
||||
}
|
||||
}
|
||||
}
|
796
book/node_modules/http2-wrapper/source/agent.js
generated
vendored
Normal file
796
book/node_modules/http2-wrapper/source/agent.js
generated
vendored
Normal file
@ -0,0 +1,796 @@
|
||||
'use strict';
|
||||
// See https://github.com/facebook/jest/issues/2549
|
||||
// eslint-disable-next-line node/prefer-global/url
|
||||
const {URL} = require('url');
|
||||
const EventEmitter = require('events');
|
||||
const tls = require('tls');
|
||||
const http2 = require('http2');
|
||||
const QuickLRU = require('quick-lru');
|
||||
const delayAsyncDestroy = require('./utils/delay-async-destroy.js');
|
||||
|
||||
const kCurrentStreamCount = Symbol('currentStreamCount');
|
||||
const kRequest = Symbol('request');
|
||||
const kOriginSet = Symbol('cachedOriginSet');
|
||||
const kGracefullyClosing = Symbol('gracefullyClosing');
|
||||
const kLength = Symbol('length');
|
||||
|
||||
const nameKeys = [
|
||||
// Not an Agent option actually
|
||||
'createConnection',
|
||||
|
||||
// `http2.connect()` options
|
||||
'maxDeflateDynamicTableSize',
|
||||
'maxSettings',
|
||||
'maxSessionMemory',
|
||||
'maxHeaderListPairs',
|
||||
'maxOutstandingPings',
|
||||
'maxReservedRemoteStreams',
|
||||
'maxSendHeaderBlockLength',
|
||||
'paddingStrategy',
|
||||
'peerMaxConcurrentStreams',
|
||||
'settings',
|
||||
|
||||
// `tls.connect()` source options
|
||||
'family',
|
||||
'localAddress',
|
||||
'rejectUnauthorized',
|
||||
|
||||
// `tls.connect()` secure context options
|
||||
'pskCallback',
|
||||
'minDHSize',
|
||||
|
||||
// `tls.connect()` destination options
|
||||
// - `servername` is automatically validated, skip it
|
||||
// - `host` and `port` just describe the destination server,
|
||||
'path',
|
||||
'socket',
|
||||
|
||||
// `tls.createSecureContext()` options
|
||||
'ca',
|
||||
'cert',
|
||||
'sigalgs',
|
||||
'ciphers',
|
||||
'clientCertEngine',
|
||||
'crl',
|
||||
'dhparam',
|
||||
'ecdhCurve',
|
||||
'honorCipherOrder',
|
||||
'key',
|
||||
'privateKeyEngine',
|
||||
'privateKeyIdentifier',
|
||||
'maxVersion',
|
||||
'minVersion',
|
||||
'pfx',
|
||||
'secureOptions',
|
||||
'secureProtocol',
|
||||
'sessionIdContext',
|
||||
'ticketKeys'
|
||||
];
|
||||
|
||||
const getSortedIndex = (array, value, compare) => {
|
||||
let low = 0;
|
||||
let high = array.length;
|
||||
|
||||
while (low < high) {
|
||||
const mid = (low + high) >>> 1;
|
||||
|
||||
if (compare(array[mid], value)) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
|
||||
return low;
|
||||
};
|
||||
|
||||
const compareSessions = (a, b) => a.remoteSettings.maxConcurrentStreams > b.remoteSettings.maxConcurrentStreams;
|
||||
|
||||
// See https://tools.ietf.org/html/rfc8336
|
||||
const closeCoveredSessions = (where, session) => {
|
||||
// Clients SHOULD NOT emit new requests on any connection whose Origin
|
||||
// Set is a proper subset of another connection's Origin Set, and they
|
||||
// SHOULD close it once all outstanding requests are satisfied.
|
||||
for (let index = 0; index < where.length; index++) {
|
||||
const coveredSession = where[index];
|
||||
|
||||
if (
|
||||
// Unfortunately `.every()` returns true for an empty array
|
||||
coveredSession[kOriginSet].length > 0
|
||||
|
||||
// The set is a proper subset when its length is less than the other set.
|
||||
&& coveredSession[kOriginSet].length < session[kOriginSet].length
|
||||
|
||||
// And the other set includes all elements of the subset.
|
||||
&& coveredSession[kOriginSet].every(origin => session[kOriginSet].includes(origin))
|
||||
|
||||
// Makes sure that the session can handle all requests from the covered session.
|
||||
&& (coveredSession[kCurrentStreamCount] + session[kCurrentStreamCount]) <= session.remoteSettings.maxConcurrentStreams
|
||||
) {
|
||||
// This allows pending requests to finish and prevents making new requests.
|
||||
gracefullyClose(coveredSession);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This is basically inverted `closeCoveredSessions(...)`.
|
||||
const closeSessionIfCovered = (where, coveredSession) => {
|
||||
for (let index = 0; index < where.length; index++) {
|
||||
const session = where[index];
|
||||
|
||||
if (
|
||||
coveredSession[kOriginSet].length > 0
|
||||
&& coveredSession[kOriginSet].length < session[kOriginSet].length
|
||||
&& coveredSession[kOriginSet].every(origin => session[kOriginSet].includes(origin))
|
||||
&& (coveredSession[kCurrentStreamCount] + session[kCurrentStreamCount]) <= session.remoteSettings.maxConcurrentStreams
|
||||
) {
|
||||
gracefullyClose(coveredSession);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const gracefullyClose = session => {
|
||||
session[kGracefullyClosing] = true;
|
||||
|
||||
if (session[kCurrentStreamCount] === 0) {
|
||||
session.close();
|
||||
}
|
||||
};
|
||||
|
||||
class Agent extends EventEmitter {
|
||||
constructor({timeout = 0, maxSessions = Number.POSITIVE_INFINITY, maxEmptySessions = 10, maxCachedTlsSessions = 100} = {}) {
|
||||
super();
|
||||
|
||||
// SESSIONS[NORMALIZED_OPTIONS] = [];
|
||||
this.sessions = {};
|
||||
|
||||
// The queue for creating new sessions. It looks like this:
|
||||
// QUEUE[NORMALIZED_OPTIONS][NORMALIZED_ORIGIN] = ENTRY_FUNCTION
|
||||
//
|
||||
// It's faster when there are many origins. If there's only one, then QUEUE[`${options}:${origin}`] is faster.
|
||||
// I guess object creation / deletion is causing the slowdown.
|
||||
//
|
||||
// The entry function has `listeners`, `completed` and `destroyed` properties.
|
||||
// `listeners` is an array of objects containing `resolve` and `reject` functions.
|
||||
// `completed` is a boolean. It's set to true after ENTRY_FUNCTION is executed.
|
||||
// `destroyed` is a boolean. If it's set to true, the session will be destroyed if hasn't connected yet.
|
||||
this.queue = {};
|
||||
|
||||
// Each session will use this timeout value.
|
||||
this.timeout = timeout;
|
||||
|
||||
// Max sessions in total
|
||||
this.maxSessions = maxSessions;
|
||||
|
||||
// Max empty sessions in total
|
||||
this.maxEmptySessions = maxEmptySessions;
|
||||
|
||||
this._emptySessionCount = 0;
|
||||
this._sessionCount = 0;
|
||||
|
||||
// We don't support push streams by default.
|
||||
this.settings = {
|
||||
enablePush: false,
|
||||
initialWindowSize: 1024 * 1024 * 32 // 32MB, see https://github.com/nodejs/node/issues/38426
|
||||
};
|
||||
|
||||
// Reusing TLS sessions increases performance.
|
||||
this.tlsSessionCache = new QuickLRU({maxSize: maxCachedTlsSessions});
|
||||
}
|
||||
|
||||
get protocol() {
|
||||
return 'https:';
|
||||
}
|
||||
|
||||
normalizeOptions(options) {
|
||||
let normalized = '';
|
||||
|
||||
for (let index = 0; index < nameKeys.length; index++) {
|
||||
const key = nameKeys[index];
|
||||
|
||||
normalized += ':';
|
||||
|
||||
if (options && options[key] !== undefined) {
|
||||
normalized += options[key];
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
_processQueue() {
|
||||
if (this._sessionCount >= this.maxSessions) {
|
||||
this.closeEmptySessions(this.maxSessions - this._sessionCount + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const normalizedOptions in this.queue) {
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const normalizedOrigin in this.queue[normalizedOptions]) {
|
||||
const item = this.queue[normalizedOptions][normalizedOrigin];
|
||||
|
||||
// The entry function can be run only once.
|
||||
if (!item.completed) {
|
||||
item.completed = true;
|
||||
|
||||
item();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_isBetterSession(thisStreamCount, thatStreamCount) {
|
||||
return thisStreamCount > thatStreamCount;
|
||||
}
|
||||
|
||||
_accept(session, listeners, normalizedOrigin, options) {
|
||||
let index = 0;
|
||||
|
||||
while (index < listeners.length && session[kCurrentStreamCount] < session.remoteSettings.maxConcurrentStreams) {
|
||||
// We assume `resolve(...)` calls `request(...)` *directly*,
|
||||
// otherwise the session will get overloaded.
|
||||
listeners[index].resolve(session);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
listeners.splice(0, index);
|
||||
|
||||
if (listeners.length > 0) {
|
||||
this.getSession(normalizedOrigin, options, listeners);
|
||||
listeners.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
getSession(origin, options, listeners) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (Array.isArray(listeners) && listeners.length > 0) {
|
||||
listeners = [...listeners];
|
||||
|
||||
// Resolve the current promise ASAP, we're just moving the listeners.
|
||||
// They will be executed at a different time.
|
||||
resolve();
|
||||
} else {
|
||||
listeners = [{resolve, reject}];
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse origin
|
||||
if (typeof origin === 'string') {
|
||||
origin = new URL(origin);
|
||||
} else if (!(origin instanceof URL)) {
|
||||
throw new TypeError('The `origin` argument needs to be a string or an URL object');
|
||||
}
|
||||
|
||||
if (options) {
|
||||
// Validate servername
|
||||
const {servername} = options;
|
||||
const {hostname} = origin;
|
||||
if (servername && hostname !== servername) {
|
||||
throw new Error(`Origin ${hostname} differs from servername ${servername}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
for (let index = 0; index < listeners.length; index++) {
|
||||
listeners[index].reject(error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedOptions = this.normalizeOptions(options);
|
||||
const normalizedOrigin = origin.origin;
|
||||
|
||||
if (normalizedOptions in this.sessions) {
|
||||
const sessions = this.sessions[normalizedOptions];
|
||||
|
||||
let maxConcurrentStreams = -1;
|
||||
let currentStreamsCount = -1;
|
||||
let optimalSession;
|
||||
|
||||
// We could just do this.sessions[normalizedOptions].find(...) but that isn't optimal.
|
||||
// Additionally, we are looking for session which has biggest current pending streams count.
|
||||
//
|
||||
// |------------| |------------| |------------| |------------|
|
||||
// | Session: A | | Session: B | | Session: C | | Session: D |
|
||||
// | Pending: 5 |-| Pending: 8 |-| Pending: 9 |-| Pending: 4 |
|
||||
// | Max: 10 | | Max: 10 | | Max: 9 | | Max: 5 |
|
||||
// |------------| |------------| |------------| |------------|
|
||||
// ^
|
||||
// |
|
||||
// pick this one --
|
||||
//
|
||||
for (let index = 0; index < sessions.length; index++) {
|
||||
const session = sessions[index];
|
||||
|
||||
const sessionMaxConcurrentStreams = session.remoteSettings.maxConcurrentStreams;
|
||||
|
||||
if (sessionMaxConcurrentStreams < maxConcurrentStreams) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!session[kOriginSet].includes(normalizedOrigin)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sessionCurrentStreamsCount = session[kCurrentStreamCount];
|
||||
|
||||
if (
|
||||
sessionCurrentStreamsCount >= sessionMaxConcurrentStreams
|
||||
|| session[kGracefullyClosing]
|
||||
// Unfortunately the `close` event isn't called immediately,
|
||||
// so `session.destroyed` is `true`, but `session.closed` is `false`.
|
||||
|| session.destroyed
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We only need set this once.
|
||||
if (!optimalSession) {
|
||||
maxConcurrentStreams = sessionMaxConcurrentStreams;
|
||||
}
|
||||
|
||||
// Either get the session which has biggest current stream count or the lowest.
|
||||
if (this._isBetterSession(sessionCurrentStreamsCount, currentStreamsCount)) {
|
||||
optimalSession = session;
|
||||
currentStreamsCount = sessionCurrentStreamsCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (optimalSession) {
|
||||
this._accept(optimalSession, listeners, normalizedOrigin, options);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (normalizedOptions in this.queue) {
|
||||
if (normalizedOrigin in this.queue[normalizedOptions]) {
|
||||
// There's already an item in the queue, just attach ourselves to it.
|
||||
this.queue[normalizedOptions][normalizedOrigin].listeners.push(...listeners);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.queue[normalizedOptions] = {
|
||||
[kLength]: 0
|
||||
};
|
||||
}
|
||||
|
||||
// The entry must be removed from the queue IMMEDIATELY when:
|
||||
// 1. the session connects successfully,
|
||||
// 2. an error occurs.
|
||||
const removeFromQueue = () => {
|
||||
// Our entry can be replaced. We cannot remove the new one.
|
||||
if (normalizedOptions in this.queue && this.queue[normalizedOptions][normalizedOrigin] === entry) {
|
||||
delete this.queue[normalizedOptions][normalizedOrigin];
|
||||
|
||||
if (--this.queue[normalizedOptions][kLength] === 0) {
|
||||
delete this.queue[normalizedOptions];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The main logic is here
|
||||
const entry = async () => {
|
||||
this._sessionCount++;
|
||||
|
||||
const name = `${normalizedOrigin}:${normalizedOptions}`;
|
||||
let receivedSettings = false;
|
||||
let socket;
|
||||
|
||||
try {
|
||||
const computedOptions = {...options};
|
||||
|
||||
if (computedOptions.settings === undefined) {
|
||||
computedOptions.settings = this.settings;
|
||||
}
|
||||
|
||||
if (computedOptions.session === undefined) {
|
||||
computedOptions.session = this.tlsSessionCache.get(name);
|
||||
}
|
||||
|
||||
const createConnection = computedOptions.createConnection || this.createConnection;
|
||||
|
||||
// A hacky workaround to enable async `createConnection`
|
||||
socket = await createConnection.call(this, origin, computedOptions);
|
||||
computedOptions.createConnection = () => socket;
|
||||
|
||||
const session = http2.connect(origin, computedOptions);
|
||||
session[kCurrentStreamCount] = 0;
|
||||
session[kGracefullyClosing] = false;
|
||||
|
||||
// Node.js return https://false:443 instead of https://1.1.1.1:443
|
||||
const getOriginSet = () => {
|
||||
const {socket} = session;
|
||||
|
||||
let originSet;
|
||||
if (socket.servername === false) {
|
||||
socket.servername = socket.remoteAddress;
|
||||
originSet = session.originSet;
|
||||
socket.servername = false;
|
||||
} else {
|
||||
originSet = session.originSet;
|
||||
}
|
||||
|
||||
return originSet;
|
||||
};
|
||||
|
||||
const isFree = () => session[kCurrentStreamCount] < session.remoteSettings.maxConcurrentStreams;
|
||||
|
||||
session.socket.once('session', tlsSession => {
|
||||
this.tlsSessionCache.set(name, tlsSession);
|
||||
});
|
||||
|
||||
session.once('error', error => {
|
||||
// Listeners are empty when the session successfully connected.
|
||||
for (let index = 0; index < listeners.length; index++) {
|
||||
listeners[index].reject(error);
|
||||
}
|
||||
|
||||
// The connection got broken, purge the cache.
|
||||
this.tlsSessionCache.delete(name);
|
||||
});
|
||||
|
||||
session.setTimeout(this.timeout, () => {
|
||||
// Terminates all streams owned by this session.
|
||||
session.destroy();
|
||||
});
|
||||
|
||||
session.once('close', () => {
|
||||
this._sessionCount--;
|
||||
|
||||
if (receivedSettings) {
|
||||
// Assumes session `close` is emitted after request `close`
|
||||
this._emptySessionCount--;
|
||||
|
||||
// This cannot be moved to the stream logic,
|
||||
// because there may be a session that hadn't made a single request.
|
||||
const where = this.sessions[normalizedOptions];
|
||||
|
||||
if (where.length === 1) {
|
||||
delete this.sessions[normalizedOptions];
|
||||
} else {
|
||||
where.splice(where.indexOf(session), 1);
|
||||
}
|
||||
} else {
|
||||
// Broken connection
|
||||
removeFromQueue();
|
||||
|
||||
const error = new Error('Session closed without receiving a SETTINGS frame');
|
||||
error.code = 'HTTP2WRAPPER_NOSETTINGS';
|
||||
|
||||
for (let index = 0; index < listeners.length; index++) {
|
||||
listeners[index].reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
// There may be another session awaiting.
|
||||
this._processQueue();
|
||||
});
|
||||
|
||||
// Iterates over the queue and processes listeners.
|
||||
const processListeners = () => {
|
||||
const queue = this.queue[normalizedOptions];
|
||||
if (!queue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const originSet = session[kOriginSet];
|
||||
|
||||
for (let index = 0; index < originSet.length; index++) {
|
||||
const origin = originSet[index];
|
||||
|
||||
if (origin in queue) {
|
||||
const {listeners, completed} = queue[origin];
|
||||
|
||||
let index = 0;
|
||||
|
||||
// Prevents session overloading.
|
||||
while (index < listeners.length && isFree()) {
|
||||
// We assume `resolve(...)` calls `request(...)` *directly*,
|
||||
// otherwise the session will get overloaded.
|
||||
listeners[index].resolve(session);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
queue[origin].listeners.splice(0, index);
|
||||
|
||||
if (queue[origin].listeners.length === 0 && !completed) {
|
||||
delete queue[origin];
|
||||
|
||||
if (--queue[kLength] === 0) {
|
||||
delete this.queue[normalizedOptions];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We're no longer free, no point in continuing.
|
||||
if (!isFree()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The Origin Set cannot shrink. No need to check if it suddenly became covered by another one.
|
||||
session.on('origin', () => {
|
||||
session[kOriginSet] = getOriginSet() || [];
|
||||
session[kGracefullyClosing] = false;
|
||||
closeSessionIfCovered(this.sessions[normalizedOptions], session);
|
||||
|
||||
if (session[kGracefullyClosing] || !isFree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
processListeners();
|
||||
|
||||
if (!isFree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close covered sessions (if possible).
|
||||
closeCoveredSessions(this.sessions[normalizedOptions], session);
|
||||
});
|
||||
|
||||
session.once('remoteSettings', () => {
|
||||
// The Agent could have been destroyed already.
|
||||
if (entry.destroyed) {
|
||||
const error = new Error('Agent has been destroyed');
|
||||
|
||||
for (let index = 0; index < listeners.length; index++) {
|
||||
listeners[index].reject(error);
|
||||
}
|
||||
|
||||
session.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// See https://github.com/nodejs/node/issues/38426
|
||||
if (session.setLocalWindowSize) {
|
||||
session.setLocalWindowSize(1024 * 1024 * 4); // 4 MB
|
||||
}
|
||||
|
||||
session[kOriginSet] = getOriginSet() || [];
|
||||
|
||||
if (session.socket.encrypted) {
|
||||
const mainOrigin = session[kOriginSet][0];
|
||||
if (mainOrigin !== normalizedOrigin) {
|
||||
const error = new Error(`Requested origin ${normalizedOrigin} does not match server ${mainOrigin}`);
|
||||
|
||||
for (let index = 0; index < listeners.length; index++) {
|
||||
listeners[index].reject(error);
|
||||
}
|
||||
|
||||
session.destroy();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
removeFromQueue();
|
||||
|
||||
{
|
||||
const where = this.sessions;
|
||||
|
||||
if (normalizedOptions in where) {
|
||||
const sessions = where[normalizedOptions];
|
||||
sessions.splice(getSortedIndex(sessions, session, compareSessions), 0, session);
|
||||
} else {
|
||||
where[normalizedOptions] = [session];
|
||||
}
|
||||
}
|
||||
|
||||
receivedSettings = true;
|
||||
this._emptySessionCount++;
|
||||
|
||||
this.emit('session', session);
|
||||
this._accept(session, listeners, normalizedOrigin, options);
|
||||
|
||||
if (session[kCurrentStreamCount] === 0 && this._emptySessionCount > this.maxEmptySessions) {
|
||||
this.closeEmptySessions(this._emptySessionCount - this.maxEmptySessions);
|
||||
}
|
||||
|
||||
// `session.remoteSettings.maxConcurrentStreams` might get increased
|
||||
session.on('remoteSettings', () => {
|
||||
if (!isFree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
processListeners();
|
||||
|
||||
if (!isFree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In case the Origin Set changes
|
||||
closeCoveredSessions(this.sessions[normalizedOptions], session);
|
||||
});
|
||||
});
|
||||
|
||||
// Shim `session.request()` in order to catch all streams
|
||||
session[kRequest] = session.request;
|
||||
session.request = (headers, streamOptions) => {
|
||||
if (session[kGracefullyClosing]) {
|
||||
throw new Error('The session is gracefully closing. No new streams are allowed.');
|
||||
}
|
||||
|
||||
const stream = session[kRequest](headers, streamOptions);
|
||||
|
||||
// The process won't exit until the session is closed or all requests are gone.
|
||||
session.ref();
|
||||
|
||||
if (session[kCurrentStreamCount]++ === 0) {
|
||||
this._emptySessionCount--;
|
||||
}
|
||||
|
||||
stream.once('close', () => {
|
||||
if (--session[kCurrentStreamCount] === 0) {
|
||||
this._emptySessionCount++;
|
||||
session.unref();
|
||||
|
||||
if (this._emptySessionCount > this.maxEmptySessions || session[kGracefullyClosing]) {
|
||||
session.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (session.destroyed || session.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFree() && !closeSessionIfCovered(this.sessions[normalizedOptions], session)) {
|
||||
closeCoveredSessions(this.sessions[normalizedOptions], session);
|
||||
processListeners();
|
||||
|
||||
if (session[kCurrentStreamCount] === 0) {
|
||||
this._processQueue();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return stream;
|
||||
};
|
||||
} catch (error) {
|
||||
removeFromQueue();
|
||||
this._sessionCount--;
|
||||
|
||||
for (let index = 0; index < listeners.length; index++) {
|
||||
listeners[index].reject(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
entry.listeners = listeners;
|
||||
entry.completed = false;
|
||||
entry.destroyed = false;
|
||||
|
||||
this.queue[normalizedOptions][normalizedOrigin] = entry;
|
||||
this.queue[normalizedOptions][kLength]++;
|
||||
this._processQueue();
|
||||
});
|
||||
}
|
||||
|
||||
request(origin, options, headers, streamOptions) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.getSession(origin, options, [{
|
||||
reject,
|
||||
resolve: session => {
|
||||
try {
|
||||
const stream = session.request(headers, streamOptions);
|
||||
|
||||
// Do not throw before `request(...)` has been awaited
|
||||
delayAsyncDestroy(stream);
|
||||
|
||||
resolve(stream);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
}]);
|
||||
});
|
||||
}
|
||||
|
||||
async createConnection(origin, options) {
|
||||
return Agent.connect(origin, options);
|
||||
}
|
||||
|
||||
static connect(origin, options) {
|
||||
options.ALPNProtocols = ['h2'];
|
||||
|
||||
const port = origin.port || 443;
|
||||
const host = origin.hostname;
|
||||
|
||||
if (typeof options.servername === 'undefined') {
|
||||
options.servername = host;
|
||||
}
|
||||
|
||||
const socket = tls.connect(port, host, options);
|
||||
|
||||
if (options.socket) {
|
||||
socket._peername = {
|
||||
family: undefined,
|
||||
address: undefined,
|
||||
port
|
||||
};
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
closeEmptySessions(maxCount = Number.POSITIVE_INFINITY) {
|
||||
let closedCount = 0;
|
||||
|
||||
const {sessions} = this;
|
||||
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const key in sessions) {
|
||||
const thisSessions = sessions[key];
|
||||
|
||||
for (let index = 0; index < thisSessions.length; index++) {
|
||||
const session = thisSessions[index];
|
||||
|
||||
if (session[kCurrentStreamCount] === 0) {
|
||||
closedCount++;
|
||||
session.close();
|
||||
|
||||
if (closedCount >= maxCount) {
|
||||
return closedCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closedCount;
|
||||
}
|
||||
|
||||
destroy(reason) {
|
||||
const {sessions, queue} = this;
|
||||
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const key in sessions) {
|
||||
const thisSessions = sessions[key];
|
||||
|
||||
for (let index = 0; index < thisSessions.length; index++) {
|
||||
thisSessions[index].destroy(reason);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const normalizedOptions in queue) {
|
||||
const entries = queue[normalizedOptions];
|
||||
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const normalizedOrigin in entries) {
|
||||
entries[normalizedOrigin].destroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// New requests should NOT attach to destroyed sessions
|
||||
this.queue = {};
|
||||
this.tlsSessionCache.clear();
|
||||
}
|
||||
|
||||
get emptySessionCount() {
|
||||
return this._emptySessionCount;
|
||||
}
|
||||
|
||||
get pendingSessionCount() {
|
||||
return this._sessionCount - this._emptySessionCount;
|
||||
}
|
||||
|
||||
get sessionCount() {
|
||||
return this._sessionCount;
|
||||
}
|
||||
}
|
||||
|
||||
Agent.kCurrentStreamCount = kCurrentStreamCount;
|
||||
Agent.kGracefullyClosing = kGracefullyClosing;
|
||||
|
||||
module.exports = {
|
||||
Agent,
|
||||
globalAgent: new Agent()
|
||||
};
|
225
book/node_modules/http2-wrapper/source/auto.js
generated
vendored
Normal file
225
book/node_modules/http2-wrapper/source/auto.js
generated
vendored
Normal file
@ -0,0 +1,225 @@
|
||||
'use strict';
|
||||
// See https://github.com/facebook/jest/issues/2549
|
||||
// eslint-disable-next-line node/prefer-global/url
|
||||
const {URL, urlToHttpOptions} = require('url');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const resolveALPN = require('resolve-alpn');
|
||||
const QuickLRU = require('quick-lru');
|
||||
const {Agent, globalAgent} = require('./agent.js');
|
||||
const Http2ClientRequest = require('./client-request.js');
|
||||
const calculateServerName = require('./utils/calculate-server-name.js');
|
||||
const delayAsyncDestroy = require('./utils/delay-async-destroy.js');
|
||||
|
||||
const cache = new QuickLRU({maxSize: 100});
|
||||
const queue = new Map();
|
||||
|
||||
const installSocket = (agent, socket, options) => {
|
||||
socket._httpMessage = {shouldKeepAlive: true};
|
||||
|
||||
const onFree = () => {
|
||||
agent.emit('free', socket, options);
|
||||
};
|
||||
|
||||
socket.on('free', onFree);
|
||||
|
||||
const onClose = () => {
|
||||
agent.removeSocket(socket, options);
|
||||
};
|
||||
|
||||
socket.on('close', onClose);
|
||||
|
||||
const onTimeout = () => {
|
||||
const {freeSockets} = agent;
|
||||
|
||||
for (const sockets of Object.values(freeSockets)) {
|
||||
if (sockets.includes(socket)) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
socket.on('timeout', onTimeout);
|
||||
|
||||
const onRemove = () => {
|
||||
agent.removeSocket(socket, options);
|
||||
socket.off('close', onClose);
|
||||
socket.off('free', onFree);
|
||||
socket.off('timeout', onTimeout);
|
||||
socket.off('agentRemove', onRemove);
|
||||
};
|
||||
|
||||
socket.on('agentRemove', onRemove);
|
||||
|
||||
agent.emit('free', socket, options);
|
||||
};
|
||||
|
||||
const createResolveProtocol = (cache, queue = new Map(), connect = undefined) => {
|
||||
return async options => {
|
||||
const name = `${options.host}:${options.port}:${options.ALPNProtocols.sort()}`;
|
||||
|
||||
if (!cache.has(name)) {
|
||||
if (queue.has(name)) {
|
||||
const result = await queue.get(name);
|
||||
return {alpnProtocol: result.alpnProtocol};
|
||||
}
|
||||
|
||||
const {path} = options;
|
||||
options.path = options.socketPath;
|
||||
|
||||
const resultPromise = resolveALPN(options, connect);
|
||||
queue.set(name, resultPromise);
|
||||
|
||||
try {
|
||||
const result = await resultPromise;
|
||||
|
||||
cache.set(name, result.alpnProtocol);
|
||||
queue.delete(name);
|
||||
|
||||
options.path = path;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
queue.delete(name);
|
||||
|
||||
options.path = path;
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return {alpnProtocol: cache.get(name)};
|
||||
};
|
||||
};
|
||||
|
||||
const defaultResolveProtocol = createResolveProtocol(cache, queue);
|
||||
|
||||
module.exports = async (input, options, callback) => {
|
||||
if (typeof input === 'string') {
|
||||
input = urlToHttpOptions(new URL(input));
|
||||
} else if (input instanceof URL) {
|
||||
input = urlToHttpOptions(input);
|
||||
} else {
|
||||
input = {...input};
|
||||
}
|
||||
|
||||
if (typeof options === 'function' || options === undefined) {
|
||||
// (options, callback)
|
||||
callback = options;
|
||||
options = input;
|
||||
} else {
|
||||
// (input, options, callback)
|
||||
options = Object.assign(input, options);
|
||||
}
|
||||
|
||||
options.ALPNProtocols = options.ALPNProtocols || ['h2', 'http/1.1'];
|
||||
|
||||
if (!Array.isArray(options.ALPNProtocols) || options.ALPNProtocols.length === 0) {
|
||||
throw new Error('The `ALPNProtocols` option must be an Array with at least one entry');
|
||||
}
|
||||
|
||||
options.protocol = options.protocol || 'https:';
|
||||
const isHttps = options.protocol === 'https:';
|
||||
|
||||
options.host = options.hostname || options.host || 'localhost';
|
||||
options.session = options.tlsSession;
|
||||
options.servername = options.servername || calculateServerName((options.headers && options.headers.host) || options.host);
|
||||
options.port = options.port || (isHttps ? 443 : 80);
|
||||
options._defaultAgent = isHttps ? https.globalAgent : http.globalAgent;
|
||||
|
||||
const resolveProtocol = options.resolveProtocol || defaultResolveProtocol;
|
||||
|
||||
// Note: We don't support `h2session` here
|
||||
|
||||
let {agent} = options;
|
||||
if (agent !== undefined && agent !== false && agent.constructor.name !== 'Object') {
|
||||
throw new Error('The `options.agent` can be only an object `http`, `https` or `http2` properties');
|
||||
}
|
||||
|
||||
if (isHttps) {
|
||||
options.resolveSocket = true;
|
||||
|
||||
let {socket, alpnProtocol, timeout} = await resolveProtocol(options);
|
||||
|
||||
if (timeout) {
|
||||
if (socket) {
|
||||
socket.destroy();
|
||||
}
|
||||
|
||||
const error = new Error(`Timed out resolving ALPN: ${options.timeout} ms`);
|
||||
error.code = 'ETIMEDOUT';
|
||||
error.ms = options.timeout;
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
// We can't accept custom `createConnection` because the API is different for HTTP/2
|
||||
if (socket && options.createConnection) {
|
||||
socket.destroy();
|
||||
socket = undefined;
|
||||
}
|
||||
|
||||
delete options.resolveSocket;
|
||||
|
||||
const isHttp2 = alpnProtocol === 'h2';
|
||||
|
||||
if (agent) {
|
||||
agent = isHttp2 ? agent.http2 : agent.https;
|
||||
options.agent = agent;
|
||||
}
|
||||
|
||||
if (agent === undefined) {
|
||||
agent = isHttp2 ? globalAgent : https.globalAgent;
|
||||
}
|
||||
|
||||
if (socket) {
|
||||
if (agent === false) {
|
||||
socket.destroy();
|
||||
} else {
|
||||
const defaultCreateConnection = (isHttp2 ? Agent : https.Agent).prototype.createConnection;
|
||||
|
||||
if (agent.createConnection === defaultCreateConnection) {
|
||||
if (isHttp2) {
|
||||
options._reuseSocket = socket;
|
||||
} else {
|
||||
installSocket(agent, socket, options);
|
||||
}
|
||||
} else {
|
||||
socket.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isHttp2) {
|
||||
return delayAsyncDestroy(new Http2ClientRequest(options, callback));
|
||||
}
|
||||
} else if (agent) {
|
||||
options.agent = agent.http;
|
||||
}
|
||||
|
||||
// If we're sending HTTP/1.1, handle any explicitly set H2 headers in the options:
|
||||
if (options.headers) {
|
||||
options.headers = {...options.headers};
|
||||
|
||||
// :authority is equivalent to the HTTP/1.1 host header
|
||||
if (options.headers[':authority']) {
|
||||
if (!options.headers.host) {
|
||||
options.headers.host = options.headers[':authority'];
|
||||
}
|
||||
|
||||
delete options.headers[':authority'];
|
||||
}
|
||||
|
||||
// Remove other HTTP/2 headers as they have their counterparts in the options
|
||||
delete options.headers[':method'];
|
||||
delete options.headers[':scheme'];
|
||||
delete options.headers[':path'];
|
||||
}
|
||||
|
||||
return delayAsyncDestroy(http.request(options, callback));
|
||||
};
|
||||
|
||||
module.exports.protocolCache = cache;
|
||||
module.exports.resolveProtocol = defaultResolveProtocol;
|
||||
module.exports.createResolveProtocol = createResolveProtocol;
|
563
book/node_modules/http2-wrapper/source/client-request.js
generated
vendored
Normal file
563
book/node_modules/http2-wrapper/source/client-request.js
generated
vendored
Normal file
@ -0,0 +1,563 @@
|
||||
'use strict';
|
||||
// See https://github.com/facebook/jest/issues/2549
|
||||
// eslint-disable-next-line node/prefer-global/url
|
||||
const {URL, urlToHttpOptions} = require('url');
|
||||
const http2 = require('http2');
|
||||
const {Writable} = require('stream');
|
||||
const {Agent, globalAgent} = require('./agent.js');
|
||||
const IncomingMessage = require('./incoming-message.js');
|
||||
const proxyEvents = require('./utils/proxy-events.js');
|
||||
const {
|
||||
ERR_INVALID_ARG_TYPE,
|
||||
ERR_INVALID_PROTOCOL,
|
||||
ERR_HTTP_HEADERS_SENT
|
||||
} = require('./utils/errors.js');
|
||||
const validateHeaderName = require('./utils/validate-header-name.js');
|
||||
const validateHeaderValue = require('./utils/validate-header-value.js');
|
||||
const proxySocketHandler = require('./utils/proxy-socket-handler.js');
|
||||
|
||||
const {
|
||||
HTTP2_HEADER_STATUS,
|
||||
HTTP2_HEADER_METHOD,
|
||||
HTTP2_HEADER_PATH,
|
||||
HTTP2_HEADER_AUTHORITY,
|
||||
HTTP2_METHOD_CONNECT
|
||||
} = http2.constants;
|
||||
|
||||
const kHeaders = Symbol('headers');
|
||||
const kOrigin = Symbol('origin');
|
||||
const kSession = Symbol('session');
|
||||
const kOptions = Symbol('options');
|
||||
const kFlushedHeaders = Symbol('flushedHeaders');
|
||||
const kJobs = Symbol('jobs');
|
||||
const kPendingAgentPromise = Symbol('pendingAgentPromise');
|
||||
|
||||
class ClientRequest extends Writable {
|
||||
constructor(input, options, callback) {
|
||||
super({
|
||||
autoDestroy: false,
|
||||
emitClose: false
|
||||
});
|
||||
|
||||
if (typeof input === 'string') {
|
||||
input = urlToHttpOptions(new URL(input));
|
||||
} else if (input instanceof URL) {
|
||||
input = urlToHttpOptions(input);
|
||||
} else {
|
||||
input = {...input};
|
||||
}
|
||||
|
||||
if (typeof options === 'function' || options === undefined) {
|
||||
// (options, callback)
|
||||
callback = options;
|
||||
options = input;
|
||||
} else {
|
||||
// (input, options, callback)
|
||||
options = Object.assign(input, options);
|
||||
}
|
||||
|
||||
if (options.h2session) {
|
||||
this[kSession] = options.h2session;
|
||||
|
||||
if (this[kSession].destroyed) {
|
||||
throw new Error('The session has been closed already');
|
||||
}
|
||||
|
||||
this.protocol = this[kSession].socket.encrypted ? 'https:' : 'http:';
|
||||
} else if (options.agent === false) {
|
||||
this.agent = new Agent({maxEmptySessions: 0});
|
||||
} else if (typeof options.agent === 'undefined' || options.agent === null) {
|
||||
this.agent = globalAgent;
|
||||
} else if (typeof options.agent.request === 'function') {
|
||||
this.agent = options.agent;
|
||||
} else {
|
||||
throw new ERR_INVALID_ARG_TYPE('options.agent', ['http2wrapper.Agent-like Object', 'undefined', 'false'], options.agent);
|
||||
}
|
||||
|
||||
if (this.agent) {
|
||||
this.protocol = this.agent.protocol;
|
||||
}
|
||||
|
||||
if (options.protocol && options.protocol !== this.protocol) {
|
||||
throw new ERR_INVALID_PROTOCOL(options.protocol, this.protocol);
|
||||
}
|
||||
|
||||
if (!options.port) {
|
||||
options.port = options.defaultPort || (this.agent && this.agent.defaultPort) || 443;
|
||||
}
|
||||
|
||||
options.host = options.hostname || options.host || 'localhost';
|
||||
|
||||
// Unused
|
||||
delete options.hostname;
|
||||
|
||||
const {timeout} = options;
|
||||
options.timeout = undefined;
|
||||
|
||||
this[kHeaders] = Object.create(null);
|
||||
this[kJobs] = [];
|
||||
|
||||
this[kPendingAgentPromise] = undefined;
|
||||
|
||||
this.socket = null;
|
||||
this.connection = null;
|
||||
|
||||
this.method = options.method || 'GET';
|
||||
|
||||
if (!(this.method === 'CONNECT' && (options.path === '/' || options.path === undefined))) {
|
||||
this.path = options.path;
|
||||
}
|
||||
|
||||
this.res = null;
|
||||
this.aborted = false;
|
||||
this.reusedSocket = false;
|
||||
|
||||
const {headers} = options;
|
||||
if (headers) {
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const header in headers) {
|
||||
this.setHeader(header, headers[header]);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.auth && !('authorization' in this[kHeaders])) {
|
||||
this[kHeaders].authorization = 'Basic ' + Buffer.from(options.auth).toString('base64');
|
||||
}
|
||||
|
||||
options.session = options.tlsSession;
|
||||
options.path = options.socketPath;
|
||||
|
||||
this[kOptions] = options;
|
||||
|
||||
// Clients that generate HTTP/2 requests directly SHOULD use the :authority pseudo-header field instead of the Host header field.
|
||||
this[kOrigin] = new URL(`${this.protocol}//${options.servername || options.host}:${options.port}`);
|
||||
|
||||
// A socket is being reused
|
||||
const reuseSocket = options._reuseSocket;
|
||||
if (reuseSocket) {
|
||||
options.createConnection = (...args) => {
|
||||
if (reuseSocket.destroyed) {
|
||||
return this.agent.createConnection(...args);
|
||||
}
|
||||
|
||||
return reuseSocket;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
this.agent.getSession(this[kOrigin], this[kOptions]).catch(() => {});
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
this.setTimeout(timeout);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
this.once('response', callback);
|
||||
}
|
||||
|
||||
this[kFlushedHeaders] = false;
|
||||
}
|
||||
|
||||
get method() {
|
||||
return this[kHeaders][HTTP2_HEADER_METHOD];
|
||||
}
|
||||
|
||||
set method(value) {
|
||||
if (value) {
|
||||
this[kHeaders][HTTP2_HEADER_METHOD] = value.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
get path() {
|
||||
const header = this.method === 'CONNECT' ? HTTP2_HEADER_AUTHORITY : HTTP2_HEADER_PATH;
|
||||
|
||||
return this[kHeaders][header];
|
||||
}
|
||||
|
||||
set path(value) {
|
||||
if (value) {
|
||||
const header = this.method === 'CONNECT' ? HTTP2_HEADER_AUTHORITY : HTTP2_HEADER_PATH;
|
||||
|
||||
this[kHeaders][header] = value;
|
||||
}
|
||||
}
|
||||
|
||||
get host() {
|
||||
return this[kOrigin].hostname;
|
||||
}
|
||||
|
||||
set host(_value) {
|
||||
// Do nothing as this is read only.
|
||||
}
|
||||
|
||||
get _mustNotHaveABody() {
|
||||
return this.method === 'GET' || this.method === 'HEAD' || this.method === 'DELETE';
|
||||
}
|
||||
|
||||
_write(chunk, encoding, callback) {
|
||||
// https://github.com/nodejs/node/blob/654df09ae0c5e17d1b52a900a545f0664d8c7627/lib/internal/http2/util.js#L148-L156
|
||||
if (this._mustNotHaveABody) {
|
||||
callback(new Error('The GET, HEAD and DELETE methods must NOT have a body'));
|
||||
/* istanbul ignore next: Node.js 12 throws directly */
|
||||
return;
|
||||
}
|
||||
|
||||
this.flushHeaders();
|
||||
|
||||
const callWrite = () => this._request.write(chunk, encoding, callback);
|
||||
if (this._request) {
|
||||
callWrite();
|
||||
} else {
|
||||
this[kJobs].push(callWrite);
|
||||
}
|
||||
}
|
||||
|
||||
_final(callback) {
|
||||
this.flushHeaders();
|
||||
|
||||
const callEnd = () => {
|
||||
// For GET, HEAD and DELETE and CONNECT
|
||||
if (this._mustNotHaveABody || this.method === 'CONNECT') {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
this._request.end(callback);
|
||||
};
|
||||
|
||||
if (this._request) {
|
||||
callEnd();
|
||||
} else {
|
||||
this[kJobs].push(callEnd);
|
||||
}
|
||||
}
|
||||
|
||||
abort() {
|
||||
if (this.res && this.res.complete) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.aborted) {
|
||||
process.nextTick(() => this.emit('abort'));
|
||||
}
|
||||
|
||||
this.aborted = true;
|
||||
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
async _destroy(error, callback) {
|
||||
if (this.res) {
|
||||
this.res._dump();
|
||||
}
|
||||
|
||||
if (this._request) {
|
||||
this._request.destroy();
|
||||
} else {
|
||||
process.nextTick(() => {
|
||||
this.emit('close');
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await this[kPendingAgentPromise];
|
||||
} catch (internalError) {
|
||||
if (this.aborted) {
|
||||
error = internalError;
|
||||
}
|
||||
}
|
||||
|
||||
callback(error);
|
||||
}
|
||||
|
||||
async flushHeaders() {
|
||||
if (this[kFlushedHeaders] || this.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this[kFlushedHeaders] = true;
|
||||
|
||||
const isConnectMethod = this.method === HTTP2_METHOD_CONNECT;
|
||||
|
||||
// The real magic is here
|
||||
const onStream = stream => {
|
||||
this._request = stream;
|
||||
|
||||
if (this.destroyed) {
|
||||
stream.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Forwards `timeout`, `continue`, `close` and `error` events to this instance.
|
||||
if (!isConnectMethod) {
|
||||
// TODO: Should we proxy `close` here?
|
||||
proxyEvents(stream, this, ['timeout', 'continue']);
|
||||
}
|
||||
|
||||
stream.once('error', error => {
|
||||
this.destroy(error);
|
||||
});
|
||||
|
||||
stream.once('aborted', () => {
|
||||
const {res} = this;
|
||||
if (res) {
|
||||
res.aborted = true;
|
||||
res.emit('aborted');
|
||||
res.destroy();
|
||||
} else {
|
||||
this.destroy(new Error('The server aborted the HTTP/2 stream'));
|
||||
}
|
||||
});
|
||||
|
||||
const onResponse = (headers, flags, rawHeaders) => {
|
||||
// If we were to emit raw request stream, it would be as fast as the native approach.
|
||||
// Note that wrapping the raw stream in a Proxy instance won't improve the performance (already tested it).
|
||||
const response = new IncomingMessage(this.socket, stream.readableHighWaterMark);
|
||||
this.res = response;
|
||||
|
||||
// Undocumented, but it is used by `cacheable-request`
|
||||
response.url = `${this[kOrigin].origin}${this.path}`;
|
||||
|
||||
response.req = this;
|
||||
response.statusCode = headers[HTTP2_HEADER_STATUS];
|
||||
response.headers = headers;
|
||||
response.rawHeaders = rawHeaders;
|
||||
|
||||
response.once('end', () => {
|
||||
response.complete = true;
|
||||
|
||||
// Has no effect, just be consistent with the Node.js behavior
|
||||
response.socket = null;
|
||||
response.connection = null;
|
||||
});
|
||||
|
||||
if (isConnectMethod) {
|
||||
response.upgrade = true;
|
||||
|
||||
// The HTTP1 API says the socket is detached here,
|
||||
// but we can't do that so we pass the original HTTP2 request.
|
||||
if (this.emit('connect', response, stream, Buffer.alloc(0))) {
|
||||
this.emit('close');
|
||||
} else {
|
||||
// No listeners attached, destroy the original request.
|
||||
stream.destroy();
|
||||
}
|
||||
} else {
|
||||
// Forwards data
|
||||
stream.on('data', chunk => {
|
||||
if (!response._dumped && !response.push(chunk)) {
|
||||
stream.pause();
|
||||
}
|
||||
});
|
||||
|
||||
stream.once('end', () => {
|
||||
if (!this.aborted) {
|
||||
response.push(null);
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.emit('response', response)) {
|
||||
// No listeners attached, dump the response.
|
||||
response._dump();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This event tells we are ready to listen for the data.
|
||||
stream.once('response', onResponse);
|
||||
|
||||
// Emits `information` event
|
||||
stream.once('headers', headers => this.emit('information', {statusCode: headers[HTTP2_HEADER_STATUS]}));
|
||||
|
||||
stream.once('trailers', (trailers, flags, rawTrailers) => {
|
||||
const {res} = this;
|
||||
|
||||
// https://github.com/nodejs/node/issues/41251
|
||||
if (res === null) {
|
||||
onResponse(trailers, flags, rawTrailers);
|
||||
return;
|
||||
}
|
||||
|
||||
// Assigns trailers to the response object.
|
||||
res.trailers = trailers;
|
||||
res.rawTrailers = rawTrailers;
|
||||
});
|
||||
|
||||
stream.once('close', () => {
|
||||
const {aborted, res} = this;
|
||||
if (res) {
|
||||
if (aborted) {
|
||||
res.aborted = true;
|
||||
res.emit('aborted');
|
||||
res.destroy();
|
||||
}
|
||||
|
||||
const finish = () => {
|
||||
res.emit('close');
|
||||
|
||||
this.destroy();
|
||||
this.emit('close');
|
||||
};
|
||||
|
||||
if (res.readable) {
|
||||
res.once('end', finish);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.destroyed) {
|
||||
this.destroy(new Error('The HTTP/2 stream has been early terminated'));
|
||||
this.emit('close');
|
||||
return;
|
||||
}
|
||||
|
||||
this.destroy();
|
||||
this.emit('close');
|
||||
});
|
||||
|
||||
this.socket = new Proxy(stream, proxySocketHandler);
|
||||
|
||||
for (const job of this[kJobs]) {
|
||||
job();
|
||||
}
|
||||
|
||||
this[kJobs].length = 0;
|
||||
|
||||
this.emit('socket', this.socket);
|
||||
};
|
||||
|
||||
if (!(HTTP2_HEADER_AUTHORITY in this[kHeaders]) && !isConnectMethod) {
|
||||
this[kHeaders][HTTP2_HEADER_AUTHORITY] = this[kOrigin].host;
|
||||
}
|
||||
|
||||
// Makes a HTTP2 request
|
||||
if (this[kSession]) {
|
||||
try {
|
||||
onStream(this[kSession].request(this[kHeaders]));
|
||||
} catch (error) {
|
||||
this.destroy(error);
|
||||
}
|
||||
} else {
|
||||
this.reusedSocket = true;
|
||||
|
||||
try {
|
||||
const promise = this.agent.request(this[kOrigin], this[kOptions], this[kHeaders]);
|
||||
this[kPendingAgentPromise] = promise;
|
||||
|
||||
onStream(await promise);
|
||||
|
||||
this[kPendingAgentPromise] = false;
|
||||
} catch (error) {
|
||||
this[kPendingAgentPromise] = false;
|
||||
|
||||
this.destroy(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get connection() {
|
||||
return this.socket;
|
||||
}
|
||||
|
||||
set connection(value) {
|
||||
this.socket = value;
|
||||
}
|
||||
|
||||
getHeaderNames() {
|
||||
return Object.keys(this[kHeaders]);
|
||||
}
|
||||
|
||||
hasHeader(name) {
|
||||
if (typeof name !== 'string') {
|
||||
throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
|
||||
}
|
||||
|
||||
return Boolean(this[kHeaders][name.toLowerCase()]);
|
||||
}
|
||||
|
||||
getHeader(name) {
|
||||
if (typeof name !== 'string') {
|
||||
throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
|
||||
}
|
||||
|
||||
return this[kHeaders][name.toLowerCase()];
|
||||
}
|
||||
|
||||
get headersSent() {
|
||||
return this[kFlushedHeaders];
|
||||
}
|
||||
|
||||
removeHeader(name) {
|
||||
if (typeof name !== 'string') {
|
||||
throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
|
||||
}
|
||||
|
||||
if (this.headersSent) {
|
||||
throw new ERR_HTTP_HEADERS_SENT('remove');
|
||||
}
|
||||
|
||||
delete this[kHeaders][name.toLowerCase()];
|
||||
}
|
||||
|
||||
setHeader(name, value) {
|
||||
if (this.headersSent) {
|
||||
throw new ERR_HTTP_HEADERS_SENT('set');
|
||||
}
|
||||
|
||||
validateHeaderName(name);
|
||||
validateHeaderValue(name, value);
|
||||
|
||||
const lowercased = name.toLowerCase();
|
||||
|
||||
if (lowercased === 'connection') {
|
||||
if (value.toLowerCase() === 'keep-alive') {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid 'connection' header: ${value}`);
|
||||
}
|
||||
|
||||
if (lowercased === 'host' && this.method === 'CONNECT') {
|
||||
this[kHeaders][HTTP2_HEADER_AUTHORITY] = value;
|
||||
} else {
|
||||
this[kHeaders][lowercased] = value;
|
||||
}
|
||||
}
|
||||
|
||||
setNoDelay() {
|
||||
// HTTP2 sockets cannot be malformed, do nothing.
|
||||
}
|
||||
|
||||
setSocketKeepAlive() {
|
||||
// HTTP2 sockets cannot be malformed, do nothing.
|
||||
}
|
||||
|
||||
setTimeout(ms, callback) {
|
||||
const applyTimeout = () => this._request.setTimeout(ms, callback);
|
||||
|
||||
if (this._request) {
|
||||
applyTimeout();
|
||||
} else {
|
||||
this[kJobs].push(applyTimeout);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
get maxHeadersCount() {
|
||||
if (!this.destroyed && this._request) {
|
||||
return this._request.session.localSettings.maxHeaderListSize;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
set maxHeadersCount(_value) {
|
||||
// Updating HTTP2 settings would affect all requests, do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientRequest;
|
73
book/node_modules/http2-wrapper/source/incoming-message.js
generated
vendored
Normal file
73
book/node_modules/http2-wrapper/source/incoming-message.js
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
'use strict';
|
||||
const {Readable} = require('stream');
|
||||
|
||||
class IncomingMessage extends Readable {
|
||||
constructor(socket, highWaterMark) {
|
||||
super({
|
||||
emitClose: false,
|
||||
autoDestroy: true,
|
||||
highWaterMark
|
||||
});
|
||||
|
||||
this.statusCode = null;
|
||||
this.statusMessage = '';
|
||||
this.httpVersion = '2.0';
|
||||
this.httpVersionMajor = 2;
|
||||
this.httpVersionMinor = 0;
|
||||
this.headers = {};
|
||||
this.trailers = {};
|
||||
this.req = null;
|
||||
|
||||
this.aborted = false;
|
||||
this.complete = false;
|
||||
this.upgrade = null;
|
||||
|
||||
this.rawHeaders = [];
|
||||
this.rawTrailers = [];
|
||||
|
||||
this.socket = socket;
|
||||
|
||||
this._dumped = false;
|
||||
}
|
||||
|
||||
get connection() {
|
||||
return this.socket;
|
||||
}
|
||||
|
||||
set connection(value) {
|
||||
this.socket = value;
|
||||
}
|
||||
|
||||
_destroy(error, callback) {
|
||||
if (!this.readableEnded) {
|
||||
this.aborted = true;
|
||||
}
|
||||
|
||||
// See https://github.com/nodejs/node/issues/35303
|
||||
callback();
|
||||
|
||||
this.req._request.destroy(error);
|
||||
}
|
||||
|
||||
setTimeout(ms, callback) {
|
||||
this.req.setTimeout(ms, callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
_dump() {
|
||||
if (!this._dumped) {
|
||||
this._dumped = true;
|
||||
|
||||
this.removeAllListeners('data');
|
||||
this.resume();
|
||||
}
|
||||
}
|
||||
|
||||
_read() {
|
||||
if (this.req) {
|
||||
this.req._request.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IncomingMessage;
|
50
book/node_modules/http2-wrapper/source/index.js
generated
vendored
Normal file
50
book/node_modules/http2-wrapper/source/index.js
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
const http2 = require('http2');
|
||||
const {
|
||||
Agent,
|
||||
globalAgent
|
||||
} = require('./agent.js');
|
||||
const ClientRequest = require('./client-request.js');
|
||||
const IncomingMessage = require('./incoming-message.js');
|
||||
const auto = require('./auto.js');
|
||||
const {
|
||||
HttpOverHttp2,
|
||||
HttpsOverHttp2
|
||||
} = require('./proxies/h1-over-h2.js');
|
||||
const Http2OverHttp2 = require('./proxies/h2-over-h2.js');
|
||||
const {
|
||||
Http2OverHttp,
|
||||
Http2OverHttps
|
||||
} = require('./proxies/h2-over-h1.js');
|
||||
const validateHeaderName = require('./utils/validate-header-name.js');
|
||||
const validateHeaderValue = require('./utils/validate-header-value.js');
|
||||
|
||||
const request = (url, options, callback) => new ClientRequest(url, options, callback);
|
||||
|
||||
const get = (url, options, callback) => {
|
||||
// eslint-disable-next-line unicorn/prevent-abbreviations
|
||||
const req = new ClientRequest(url, options, callback);
|
||||
req.end();
|
||||
|
||||
return req;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
...http2,
|
||||
ClientRequest,
|
||||
IncomingMessage,
|
||||
Agent,
|
||||
globalAgent,
|
||||
request,
|
||||
get,
|
||||
auto,
|
||||
proxies: {
|
||||
HttpOverHttp2,
|
||||
HttpsOverHttp2,
|
||||
Http2OverHttp2,
|
||||
Http2OverHttp,
|
||||
Http2OverHttps
|
||||
},
|
||||
validateHeaderName,
|
||||
validateHeaderValue
|
||||
};
|
17
book/node_modules/http2-wrapper/source/proxies/get-auth-headers.js
generated
vendored
Normal file
17
book/node_modules/http2-wrapper/source/proxies/get-auth-headers.js
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = self => {
|
||||
const {username, password} = self.proxyOptions.url;
|
||||
|
||||
if (username || password) {
|
||||
const data = `${username}:${password}`;
|
||||
const authorization = `Basic ${Buffer.from(data).toString('base64')}`;
|
||||
|
||||
return {
|
||||
'proxy-authorization': authorization,
|
||||
authorization
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
90
book/node_modules/http2-wrapper/source/proxies/h1-over-h2.js
generated
vendored
Normal file
90
book/node_modules/http2-wrapper/source/proxies/h1-over-h2.js
generated
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
'use strict';
|
||||
const tls = require('tls');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const JSStreamSocket = require('../utils/js-stream-socket.js');
|
||||
const {globalAgent} = require('../agent.js');
|
||||
const UnexpectedStatusCodeError = require('./unexpected-status-code-error.js');
|
||||
const initialize = require('./initialize.js');
|
||||
const getAuthorizationHeaders = require('./get-auth-headers.js');
|
||||
|
||||
const createConnection = (self, options, callback) => {
|
||||
(async () => {
|
||||
try {
|
||||
const {proxyOptions} = self;
|
||||
const {url, headers, raw} = proxyOptions;
|
||||
|
||||
const stream = await globalAgent.request(url, proxyOptions, {
|
||||
...getAuthorizationHeaders(self),
|
||||
...headers,
|
||||
':method': 'CONNECT',
|
||||
':authority': `${options.host}:${options.port}`
|
||||
});
|
||||
|
||||
stream.once('error', callback);
|
||||
stream.once('response', headers => {
|
||||
const statusCode = headers[':status'];
|
||||
|
||||
if (statusCode !== 200) {
|
||||
callback(new UnexpectedStatusCodeError(statusCode, ''));
|
||||
return;
|
||||
}
|
||||
|
||||
const encrypted = self instanceof https.Agent;
|
||||
|
||||
if (raw && encrypted) {
|
||||
options.socket = stream;
|
||||
const secureStream = tls.connect(options);
|
||||
|
||||
secureStream.once('close', () => {
|
||||
stream.destroy();
|
||||
});
|
||||
|
||||
callback(null, secureStream);
|
||||
return;
|
||||
}
|
||||
|
||||
const socket = new JSStreamSocket(stream);
|
||||
socket.encrypted = false;
|
||||
socket._handle.getpeername = out => {
|
||||
out.family = undefined;
|
||||
out.address = undefined;
|
||||
out.port = undefined;
|
||||
};
|
||||
|
||||
callback(null, socket);
|
||||
});
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
class HttpOverHttp2 extends http.Agent {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
initialize(this, options.proxyOptions);
|
||||
}
|
||||
|
||||
createConnection(options, callback) {
|
||||
createConnection(this, options, callback);
|
||||
}
|
||||
}
|
||||
|
||||
class HttpsOverHttp2 extends https.Agent {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
initialize(this, options.proxyOptions);
|
||||
}
|
||||
|
||||
createConnection(options, callback) {
|
||||
createConnection(this, options, callback);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
HttpOverHttp2,
|
||||
HttpsOverHttp2
|
||||
};
|
48
book/node_modules/http2-wrapper/source/proxies/h2-over-h1.js
generated
vendored
Normal file
48
book/node_modules/http2-wrapper/source/proxies/h2-over-h1.js
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const Http2OverHttpX = require('./h2-over-hx.js');
|
||||
const getAuthorizationHeaders = require('./get-auth-headers.js');
|
||||
|
||||
const getStream = request => new Promise((resolve, reject) => {
|
||||
const onConnect = (response, socket, head) => {
|
||||
socket.unshift(head);
|
||||
|
||||
request.off('error', reject);
|
||||
resolve([socket, response.statusCode, response.statusMessage]);
|
||||
};
|
||||
|
||||
request.once('error', reject);
|
||||
request.once('connect', onConnect);
|
||||
});
|
||||
|
||||
class Http2OverHttp extends Http2OverHttpX {
|
||||
async _getProxyStream(authority) {
|
||||
const {proxyOptions} = this;
|
||||
const {url, headers} = this.proxyOptions;
|
||||
|
||||
const network = url.protocol === 'https:' ? https : http;
|
||||
|
||||
// `new URL('https://localhost/httpbin.org:443')` results in
|
||||
// a `/httpbin.org:443` path, which has an invalid leading slash.
|
||||
const request = network.request({
|
||||
...proxyOptions,
|
||||
hostname: url.hostname,
|
||||
port: url.port,
|
||||
path: authority,
|
||||
headers: {
|
||||
...getAuthorizationHeaders(this),
|
||||
...headers,
|
||||
host: authority
|
||||
},
|
||||
method: 'CONNECT'
|
||||
}).end();
|
||||
|
||||
return getStream(request);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Http2OverHttp,
|
||||
Http2OverHttps: Http2OverHttp
|
||||
};
|
32
book/node_modules/http2-wrapper/source/proxies/h2-over-h2.js
generated
vendored
Normal file
32
book/node_modules/http2-wrapper/source/proxies/h2-over-h2.js
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
const {globalAgent} = require('../agent.js');
|
||||
const Http2OverHttpX = require('./h2-over-hx.js');
|
||||
const getAuthorizationHeaders = require('./get-auth-headers.js');
|
||||
|
||||
const getStatusCode = stream => new Promise((resolve, reject) => {
|
||||
stream.once('error', reject);
|
||||
stream.once('response', headers => {
|
||||
stream.off('error', reject);
|
||||
resolve(headers[':status']);
|
||||
});
|
||||
});
|
||||
|
||||
class Http2OverHttp2 extends Http2OverHttpX {
|
||||
async _getProxyStream(authority) {
|
||||
const {proxyOptions} = this;
|
||||
|
||||
const headers = {
|
||||
...getAuthorizationHeaders(this),
|
||||
...proxyOptions.headers,
|
||||
':method': 'CONNECT',
|
||||
':authority': authority
|
||||
};
|
||||
|
||||
const stream = await globalAgent.request(proxyOptions.url, proxyOptions, headers);
|
||||
const statusCode = await getStatusCode(stream);
|
||||
|
||||
return [stream, statusCode, ''];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Http2OverHttp2;
|
40
book/node_modules/http2-wrapper/source/proxies/h2-over-hx.js
generated
vendored
Normal file
40
book/node_modules/http2-wrapper/source/proxies/h2-over-hx.js
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
const {Agent} = require('../agent.js');
|
||||
const JSStreamSocket = require('../utils/js-stream-socket.js');
|
||||
const UnexpectedStatusCodeError = require('./unexpected-status-code-error.js');
|
||||
const initialize = require('./initialize.js');
|
||||
|
||||
class Http2OverHttpX extends Agent {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
initialize(this, options.proxyOptions);
|
||||
}
|
||||
|
||||
async createConnection(origin, options) {
|
||||
const authority = `${origin.hostname}:${origin.port || 443}`;
|
||||
|
||||
const [stream, statusCode, statusMessage] = await this._getProxyStream(authority);
|
||||
if (statusCode !== 200) {
|
||||
throw new UnexpectedStatusCodeError(statusCode, statusMessage);
|
||||
}
|
||||
|
||||
if (this.proxyOptions.raw) {
|
||||
options.socket = stream;
|
||||
} else {
|
||||
const socket = new JSStreamSocket(stream);
|
||||
socket.encrypted = false;
|
||||
socket._handle.getpeername = out => {
|
||||
out.family = undefined;
|
||||
out.address = undefined;
|
||||
out.port = undefined;
|
||||
};
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
return super.createConnection(origin, options);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Http2OverHttpX;
|
21
book/node_modules/http2-wrapper/source/proxies/initialize.js
generated
vendored
Normal file
21
book/node_modules/http2-wrapper/source/proxies/initialize.js
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
// See https://github.com/facebook/jest/issues/2549
|
||||
// eslint-disable-next-line node/prefer-global/url
|
||||
const {URL} = require('url');
|
||||
const checkType = require('../utils/check-type.js');
|
||||
|
||||
module.exports = (self, proxyOptions) => {
|
||||
checkType('proxyOptions', proxyOptions, ['object']);
|
||||
checkType('proxyOptions.headers', proxyOptions.headers, ['object', 'undefined']);
|
||||
checkType('proxyOptions.raw', proxyOptions.raw, ['boolean', 'undefined']);
|
||||
checkType('proxyOptions.url', proxyOptions.url, [URL, 'string']);
|
||||
|
||||
const url = new URL(proxyOptions.url);
|
||||
|
||||
self.proxyOptions = {
|
||||
raw: true,
|
||||
...proxyOptions,
|
||||
headers: {...proxyOptions.headers},
|
||||
url
|
||||
};
|
||||
};
|
11
book/node_modules/http2-wrapper/source/proxies/unexpected-status-code-error.js
generated
vendored
Normal file
11
book/node_modules/http2-wrapper/source/proxies/unexpected-status-code-error.js
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
class UnexpectedStatusCodeError extends Error {
|
||||
constructor(statusCode, statusMessage = '') {
|
||||
super(`The proxy server rejected the request with status code ${statusCode} (${statusMessage || 'empty status message'})`);
|
||||
this.statusCode = statusCode;
|
||||
this.statusMessage = statusMessage;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UnexpectedStatusCodeError;
|
29
book/node_modules/http2-wrapper/source/utils/calculate-server-name.js
generated
vendored
Normal file
29
book/node_modules/http2-wrapper/source/utils/calculate-server-name.js
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
const {isIP} = require('net');
|
||||
const assert = require('assert');
|
||||
|
||||
const getHost = host => {
|
||||
if (host[0] === '[') {
|
||||
const idx = host.indexOf(']');
|
||||
|
||||
assert(idx !== -1);
|
||||
return host.slice(1, idx);
|
||||
}
|
||||
|
||||
const idx = host.indexOf(':');
|
||||
if (idx === -1) {
|
||||
return host;
|
||||
}
|
||||
|
||||
return host.slice(0, idx);
|
||||
};
|
||||
|
||||
module.exports = host => {
|
||||
const servername = getHost(host);
|
||||
|
||||
if (isIP(servername)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return servername;
|
||||
};
|
20
book/node_modules/http2-wrapper/source/utils/check-type.js
generated
vendored
Normal file
20
book/node_modules/http2-wrapper/source/utils/check-type.js
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
const checkType = (name, value, types) => {
|
||||
const valid = types.some(type => {
|
||||
const typeofType = typeof type;
|
||||
if (typeofType === 'string') {
|
||||
return typeof value === type;
|
||||
}
|
||||
|
||||
return value instanceof type;
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
const names = types.map(type => typeof type === 'string' ? type : type.name);
|
||||
|
||||
throw new TypeError(`Expected '${name}' to be a type of ${names.join(' or ')}, got ${typeof value}`);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = checkType;
|
33
book/node_modules/http2-wrapper/source/utils/delay-async-destroy.js
generated
vendored
Normal file
33
book/node_modules/http2-wrapper/source/utils/delay-async-destroy.js
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = stream => {
|
||||
if (stream.listenerCount('error') !== 0) {
|
||||
return stream;
|
||||
}
|
||||
|
||||
stream.__destroy = stream._destroy;
|
||||
stream._destroy = (...args) => {
|
||||
const callback = args.pop();
|
||||
|
||||
stream.__destroy(...args, async error => {
|
||||
await Promise.resolve();
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
const onError = error => {
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
Promise.resolve().then(() => {
|
||||
stream.emit('error', error);
|
||||
});
|
||||
};
|
||||
|
||||
stream.once('error', onError);
|
||||
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
Promise.resolve().then(() => {
|
||||
stream.off('error', onError);
|
||||
});
|
||||
|
||||
return stream;
|
||||
};
|
51
book/node_modules/http2-wrapper/source/utils/errors.js
generated
vendored
Normal file
51
book/node_modules/http2-wrapper/source/utils/errors.js
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
'use strict';
|
||||
/* istanbul ignore file: https://github.com/nodejs/node/blob/master/lib/internal/errors.js */
|
||||
|
||||
const makeError = (Base, key, getMessage) => {
|
||||
module.exports[key] = class NodeError extends Base {
|
||||
constructor(...args) {
|
||||
super(typeof getMessage === 'string' ? getMessage : getMessage(args));
|
||||
this.name = `${super.name} [${key}]`;
|
||||
this.code = key;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
makeError(TypeError, 'ERR_INVALID_ARG_TYPE', args => {
|
||||
const type = args[0].includes('.') ? 'property' : 'argument';
|
||||
|
||||
let valid = args[1];
|
||||
const isManyTypes = Array.isArray(valid);
|
||||
|
||||
if (isManyTypes) {
|
||||
valid = `${valid.slice(0, -1).join(', ')} or ${valid.slice(-1)}`;
|
||||
}
|
||||
|
||||
return `The "${args[0]}" ${type} must be ${isManyTypes ? 'one of' : 'of'} type ${valid}. Received ${typeof args[2]}`;
|
||||
});
|
||||
|
||||
makeError(TypeError, 'ERR_INVALID_PROTOCOL', args =>
|
||||
`Protocol "${args[0]}" not supported. Expected "${args[1]}"`
|
||||
);
|
||||
|
||||
makeError(Error, 'ERR_HTTP_HEADERS_SENT', args =>
|
||||
`Cannot ${args[0]} headers after they are sent to the client`
|
||||
);
|
||||
|
||||
makeError(TypeError, 'ERR_INVALID_HTTP_TOKEN', args =>
|
||||
`${args[0]} must be a valid HTTP token [${args[1]}]`
|
||||
);
|
||||
|
||||
makeError(TypeError, 'ERR_HTTP_INVALID_HEADER_VALUE', args =>
|
||||
`Invalid value "${args[0]} for header "${args[1]}"`
|
||||
);
|
||||
|
||||
makeError(TypeError, 'ERR_INVALID_CHAR', args =>
|
||||
`Invalid character in ${args[0]} [${args[1]}]`
|
||||
);
|
||||
|
||||
makeError(
|
||||
Error,
|
||||
'ERR_HTTP2_NO_SOCKET_MANIPULATION',
|
||||
'HTTP/2 sockets should not be directly manipulated (e.g. read and written)'
|
||||
);
|
13
book/node_modules/http2-wrapper/source/utils/is-request-pseudo-header.js
generated
vendored
Normal file
13
book/node_modules/http2-wrapper/source/utils/is-request-pseudo-header.js
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = header => {
|
||||
switch (header) {
|
||||
case ':method':
|
||||
case ':scheme':
|
||||
case ':authority':
|
||||
case ':path':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
8
book/node_modules/http2-wrapper/source/utils/js-stream-socket.js
generated
vendored
Normal file
8
book/node_modules/http2-wrapper/source/utils/js-stream-socket.js
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
const stream = require('stream');
|
||||
const tls = require('tls');
|
||||
|
||||
// Really awesome hack.
|
||||
const JSStreamSocket = (new tls.TLSSocket(new stream.PassThrough()))._handle._parentWrap.constructor;
|
||||
|
||||
module.exports = JSStreamSocket;
|
7
book/node_modules/http2-wrapper/source/utils/proxy-events.js
generated
vendored
Normal file
7
book/node_modules/http2-wrapper/source/utils/proxy-events.js
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (from, to, events) => {
|
||||
for (const event of events) {
|
||||
from.on(event, (...args) => to.emit(event, ...args));
|
||||
}
|
||||
};
|
102
book/node_modules/http2-wrapper/source/utils/proxy-socket-handler.js
generated
vendored
Normal file
102
book/node_modules/http2-wrapper/source/utils/proxy-socket-handler.js
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
const {ERR_HTTP2_NO_SOCKET_MANIPULATION} = require('./errors.js');
|
||||
|
||||
/* istanbul ignore file */
|
||||
/* https://github.com/nodejs/node/blob/6eec858f34a40ffa489c1ec54bb24da72a28c781/lib/internal/http2/compat.js#L195-L272 */
|
||||
|
||||
const proxySocketHandler = {
|
||||
has(stream, property) {
|
||||
// Replaced [kSocket] with .socket
|
||||
const reference = stream.session === undefined ? stream : stream.session.socket;
|
||||
return (property in stream) || (property in reference);
|
||||
},
|
||||
|
||||
get(stream, property) {
|
||||
switch (property) {
|
||||
case 'on':
|
||||
case 'once':
|
||||
case 'end':
|
||||
case 'emit':
|
||||
case 'destroy':
|
||||
return stream[property].bind(stream);
|
||||
case 'writable':
|
||||
case 'destroyed':
|
||||
return stream[property];
|
||||
case 'readable':
|
||||
if (stream.destroyed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return stream.readable;
|
||||
case 'setTimeout': {
|
||||
const {session} = stream;
|
||||
if (session !== undefined) {
|
||||
return session.setTimeout.bind(session);
|
||||
}
|
||||
|
||||
return stream.setTimeout.bind(stream);
|
||||
}
|
||||
|
||||
case 'write':
|
||||
case 'read':
|
||||
case 'pause':
|
||||
case 'resume':
|
||||
throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
|
||||
default: {
|
||||
// Replaced [kSocket] with .socket
|
||||
const reference = stream.session === undefined ? stream : stream.session.socket;
|
||||
const value = reference[property];
|
||||
|
||||
return typeof value === 'function' ? value.bind(reference) : value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getPrototypeOf(stream) {
|
||||
if (stream.session !== undefined) {
|
||||
// Replaced [kSocket] with .socket
|
||||
return Reflect.getPrototypeOf(stream.session.socket);
|
||||
}
|
||||
|
||||
return Reflect.getPrototypeOf(stream);
|
||||
},
|
||||
|
||||
set(stream, property, value) {
|
||||
switch (property) {
|
||||
case 'writable':
|
||||
case 'readable':
|
||||
case 'destroyed':
|
||||
case 'on':
|
||||
case 'once':
|
||||
case 'end':
|
||||
case 'emit':
|
||||
case 'destroy':
|
||||
stream[property] = value;
|
||||
return true;
|
||||
case 'setTimeout': {
|
||||
const {session} = stream;
|
||||
if (session === undefined) {
|
||||
stream.setTimeout = value;
|
||||
} else {
|
||||
session.setTimeout = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case 'write':
|
||||
case 'read':
|
||||
case 'pause':
|
||||
case 'resume':
|
||||
throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
|
||||
default: {
|
||||
// Replaced [kSocket] with .socket
|
||||
const reference = stream.session === undefined ? stream : stream.session.socket;
|
||||
reference[property] = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = proxySocketHandler;
|
11
book/node_modules/http2-wrapper/source/utils/validate-header-name.js
generated
vendored
Normal file
11
book/node_modules/http2-wrapper/source/utils/validate-header-name.js
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
const {ERR_INVALID_HTTP_TOKEN} = require('./errors.js');
|
||||
const isRequestPseudoHeader = require('./is-request-pseudo-header.js');
|
||||
|
||||
const isValidHttpToken = /^[\^`\-\w!#$%&*+.|~]+$/;
|
||||
|
||||
module.exports = name => {
|
||||
if (typeof name !== 'string' || (!isValidHttpToken.test(name) && !isRequestPseudoHeader(name))) {
|
||||
throw new ERR_INVALID_HTTP_TOKEN('Header name', name);
|
||||
}
|
||||
};
|
17
book/node_modules/http2-wrapper/source/utils/validate-header-value.js
generated
vendored
Normal file
17
book/node_modules/http2-wrapper/source/utils/validate-header-value.js
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
const {
|
||||
ERR_HTTP_INVALID_HEADER_VALUE,
|
||||
ERR_INVALID_CHAR
|
||||
} = require('./errors.js');
|
||||
|
||||
const isInvalidHeaderValue = /[^\t\u0020-\u007E\u0080-\u00FF]/;
|
||||
|
||||
module.exports = (name, value) => {
|
||||
if (typeof value === 'undefined') {
|
||||
throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
|
||||
}
|
||||
|
||||
if (isInvalidHeaderValue.test(value)) {
|
||||
throw new ERR_INVALID_CHAR('header content', name);
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user