flow like the river

This commit is contained in:
root 2025-11-07 00:06:12 +01:00
commit 013fe673f3
42435 changed files with 5764238 additions and 0 deletions

View file

@ -0,0 +1,86 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import { inspect } from 'util';
import * as http from 'http';
import { URL } from 'url';
import { ConnectionOptions as TlsConnectionOptions, TLSSocket, DetailedPeerCertificate } from 'tls';
import { Readable as ReadableStream } from 'stream';
import Diagnostic from '../Diagnostic';
import { ApiKeyAuth, BasicAuth, BearerAuth, HttpAgentOptions, UndiciAgentOptions, agentFn } from '../types';
import { kStatus, kDiagnostic, kCaFingerprint } from '../symbols';
export interface ConnectionOptions {
url: URL;
tls?: TlsConnectionOptions;
id?: string;
headers?: http.IncomingHttpHeaders;
status?: string;
auth?: BasicAuth | ApiKeyAuth | BearerAuth;
diagnostic?: Diagnostic;
timeout?: number;
agent?: HttpAgentOptions | UndiciAgentOptions | agentFn | boolean;
proxy?: string | URL;
caFingerprint?: string;
}
export interface ConnectionRequestParams {
method: string;
path: string;
headers?: http.IncomingHttpHeaders;
body?: string | Buffer | ReadableStream | null;
querystring?: string;
}
export interface ConnectionRequestOptions {
requestId: string | number;
name: string | symbol;
context: any;
maxResponseSize?: number;
maxCompressedResponseSize?: number;
signal?: AbortSignal;
timeout?: number;
}
export interface ConnectionRequestOptionsAsStream extends ConnectionRequestOptions {
asStream: true;
}
export interface ConnectionRequestResponse {
body: string | Buffer;
headers: http.IncomingHttpHeaders;
statusCode: number;
}
export interface ConnectionRequestResponseAsStream {
body: ReadableStream;
headers: http.IncomingHttpHeaders;
statusCode: number;
}
export default class BaseConnection {
url: URL;
tls: TlsConnectionOptions | null;
id: string;
timeout: number;
headers: http.IncomingHttpHeaders;
deadCount: number;
resurrectTimeout: number;
_openRequests: number;
weight: number;
[kStatus]: string;
[kCaFingerprint]: string | null;
[kDiagnostic]: Diagnostic;
static statuses: {
ALIVE: string;
DEAD: string;
};
constructor(opts: ConnectionOptions);
get status(): string;
set status(status: string);
get diagnostic(): Diagnostic;
request(params: ConnectionRequestParams, options: ConnectionRequestOptions): Promise<ConnectionRequestResponse>;
request(params: ConnectionRequestParams, options: ConnectionRequestOptionsAsStream): Promise<ConnectionRequestResponseAsStream>;
close(): Promise<void>;
[inspect.custom](depth: number, options: Record<string, any>): Record<string, any>;
toJSON(): Record<string, any>;
}
export declare function prepareHeaders(headers?: http.IncomingHttpHeaders, auth?: BasicAuth | ApiKeyAuth | BearerAuth): http.IncomingHttpHeaders;
export declare function getIssuerCertificate(socket: TLSSocket): DetailedPeerCertificate | null;

View file

