105 lines
4.9 KiB
JavaScript
105 lines
4.9 KiB
JavaScript
|
"use strict";
|
||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||
|
};
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.clearSessionsCache = exports.createResponseLike = exports.formatRequestHttp2Headers = exports.getHttp2Session = void 0;
|
||
|
const http2_1 = __importDefault(require("http2"));
|
||
|
const lru_cache_1 = __importDefault(require("lru-cache"));
|
||
|
const lodash_1 = require("lodash");
|
||
|
const connection_reset_guard_1 = require("../connection-reset-guard");
|
||
|
const logger_1 = __importDefault(require("../../utils/logger"));
|
||
|
const { HTTP2_HEADER_PATH, HTTP2_HEADER_STATUS, HTTP2_HEADER_METHOD, HTTP2_HEADER_AUTHORITY, HTTP2_HEADER_CONNECTION, HTTP2_HEADER_UPGRADE, HTTP2_HEADER_KEEP_ALIVE, HTTP2_HEADER_PROXY_CONNECTION, HTTP2_HEADER_TRANSFER_ENCODING, HTTP2_HEADER_HTTP2_SETTINGS, HTTP2_HEADER_HOST, } = http2_1.default.constants;
|
||
|
const HTTP2_SESSIONS_CACHE_SIZE = 100;
|
||
|
const HTTP2_SESSION_TIMEOUT = 60000;
|
||
|
const HTTP2_CONNECT_TIMEOUT = 5000;
|
||
|
const HTTP2_LOCAL_SETTINGS_TIMEOUT = 3000;
|
||
|
const HTTP2_UNSUPPORTED_HEADERS = [
|
||
|
HTTP2_HEADER_CONNECTION, HTTP2_HEADER_UPGRADE, HTTP2_HEADER_HTTP2_SETTINGS, HTTP2_HEADER_KEEP_ALIVE,
|
||
|
HTTP2_HEADER_PROXY_CONNECTION, HTTP2_HEADER_TRANSFER_ENCODING, HTTP2_HEADER_HOST,
|
||
|
];
|
||
|
const unsupportedOrigins = [];
|
||
|
const pendingSessions = new Map();
|
||
|
const sessionsCache = new lru_cache_1.default({
|
||
|
max: HTTP2_SESSIONS_CACHE_SIZE,
|
||
|
dispose: (_, session) => {
|
||
|
if (!session.closed)
|
||
|
session.close();
|
||
|
},
|
||
|
});
|
||
|
async function getHttp2Session(requestId, origin) {
|
||
|
if (sessionsCache.has(origin))
|
||
|
return sessionsCache.get(origin) || null;
|
||
|
if (pendingSessions.has(origin))
|
||
|
return pendingSessions.get(origin) || null;
|
||
|
if (unsupportedOrigins.includes(origin))
|
||
|
return null;
|
||
|
const pendingSession = new Promise(resolve => {
|
||
|
const session = http2_1.default.connect(origin, { settings: { enablePush: false } });
|
||
|
const errorHandler = (err) => {
|
||
|
pendingSessions.delete(origin);
|
||
|
if (err.code === 'ERR_HTTP2_ERROR' || err.code === 'ERR_HTTP2_PING_CANCEL') {
|
||
|
unsupportedOrigins.push(origin);
|
||
|
logger_1.default.destination.onHttp2Unsupported(requestId, origin);
|
||
|
}
|
||
|
session.destroy();
|
||
|
resolve(null);
|
||
|
};
|
||
|
const connectTimeout = setTimeout(() => errorHandler({ code: 'ERR_HTTP2_ERROR' }), HTTP2_CONNECT_TIMEOUT);
|
||
|
session.once('error', errorHandler);
|
||
|
session.once('connect', () => {
|
||
|
const localSettingsTimeout = setTimeout(() => errorHandler({ code: 'ERR_HTTP2_ERROR' }), HTTP2_LOCAL_SETTINGS_TIMEOUT);
|
||
|
clearTimeout(connectTimeout);
|
||
|
session.ping(lodash_1.noop);
|
||
|
session.once('localSettings', () => {
|
||
|
clearTimeout(localSettingsTimeout);
|
||
|
pendingSessions.delete(origin);
|
||
|
sessionsCache.set(origin, session);
|
||
|
logger_1.default.destination.onHttp2SessionCreated(requestId, origin, sessionsCache.length, HTTP2_SESSIONS_CACHE_SIZE);
|
||
|
session.once('close', () => {
|
||
|
sessionsCache.del(origin);
|
||
|
logger_1.default.destination.onHttp2SessionClosed(requestId, origin, sessionsCache.length, HTTP2_SESSIONS_CACHE_SIZE);
|
||
|
});
|
||
|
session.off('error', errorHandler);
|
||
|
session.once('error', (err) => {
|
||
|
if (!(0, connection_reset_guard_1.isConnectionResetError)(err)) {
|
||
|
logger_1.default.destination.onHttp2Error(requestId, origin, err);
|
||
|
throw err;
|
||
|
}
|
||
|
});
|
||
|
session.setTimeout(HTTP2_SESSION_TIMEOUT, () => {
|
||
|
logger_1.default.destination.onHttp2SessionTimeout(origin, HTTP2_SESSION_TIMEOUT);
|
||
|
sessionsCache.del(origin);
|
||
|
});
|
||
|
resolve(session);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
pendingSessions.set(origin, pendingSession);
|
||
|
return pendingSession;
|
||
|
}
|
||
|
exports.getHttp2Session = getHttp2Session;
|
||
|
function formatRequestHttp2Headers(opts) {
|
||
|
return Object.keys(opts.headers).reduce((headers, key) => {
|
||
|
if (!HTTP2_UNSUPPORTED_HEADERS.includes(key))
|
||
|
headers[key] = opts.headers[key];
|
||
|
return headers;
|
||
|
}, {
|
||
|
[HTTP2_HEADER_METHOD]: opts.method,
|
||
|
[HTTP2_HEADER_PATH]: opts.path,
|
||
|
[HTTP2_HEADER_AUTHORITY]: opts.headers.host,
|
||
|
});
|
||
|
}
|
||
|
exports.formatRequestHttp2Headers = formatRequestHttp2Headers;
|
||
|
function createResponseLike(stream, response) {
|
||
|
const statusCode = response[HTTP2_HEADER_STATUS];
|
||
|
const headers = Object.assign({}, response);
|
||
|
delete headers[HTTP2_HEADER_STATUS];
|
||
|
return Object.assign(stream, { trailers: {}, statusCode, headers });
|
||
|
}
|
||
|
exports.createResponseLike = createResponseLike;
|
||
|
function clearSessionsCache() {
|
||
|
sessionsCache.reset();
|
||
|
}
|
||
|
exports.clearSessionsCache = clearSessionsCache;
|