@ -0,0 +1,224 @@
"use strict";
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
var _a, _b, _c;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getIssuerCertificate = exports.prepareHeaders = void 0;
const tslib_1 = require("tslib");
const util_1 = require("util");
const Diagnostic_1 = tslib_1.__importDefault(require("../Diagnostic"));
const errors_1 = require("../errors");
const symbols_1 = require("../symbols");
class BaseConnection {
constructor(opts) {
var _d, _e, _f, _g, _h, _j;
Object.defineProperty(this, "url", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "tls", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "id", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "timeout", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "headers", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "deadCount", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "resurrectTimeout", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_openRequests", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "weight", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, _a, {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, _b, {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, _c, {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.url = opts.url;
this.tls = (_d = opts.tls) !== null && _d !== void 0 ? _d : null;
this.id = (_e = opts.id) !== null && _e !== void 0 ? _e : stripAuth(opts.url.href);
this.headers = prepareHeaders(opts.headers, opts.auth);
this.timeout = (_f = opts.timeout) !== null && _f !== void 0 ? _f : 30000;
this.deadCount = 0;
this.resurrectTimeout = 0;
this.weight = 0;
this._openRequests = 0;
this[symbols_1.kStatus] = (_g = opts.status) !== null && _g !== void 0 ? _g : BaseConnection.statuses.ALIVE;
this[symbols_1.kDiagnostic] = (_h = opts.diagnostic) !== null && _h !== void 0 ? _h : new Diagnostic_1.default();
this[symbols_1.kCaFingerprint] = (_j = opts.caFingerprint) !== null && _j !== void 0 ? _j : null;
if (!['http:', 'https:'].includes(this.url.protocol)) {
throw new errors_1.ConfigurationError(`Invalid protocol: '${this.url.protocol}'`);
}
}
get status() {
return this[symbols_1.kStatus];
}
set status(status) {
if (!validStatuses.includes(status)) {
throw new errors_1.ConfigurationError(`Unsupported status: '${status}'`);
}
this[symbols_1.kStatus] = status;
}
get diagnostic() {
return this[symbols_1.kDiagnostic];
}
async request(params, options) {
throw new errors_1.ConfigurationError('The request method should be implemented by extended classes');
}
/* istanbul ignore next */
async close() {
throw new errors_1.ConfigurationError('The close method should be implemented by extended classes');
}
// Handles console.log and utils.inspect invocations.
// We want to hide `auth`, `agent` and `tls` since they made
// the logs very hard to read. The user can still
// access them with `instance.agent` and `instance.tls`.
[(_a = symbols_1.kStatus, _b = symbols_1.kCaFingerprint, _c = symbols_1.kDiagnostic, util_1.inspect.custom)](depth, options) {
const { authorization, ...headers } = this.headers;
return {
url: stripAuth(this.url.toString()),
id: this.id,
headers,
status: this.status
};
}
toJSON() {
const { authorization, ...headers } = this.headers;
return {
url: stripAuth(this.url.toString()),
id: this.id,
headers,
status: this.status
};
}
}
exports.default = BaseConnection;
Object.defineProperty(BaseConnection, "statuses", {
enumerable: true,
configurable: true,
writable: true,
value: {
ALIVE: 'alive',
DEAD: 'dead'
}
});
const validStatuses = Object.keys(BaseConnection.statuses)
// @ts-expect-error
.map(k => BaseConnection.statuses[k]);
function stripAuth(url) {
if (!url.includes('@'))
return url;
return url.slice(0, url.indexOf('//') + 2) + url.slice(url.indexOf('@') + 1);
}
function prepareHeaders(headers = {}, auth) {
if (auth != null && headers.authorization == null) {
/* istanbul ignore else */
if (isApiKeyAuth(auth)) {
if (typeof auth.apiKey === 'object') {
headers.authorization = 'ApiKey ' + Buffer.from(`${auth.apiKey.id}:${auth.apiKey.api_key}`).toString('base64');
}
else {
headers.authorization = `ApiKey ${auth.apiKey}`;
}
}
else if (isBearerAuth(auth)) {
headers.authorization = `Bearer ${auth.bearer}`;
}
else if (auth.username != null && auth.password != null) {
headers.authorization = 'Basic ' + Buffer.from(`${auth.username}:${auth.password}`).toString('base64');
}
}
return headers;
}
exports.prepareHeaders = prepareHeaders;
function isApiKeyAuth(auth) {
return auth.apiKey != null;
}
function isBearerAuth(auth) {
return auth.bearer != null;
}
function getIssuerCertificate(socket) {
let certificate = socket.getPeerCertificate(true);
while (certificate !== null && Object.keys(certificate).length > 0) {
// invalid certificate
if (certificate.issuerCertificate == null) {
return null;
}
// We have reached the root certificate.
// In case of self-signed certificates, `issuerCertificate` may be a circular reference.
if (certificate.fingerprint256 === certificate.issuerCertificate.fingerprint256) {
break;
}
// continue the loop
certificate = certificate.issuerCertificate;
}
return certificate;
}
exports.getIssuerCertificate = getIssuerCertificate;
//# sourceMappingURL=BaseConnection.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"BaseConnection.js","sourceRoot":"","sources":["../../src/connection/BaseConnection.ts"],"names":[],"mappings":"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"}

View file

@ -0,0 +1,15 @@
/// <reference types="node" />
/// <reference types="node" />
import hpagent from 'hpagent';
import http from 'http';
import https from 'https';
import BaseConnection, { ConnectionOptions, ConnectionRequestParams, ConnectionRequestOptions, ConnectionRequestOptionsAsStream, ConnectionRequestResponse, ConnectionRequestResponseAsStream } from './BaseConnection';
export default class HttpConnection extends BaseConnection {
agent?: http.Agent | https.Agent | hpagent.HttpProxyAgent | hpagent.HttpsProxyAgent;
makeRequest: typeof http.request | typeof https.request;
constructor(opts: ConnectionOptions);
request(params: ConnectionRequestParams, options: ConnectionRequestOptions): Promise<ConnectionRequestResponse>;
request(params: ConnectionRequestParams, options: ConnectionRequestOptionsAsStream): Promise<ConnectionRequestResponseAsStream>;
close(): Promise<void>;
buildRequestObject(params: ConnectionRequestParams, options: ConnectionRequestOptions): http.ClientRequestArgs;
}

View file

@ -0,0 +1,375 @@
"use strict";
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
/* eslint-disable @typescript-eslint/restrict-template-expressions */
const hpagent_1 = tslib_1.__importDefault(require("hpagent"));
const http_1 = tslib_1.__importDefault(require("http"));
const https_1 = tslib_1.__importDefault(require("https"));
const debug_1 = tslib_1.__importDefault(require("debug"));
const buffer_1 = tslib_1.__importDefault(require("buffer"));
const BaseConnection_1 = tslib_1.__importStar(require("./BaseConnection"));
const symbols_1 = require("../symbols");
const stream_1 = require("stream");
const errors_1 = require("../errors");
const debug = (0, debug_1.default)('elasticsearch');
const INVALID_PATH_REGEX = /[^\u0021-\u00ff]/;
const MAX_BUFFER_LENGTH = buffer_1.default.constants.MAX_LENGTH;
const MAX_STRING_LENGTH = buffer_1.default.constants.MAX_STRING_LENGTH;
class HttpConnection extends BaseConnection_1.default {
constructor(opts) {
super(opts);
Object.defineProperty(this, "agent", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "makeRequest", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
if (typeof opts.agent === 'function') {
this.agent = opts.agent(opts);
}
else if (typeof opts.agent === 'boolean') {
this.agent = undefined;
}
else {
if (opts.agent != null && !isHttpAgentOptions(opts.agent)) {
throw new errors_1.ConfigurationError('Bad agent configuration for Http agent');
}
const agentOptions = Object.assign({}, {
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 256,
maxFreeSockets: 256,
scheduling: 'lifo'
}, opts.agent);
if (opts.proxy != null) {
const proxyAgentOptions = {
...agentOptions,
proxy: opts.proxy
};
this.agent = this.url.protocol === 'http:'
? new hpagent_1.default.HttpProxyAgent(proxyAgentOptions)
: new hpagent_1.default.HttpsProxyAgent(Object.assign({}, proxyAgentOptions, this.tls));
}
else {
this.agent = this.url.protocol === 'http:'
? new http_1.default.Agent(agentOptions)
: new https_1.default.Agent(Object.assign({}, agentOptions, this.tls));
}
}
this.makeRequest = this.url.protocol === 'http:'
? http_1.default.request
: https_1.default.request;
}
async request(params, options) {
return await new Promise((resolve, reject) => {
var _a, _b;
let cleanedListeners = false;
const maxResponseSize = (_a = options.maxResponseSize) !== null && _a !== void 0 ? _a : MAX_STRING_LENGTH;
const maxCompressedResponseSize = (_b = options.maxCompressedResponseSize) !== null && _b !== void 0 ? _b : MAX_BUFFER_LENGTH;
const requestParams = this.buildRequestObject(params, options);
// https://github.com/nodejs/node/commit/b961d9fd83
if (INVALID_PATH_REGEX.test(requestParams.path)) {
return reject(new TypeError(`ERR_UNESCAPED_CHARACTERS: ${requestParams.path}`));
}
debug('Starting a new request', params);
let request;
try {
request = this.makeRequest(requestParams);
}
catch (err) {
return reject(err);
}
const abortListener = () => {
request.abort();
};
this._openRequests++;
if (options.signal != null) {
options.signal.addEventListener('abort', abortListener, { once: true });
}
const onResponse = (response) => {
var _a, _b;
cleanListeners();
this._openRequests--;
if (options.asStream === true) {
return resolve({
body: response,
statusCode: response.statusCode,
headers: response.headers
});
}
const contentEncoding = ((_a = response.headers['content-encoding']) !== null && _a !== void 0 ? _a : '').toLowerCase();
const isCompressed = contentEncoding.includes('gzip') || contentEncoding.includes('deflate');
const isVectorTile = ((_b = response.headers['content-type']) !== null && _b !== void 0 ? _b : '').includes('application/vnd.mapbox-vector-tile');
/* istanbul ignore else */
if (response.headers['content-length'] !== undefined) {
const contentLength = Number(response.headers['content-length']);
if (isCompressed && contentLength > maxCompressedResponseSize) {
response.destroy();
return reject(new errors_1.RequestAbortedError(`The content length (${contentLength}) is bigger than the maximum allowed buffer (${maxCompressedResponseSize})`));
}
else if (contentLength > maxResponseSize) {
response.destroy();
return reject(new errors_1.RequestAbortedError(`The content length (${contentLength}) is bigger than the maximum allowed string (${maxResponseSize})`));
}
}
// if the response is compressed, we must handle it
// as buffer for allowing decompression later
let payload = isCompressed || isVectorTile ? new Array() : '';
const onData = isCompressed || isVectorTile ? onDataAsBuffer : onDataAsString;
let currentLength = 0;
function onDataAsBuffer(chunk) {
currentLength += Buffer.byteLength(chunk);
if (currentLength > maxCompressedResponseSize) {
// TODO: hacky solution, refactor to avoid using the deprecated aborted event
response.removeListener('aborted', onAbort);
response.destroy();
onEnd(new errors_1.RequestAbortedError(`The content length (${currentLength}) is bigger than the maximum allowed buffer (${maxCompressedResponseSize})`));
}
else {
payload.push(chunk);
}
}
function onDataAsString(chunk) {
currentLength += Buffer.byteLength(chunk);
if (currentLength > maxResponseSize) {
// TODO: hacky solution, refactor to avoid using the deprecated aborted event
response.removeListener('aborted', onAbort);
response.destroy();
onEnd(new errors_1.RequestAbortedError(`The content length (${currentLength}) is bigger than the maximum allowed string (${maxResponseSize})`));
}
else {
payload = `${payload}${chunk}`;
}
}
const onEnd = (err) => {
response.removeListener('data', onData);
response.removeListener('end', onEnd);
response.removeListener('error', onEnd);
response.removeListener('aborted', onAbort);
if (err != null) {
if (err.name === 'RequestAbortedError') {
return reject(err);
}
return reject(new errors_1.ConnectionError(err.message));
}
resolve({
body: isCompressed || isVectorTile ? Buffer.concat(payload) : payload,
statusCode: response.statusCode,
headers: response.headers
});
};
const onAbort = () => {
response.destroy();
onEnd(new Error('Response aborted while reading the body'));
};
if (!isCompressed && !isVectorTile) {
response.setEncoding('utf8');
}
this.diagnostic.emit('deserialization', null, options);
response.on('data', onData);
response.on('error', onEnd);
response.on('end', onEnd);
response.on('aborted', onAbort);
};
const onTimeout = () => {
cleanListeners();
this._openRequests--;
request.once('error', () => { }); // we need to catch the request aborted error
request.abort();
reject(new errors_1.TimeoutError('Request timed out'));
};
const onError = (err) => {
var _a, _b, _c, _d, _e, _f, _g, _h;
cleanListeners();
this._openRequests--;
let message = err.message;
// @ts-expect-error
if (err.code === 'ECONNRESET') {
message += ` - Local: ${(_b = (_a = request.socket) === null || _a === void 0 ? void 0 : _a.localAddress) !== null && _b !== void 0 ? _b : 'unknown'}:${(_d = (_c = request.socket) === null || _c === void 0 ? void 0 : _c.localPort) !== null && _d !== void 0 ? _d : 'unknown'}, Remote: ${(_f = (_e = request.socket) === null || _e === void 0 ? void 0 : _e.remoteAddress) !== null && _f !== void 0 ? _f : 'unknown'}:${(_h = (_g = request.socket) === null || _g === void 0 ? void 0 : _g.remotePort) !== null && _h !== void 0 ? _h : 'unknown'}`;
}
reject(new errors_1.ConnectionError(message));
};
const onAbort = () => {
cleanListeners();
request.once('error', () => { }); // we need to catch the request aborted error
debug('Request aborted', params);
this._openRequests--;
reject(new errors_1.RequestAbortedError('Request aborted'));
};
const onSocket = (socket) => {
/* istanbul ignore else */
if (!socket.isSessionReused()) {
socket.once('secureConnect', () => {
const issuerCertificate = (0, BaseConnection_1.getIssuerCertificate)(socket);
/* istanbul ignore next */
if (issuerCertificate == null) {
onError(new Error('Invalid or malformed certificate'));
request.once('error', () => { }); // we need to catch the request aborted error
return request.abort();
}
// Check if fingerprint matches
/* istanbul ignore else */
if (this[symbols_1.kCaFingerprint] !== issuerCertificate.fingerprint256) {
onError(new Error('Server certificate CA fingerprint does not match the value configured in caFingerprint'));
request.once('error', () => { }); // we need to catch the request aborted error
return request.abort();
}
});
}
};
request.on('response', onResponse);
request.on('timeout', onTimeout);
request.on('error', onError);
request.on('abort', onAbort);
if (this[symbols_1.kCaFingerprint] != null && requestParams.protocol === 'https:') {
request.on('socket', onSocket);
}
// Disables the Nagle algorithm
request.setNoDelay(true);
// starts the request
if (isStream(params.body)) {
(0, stream_1.pipeline)(params.body, request, err => {
/* istanbul ignore if */
if (err != null && !cleanedListeners) {
cleanListeners();
this._openRequests--;
reject(err);
}
});
}
else {
request.end(params.body);
}
return request;
function cleanListeners() {
request.removeListener('response', onResponse);
request.removeListener('timeout', onTimeout);
request.removeListener('error', onError);
request.removeListener('abort', onAbort);
request.removeListener('socket', onSocket);
if (options.signal != null) {
if ('removeEventListener' in options.signal) {
options.signal.removeEventListener('abort', abortListener);
}
else {
options.signal.removeListener('abort', abortListener);
}
}
cleanedListeners = true;
}
});
}
async close() {
debug('Closing connection', this.id);
while (this._openRequests > 0) {
await sleep(1000);
}
/* istanbul ignore else */
if (this.agent !== undefined) {
this.agent.destroy();
}
}
buildRequestObject(params, options) {
var _a;
const url = this.url;
let search = url.search;
let pathname = url.pathname;
const request = {
protocol: url.protocol,
hostname: url.hostname[0] === '['
? url.hostname.slice(1, -1)
: url.hostname,
path: '',
// https://github.com/elastic/elasticsearch-js/issues/843
port: url.port !== '' ? url.port : undefined,
headers: this.headers,
agent: this.agent,
timeout: (_a = options.timeout) !== null && _a !== void 0 ? _a : this.timeout
};
const paramsKeys = Object.keys(params);
for (let i = 0, len = paramsKeys.length; i < len; i++) {
const key = paramsKeys[i];
if (key === 'path') {
pathname = resolve(pathname, params[key]);
}
else if (key === 'querystring' && Boolean(params[key])) {
if (search === '') {
search = `?${params[key]}`;
}
else {
search += `&${params[key]}`;
}
}
else if (key === 'headers') {
request.headers = Object.assign({}, request.headers, params.headers);
}
else {
// @ts-expect-error
request[key] = params[key];
}
}
request.path = pathname + search;
return request;
}
}
exports.default = HttpConnection;
function isStream(obj) {
return obj != null && typeof obj.pipe === 'function';
}
function resolve(host, path) {
const hostEndWithSlash = host[host.length - 1] === '/';
const pathStartsWithSlash = path[0] === '/';
if (hostEndWithSlash && pathStartsWithSlash) {
return host + path.slice(1);
}
else if (hostEndWithSlash !== pathStartsWithSlash) {
return host + path;
}
else {
return host + '/' + path;
}
}
/* istanbul ignore next */
function isHttpAgentOptions(opts) {
if (opts.keepAliveTimeout != null)
return false;
if (opts.keepAliveMaxTimeout != null)
return false;
if (opts.keepAliveTimeoutThreshold != null)
return false;
if (opts.pipelining != null)
return false;
if (opts.maxHeaderSize != null)
return false;
if (opts.connections != null)
return false;
return true;
}
async function sleep(ms) {
return await new Promise((resolve) => setTimeout(resolve, ms));
}
//# sourceMappingURL=HttpConnection.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,13 @@
/// <reference types="node" />
import { EventEmitter } from 'events';
import BaseConnection, { ConnectionOptions, ConnectionRequestParams, ConnectionRequestOptions, ConnectionRequestOptionsAsStream, ConnectionRequestResponse, ConnectionRequestResponseAsStream } from './BaseConnection';
import { Pool } from 'undici';
import { kEmitter } from '../symbols';
export default class Connection extends BaseConnection {
pool: Pool;
[kEmitter]: EventEmitter;
constructor(opts: ConnectionOptions);
request(params: ConnectionRequestParams, options: ConnectionRequestOptions): Promise<ConnectionRequestResponse>;
request(params: ConnectionRequestParams, options: ConnectionRequestOptionsAsStream): Promise<ConnectionRequestResponseAsStream>;
close(): Promise<void>;
}

View file

@ -0,0 +1,255 @@
"use strict";
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
/* eslint-disable @typescript-eslint/restrict-template-expressions */
const events_1 = require("events");
const debug_1 = tslib_1.__importDefault(require("debug"));
const buffer_1 = tslib_1.__importDefault(require("buffer"));
const BaseConnection_1 = tslib_1.__importStar(require("./BaseConnection"));
const undici_1 = require("undici");
const errors_1 = require("../errors");
const symbols_1 = require("../symbols");
const debug = (0, debug_1.default)('elasticsearch');
const INVALID_PATH_REGEX = /[^\u0021-\u00ff]/;
const MAX_BUFFER_LENGTH = buffer_1.default.constants.MAX_LENGTH;
const MAX_STRING_LENGTH = buffer_1.default.constants.MAX_STRING_LENGTH;
class Connection extends BaseConnection_1.default {
constructor(opts) {
var _b;
super(opts);
Object.defineProperty(this, "pool", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, _a, {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
if (opts.proxy != null) {
throw new errors_1.ConfigurationError('Undici connection can\'t work with proxies');
}
if (typeof opts.agent === 'function' || typeof opts.agent === 'boolean') {
throw new errors_1.ConfigurationError('Undici connection agent options can\'t be a function or a boolean');
}
if (opts.agent != null && !isUndiciAgentOptions(opts.agent)) {
throw new errors_1.ConfigurationError('Bad agent configuration for Undici agent');
}
this[symbols_1.kEmitter] = new events_1.EventEmitter();
const undiciOptions = {
keepAliveTimeout: 600e3,
keepAliveMaxTimeout: 600e3,
keepAliveTimeoutThreshold: 1000,
pipelining: 1,
maxHeaderSize: 16384,
connections: 256,
headersTimeout: this.timeout,
bodyTimeout: this.timeout,
...opts.agent
};
if (this[symbols_1.kCaFingerprint] !== null) {
const caFingerprint = this[symbols_1.kCaFingerprint];
const connector = (0, undici_1.buildConnector)(((_b = this.tls) !== null && _b !== void 0 ? _b : {}));
undiciOptions.connect = function (opts, cb) {
connector(opts, (err, socket) => {
if (err != null) {
return cb(err, null);
}
if (caFingerprint !== null && isTlsSocket(opts, socket)) {
const issuerCertificate = (0, BaseConnection_1.getIssuerCertificate)(socket);
/* istanbul ignore next */
if (issuerCertificate == null) {
socket.destroy();
return cb(new Error('Invalid or malformed certificate'), null);
}
// Check if fingerprint matches
/* istanbul ignore else */
if (caFingerprint !== issuerCertificate.fingerprint256) {
socket.destroy();
return cb(new Error('Server certificate CA fingerprint does not match the value configured in caFingerprint'), null);
}
}
return cb(null, socket);
});
};
}
else if (this.tls !== null) {
undiciOptions.connect = this.tls;
}
this.pool = new undici_1.Pool(this.url.toString(), undiciOptions);
}
async request(params, options) {
var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
const maxResponseSize = (_b = options.maxResponseSize) !== null && _b !== void 0 ? _b : MAX_STRING_LENGTH;
const maxCompressedResponseSize = (_c = options.maxCompressedResponseSize) !== null && _c !== void 0 ? _c : MAX_BUFFER_LENGTH;
const requestParams = {
method: params.method,
path: params.path + (params.querystring == null || params.querystring === '' ? '' : `?${params.querystring}`),
headers: Object.assign({}, this.headers, params.headers),
body: params.body,
signal: (_d = options.signal) !== null && _d !== void 0 ? _d : this[symbols_1.kEmitter]
};
if (requestParams.path[0] !== '/') {
requestParams.path = `/${requestParams.path}`;
}
// undici does not support per-request timeouts,
// to address this issue, we default to the constructor
// timeout (which is handled by undici) and create a local
// setTimeout callback if the request-specific timeout
// is different from the constructor timeout.
let timedout = false;
let timeoutId;
if (options.timeout != null && options.timeout !== this.timeout) {
timeoutId = setTimeout(() => {
timedout = true;
if (options.signal != null) {
options.signal.dispatchEvent('abort');
}
else {
this[symbols_1.kEmitter].emit('abort');
}
}, options.timeout);
}
// https://github.com/nodejs/node/commit/b961d9fd83
if (INVALID_PATH_REGEX.test(requestParams.path)) {
throw new TypeError(`ERR_UNESCAPED_CHARACTERS: ${requestParams.path}`);
}
debug('Starting a new request', params);
let response;
try {
// @ts-expect-error method it's fine as string
response = (await this.pool.request(requestParams));
if (timeoutId != null)
clearTimeout(timeoutId);
}
catch (err) {
if (timeoutId != null)
clearTimeout(timeoutId);
switch (err.code) {
case 'UND_ERR_ABORTED':
throw (timedout ? new errors_1.TimeoutError('Request timed out') : new errors_1.RequestAbortedError('Request aborted'));
case 'UND_ERR_HEADERS_TIMEOUT':
throw new errors_1.TimeoutError('Request timed out');
case 'UND_ERR_SOCKET':
throw new errors_1.ConnectionError(`${err.message} - Local: ${(_f = (_e = err.socket) === null || _e === void 0 ? void 0 : _e.localAddress) !== null && _f !== void 0 ? _f : 'unknown'}:${(_h = (_g = err.socket) === null || _g === void 0 ? void 0 : _g.localPort) !== null && _h !== void 0 ? _h : 'unknown'}, Remote: ${(_k = (_j = err.socket) === null || _j === void 0 ? void 0 : _j.remoteAddress) !== null && _k !== void 0 ? _k : 'unknown'}:${(_m = (_l = err.socket) === null || _l === void 0 ? void 0 : _l.remotePort) !== null && _m !== void 0 ? _m : 'unknown'}`); // eslint-disable-line
default:
throw new errors_1.ConnectionError(err.message);
}
}
if (options.asStream === true) {
return {
statusCode: response.statusCode,
headers: response.headers,
body: response.body
};
}
// @ts-expect-error Assume header is not string[] for now.
const contentEncoding = ((_o = response.headers['content-encoding']) !== null && _o !== void 0 ? _o : '').toLowerCase();
const isCompressed = contentEncoding.includes('gzip') || contentEncoding.includes('deflate'); // eslint-disable-line
const isVectorTile = ((_p = response.headers['content-type']) !== null && _p !== void 0 ? _p : '').includes('application/vnd.mapbox-vector-tile');
/* istanbul ignore else */
if (response.headers['content-length'] !== undefined) {
const contentLength = Number(response.headers['content-length']);
if (isCompressed && contentLength > maxCompressedResponseSize) { // eslint-disable-line
response.body.destroy();
throw new errors_1.RequestAbortedError(`The content length (${contentLength}) is bigger than the maximum allowed buffer (${maxCompressedResponseSize})`);
}
else if (contentLength > maxResponseSize) {
response.body.destroy();
throw new errors_1.RequestAbortedError(`The content length (${contentLength}) is bigger than the maximum allowed string (${maxResponseSize})`);
}
}
this.diagnostic.emit('deserialization', null, options);
try {
if (isCompressed || isVectorTile) { // eslint-disable-line
let currentLength = 0;
const payload = [];
for await (const chunk of response.body) {
currentLength += Buffer.byteLength(chunk);
if (currentLength > maxCompressedResponseSize) {
response.body.destroy();
throw new errors_1.RequestAbortedError(`The content length (${currentLength}) is bigger than the maximum allowed buffer (${maxCompressedResponseSize})`);
}
payload.push(chunk);
}
return {
statusCode: response.statusCode,
headers: response.headers,
body: Buffer.concat(payload)
};
}
else {
let payload = '';
let currentLength = 0;
response.body.setEncoding('utf8');
for await (const chunk of response.body) {
currentLength += Buffer.byteLength(chunk);
if (currentLength > maxResponseSize) {
response.body.destroy();
throw new errors_1.RequestAbortedError(`The content length (${currentLength}) is bigger than the maximum allowed string (${maxResponseSize})`);
}
payload += chunk;
}
return {
statusCode: response.statusCode,
headers: response.headers,
body: payload
};
}
}
catch (err) {
if (err.name === 'RequestAbortedError') {
throw err;
}
throw new errors_1.ConnectionError(err.message);
}
}
async close() {
debug('Closing connection', this.id);
await this.pool.close();
}
}
exports.default = Connection;
_a = symbols_1.kEmitter;
/* istanbul ignore next */
function isUndiciAgentOptions(opts) {
if (opts.keepAlive != null)
return false;
if (opts.keepAliveMsecs != null)
return false;
if (opts.maxSockets != null)
return false;
if (opts.maxFreeSockets != null)
return false;
if (opts.scheduling != null)
return false;
if (opts.proxy != null)
return false;
return true;
}
function isTlsSocket(opts, socket) {
return socket !== null && opts.protocol === 'https:';
}
//# sourceMappingURL=UndiciConnection.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,6 @@
import BaseConnection from './BaseConnection';
import HttpConnection from './HttpConnection';
import UndiciConnection from './UndiciConnection';
export type Connection = BaseConnection | HttpConnection | UndiciConnection;
export type { ConnectionOptions, ConnectionRequestParams, ConnectionRequestOptions, ConnectionRequestOptionsAsStream, ConnectionRequestResponse, ConnectionRequestResponseAsStream } from './BaseConnection';
export { BaseConnection, HttpConnection, UndiciConnection };

View file

@ -0,0 +1,29 @@
"use strict";
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the 'License'); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.UndiciConnection = exports.HttpConnection = exports.BaseConnection = void 0;
const tslib_1 = require("tslib");
const BaseConnection_1 = tslib_1.__importDefault(require("./BaseConnection"));
exports.BaseConnection = BaseConnection_1.default;
const HttpConnection_1 = tslib_1.__importDefault(require("./HttpConnection"));
exports.HttpConnection = HttpConnection_1.default;
const UndiciConnection_1 = tslib_1.__importDefault(require("./UndiciConnection"));
exports.UndiciConnection = UndiciConnection_1.default;
//# sourceMappingURL=index.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/connection/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;;AAEH,8EAA6C;AAe3C,yBAfK,wBAAc,CAeL;AAdhB,8EAA6C;AAe3C,yBAfK,wBAAc,CAeL;AAdhB,kFAAiD;AAe/C,2BAfK,0BAAgB,CAeL"}