Add extracted source directory and README navigation

This commit is contained in:
Shawn Bot
2026-03-31 14:56:06 +00:00
parent 6252bb6eb5
commit 91e01d755b
4757 changed files with 984951 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
"use strict";
/*
* Copyright 2021 gRPC authors.
*
* Licensed 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.registerAdminService = registerAdminService;
exports.addAdminServicesToServer = addAdminServicesToServer;
const registeredAdminServices = [];
function registerAdminService(getServiceDefinition, getHandlers) {
registeredAdminServices.push({ getServiceDefinition, getHandlers });
}
function addAdminServicesToServer(server) {
for (const { getServiceDefinition, getHandlers } of registeredAdminServices) {
server.addService(getServiceDefinition(), getHandlers());
}
}
//# sourceMappingURL=admin.js.map

View File

@@ -0,0 +1,191 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.BackoffTimeout = void 0;
const constants_1 = require("./constants");
const logging = require("./logging");
const TRACER_NAME = 'backoff';
const INITIAL_BACKOFF_MS = 1000;
const BACKOFF_MULTIPLIER = 1.6;
const MAX_BACKOFF_MS = 120000;
const BACKOFF_JITTER = 0.2;
/**
* Get a number uniformly at random in the range [min, max)
* @param min
* @param max
*/
function uniformRandom(min, max) {
return Math.random() * (max - min) + min;
}
class BackoffTimeout {
constructor(callback, options) {
this.callback = callback;
/**
* The delay time at the start, and after each reset.
*/
this.initialDelay = INITIAL_BACKOFF_MS;
/**
* The exponential backoff multiplier.
*/
this.multiplier = BACKOFF_MULTIPLIER;
/**
* The maximum delay time
*/
this.maxDelay = MAX_BACKOFF_MS;
/**
* The maximum fraction by which the delay time can randomly vary after
* applying the multiplier.
*/
this.jitter = BACKOFF_JITTER;
/**
* Indicates whether the timer is currently running.
*/
this.running = false;
/**
* Indicates whether the timer should keep the Node process running if no
* other async operation is doing so.
*/
this.hasRef = true;
/**
* The time that the currently running timer was started. Only valid if
* running is true.
*/
this.startTime = new Date();
/**
* The approximate time that the currently running timer will end. Only valid
* if running is true.
*/
this.endTime = new Date();
this.id = BackoffTimeout.getNextId();
if (options) {
if (options.initialDelay) {
this.initialDelay = options.initialDelay;
}
if (options.multiplier) {
this.multiplier = options.multiplier;
}
if (options.jitter) {
this.jitter = options.jitter;
}
if (options.maxDelay) {
this.maxDelay = options.maxDelay;
}
}
this.trace('constructed initialDelay=' + this.initialDelay + ' multiplier=' + this.multiplier + ' jitter=' + this.jitter + ' maxDelay=' + this.maxDelay);
this.nextDelay = this.initialDelay;
this.timerId = setTimeout(() => { }, 0);
clearTimeout(this.timerId);
}
static getNextId() {
return this.nextId++;
}
trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, '{' + this.id + '} ' + text);
}
runTimer(delay) {
var _a, _b;
this.trace('runTimer(delay=' + delay + ')');
this.endTime = this.startTime;
this.endTime.setMilliseconds(this.endTime.getMilliseconds() + delay);
clearTimeout(this.timerId);
this.timerId = setTimeout(() => {
this.trace('timer fired');
this.running = false;
this.callback();
}, delay);
if (!this.hasRef) {
(_b = (_a = this.timerId).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
}
}
/**
* Call the callback after the current amount of delay time
*/
runOnce() {
this.trace('runOnce()');
this.running = true;
this.startTime = new Date();
this.runTimer(this.nextDelay);
const nextBackoff = Math.min(this.nextDelay * this.multiplier, this.maxDelay);
const jitterMagnitude = nextBackoff * this.jitter;
this.nextDelay =
nextBackoff + uniformRandom(-jitterMagnitude, jitterMagnitude);
}
/**
* Stop the timer. The callback will not be called until `runOnce` is called
* again.
*/
stop() {
this.trace('stop()');
clearTimeout(this.timerId);
this.running = false;
}
/**
* Reset the delay time to its initial value. If the timer is still running,
* retroactively apply that reset to the current timer.
*/
reset() {
this.trace('reset() running=' + this.running);
this.nextDelay = this.initialDelay;
if (this.running) {
const now = new Date();
const newEndTime = this.startTime;
newEndTime.setMilliseconds(newEndTime.getMilliseconds() + this.nextDelay);
clearTimeout(this.timerId);
if (now < newEndTime) {
this.runTimer(newEndTime.getTime() - now.getTime());
}
else {
this.running = false;
}
}
}
/**
* Check whether the timer is currently running.
*/
isRunning() {
return this.running;
}
/**
* Set that while the timer is running, it should keep the Node process
* running.
*/
ref() {
var _a, _b;
this.hasRef = true;
(_b = (_a = this.timerId).ref) === null || _b === void 0 ? void 0 : _b.call(_a);
}
/**
* Set that while the timer is running, it should not keep the Node process
* running.
*/
unref() {
var _a, _b;
this.hasRef = false;
(_b = (_a = this.timerId).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
}
/**
* Get the approximate timestamp of when the timer will fire. Only valid if
* this.isRunning() is true.
*/
getEndTime() {
return this.endTime;
}
}
exports.BackoffTimeout = BackoffTimeout;
BackoffTimeout.nextId = 0;
//# sourceMappingURL=backoff-timeout.js.map

View File

@@ -0,0 +1,153 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.CallCredentials = void 0;
const metadata_1 = require("./metadata");
function isCurrentOauth2Client(client) {
return ('getRequestHeaders' in client &&
typeof client.getRequestHeaders === 'function');
}
/**
* A class that represents a generic method of adding authentication-related
* metadata on a per-request basis.
*/
class CallCredentials {
/**
* Creates a new CallCredentials object from a given function that generates
* Metadata objects.
* @param metadataGenerator A function that accepts a set of options, and
* generates a Metadata object based on these options, which is passed back
* to the caller via a supplied (err, metadata) callback.
*/
static createFromMetadataGenerator(metadataGenerator) {
return new SingleCallCredentials(metadataGenerator);
}
/**
* Create a gRPC credential from a Google credential object.
* @param googleCredentials The authentication client to use.
* @return The resulting CallCredentials object.
*/
static createFromGoogleCredential(googleCredentials) {
return CallCredentials.createFromMetadataGenerator((options, callback) => {
let getHeaders;
if (isCurrentOauth2Client(googleCredentials)) {
getHeaders = googleCredentials.getRequestHeaders(options.service_url);
}
else {
getHeaders = new Promise((resolve, reject) => {
googleCredentials.getRequestMetadata(options.service_url, (err, headers) => {
if (err) {
reject(err);
return;
}
if (!headers) {
reject(new Error('Headers not set by metadata plugin'));
return;
}
resolve(headers);
});
});
}
getHeaders.then(headers => {
const metadata = new metadata_1.Metadata();
for (const key of Object.keys(headers)) {
metadata.add(key, headers[key]);
}
callback(null, metadata);
}, err => {
callback(err);
});
});
}
static createEmpty() {
return new EmptyCallCredentials();
}
}
exports.CallCredentials = CallCredentials;
class ComposedCallCredentials extends CallCredentials {
constructor(creds) {
super();
this.creds = creds;
}
async generateMetadata(options) {
const base = new metadata_1.Metadata();
const generated = await Promise.all(this.creds.map(cred => cred.generateMetadata(options)));
for (const gen of generated) {
base.merge(gen);
}
return base;
}
compose(other) {
return new ComposedCallCredentials(this.creds.concat([other]));
}
_equals(other) {
if (this === other) {
return true;
}
if (other instanceof ComposedCallCredentials) {
return this.creds.every((value, index) => value._equals(other.creds[index]));
}
else {
return false;
}
}
}
class SingleCallCredentials extends CallCredentials {
constructor(metadataGenerator) {
super();
this.metadataGenerator = metadataGenerator;
}
generateMetadata(options) {
return new Promise((resolve, reject) => {
this.metadataGenerator(options, (err, metadata) => {
if (metadata !== undefined) {
resolve(metadata);
}
else {
reject(err);
}
});
});
}
compose(other) {
return new ComposedCallCredentials([this, other]);
}
_equals(other) {
if (this === other) {
return true;
}
if (other instanceof SingleCallCredentials) {
return this.metadataGenerator === other.metadataGenerator;
}
else {
return false;
}
}
}
class EmptyCallCredentials extends CallCredentials {
generateMetadata(options) {
return Promise.resolve(new metadata_1.Metadata());
}
compose(other) {
return other;
}
_equals(other) {
return other instanceof EmptyCallCredentials;
}
}
//# sourceMappingURL=call-credentials.js.map

View File

@@ -0,0 +1,100 @@
"use strict";
/*
* Copyright 2022 gRPC authors.
*
* Licensed 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.InterceptingListenerImpl = void 0;
exports.statusOrFromValue = statusOrFromValue;
exports.statusOrFromError = statusOrFromError;
exports.isInterceptingListener = isInterceptingListener;
const metadata_1 = require("./metadata");
function statusOrFromValue(value) {
return {
ok: true,
value: value
};
}
function statusOrFromError(error) {
var _a;
return {
ok: false,
error: Object.assign(Object.assign({}, error), { metadata: (_a = error.metadata) !== null && _a !== void 0 ? _a : new metadata_1.Metadata() })
};
}
function isInterceptingListener(listener) {
return (listener.onReceiveMetadata !== undefined &&
listener.onReceiveMetadata.length === 1);
}
class InterceptingListenerImpl {
constructor(listener, nextListener) {
this.listener = listener;
this.nextListener = nextListener;
this.processingMetadata = false;
this.hasPendingMessage = false;
this.processingMessage = false;
this.pendingStatus = null;
}
processPendingMessage() {
if (this.hasPendingMessage) {
this.nextListener.onReceiveMessage(this.pendingMessage);
this.pendingMessage = null;
this.hasPendingMessage = false;
}
}
processPendingStatus() {
if (this.pendingStatus) {
this.nextListener.onReceiveStatus(this.pendingStatus);
}
}
onReceiveMetadata(metadata) {
this.processingMetadata = true;
this.listener.onReceiveMetadata(metadata, metadata => {
this.processingMetadata = false;
this.nextListener.onReceiveMetadata(metadata);
this.processPendingMessage();
this.processPendingStatus();
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onReceiveMessage(message) {
/* If this listener processes messages asynchronously, the last message may
* be reordered with respect to the status */
this.processingMessage = true;
this.listener.onReceiveMessage(message, msg => {
this.processingMessage = false;
if (this.processingMetadata) {
this.pendingMessage = msg;
this.hasPendingMessage = true;
}
else {
this.nextListener.onReceiveMessage(msg);
this.processPendingStatus();
}
});
}
onReceiveStatus(status) {
this.listener.onReceiveStatus(status, processedStatus => {
if (this.processingMetadata || this.processingMessage) {
this.pendingStatus = processedStatus;
}
else {
this.nextListener.onReceiveStatus(processedStatus);
}
});
}
}
exports.InterceptingListenerImpl = InterceptingListenerImpl;
//# sourceMappingURL=call-interface.js.map

View File

@@ -0,0 +1,24 @@
"use strict";
/*
* Copyright 2022 gRPC authors.
*
* Licensed 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.getNextCallNumber = getNextCallNumber;
let nextCallNumber = 0;
function getNextCallNumber() {
return nextCallNumber++;
}
//# sourceMappingURL=call-number.js.map

View File

@@ -0,0 +1,152 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.ClientDuplexStreamImpl = exports.ClientWritableStreamImpl = exports.ClientReadableStreamImpl = exports.ClientUnaryCallImpl = void 0;
exports.callErrorFromStatus = callErrorFromStatus;
const events_1 = require("events");
const stream_1 = require("stream");
const constants_1 = require("./constants");
/**
* Construct a ServiceError from a StatusObject. This function exists primarily
* as an attempt to make the error stack trace clearly communicate that the
* error is not necessarily a problem in gRPC itself.
* @param status
*/
function callErrorFromStatus(status, callerStack) {
const message = `${status.code} ${constants_1.Status[status.code]}: ${status.details}`;
const error = new Error(message);
const stack = `${error.stack}\nfor call at\n${callerStack}`;
return Object.assign(new Error(message), status, { stack });
}
class ClientUnaryCallImpl extends events_1.EventEmitter {
constructor() {
super();
}
cancel() {
var _a;
(_a = this.call) === null || _a === void 0 ? void 0 : _a.cancelWithStatus(constants_1.Status.CANCELLED, 'Cancelled on client');
}
getPeer() {
var _a, _b;
return (_b = (_a = this.call) === null || _a === void 0 ? void 0 : _a.getPeer()) !== null && _b !== void 0 ? _b : 'unknown';
}
getAuthContext() {
var _a, _b;
return (_b = (_a = this.call) === null || _a === void 0 ? void 0 : _a.getAuthContext()) !== null && _b !== void 0 ? _b : null;
}
}
exports.ClientUnaryCallImpl = ClientUnaryCallImpl;
class ClientReadableStreamImpl extends stream_1.Readable {
constructor(deserialize) {
super({ objectMode: true });
this.deserialize = deserialize;
}
cancel() {
var _a;
(_a = this.call) === null || _a === void 0 ? void 0 : _a.cancelWithStatus(constants_1.Status.CANCELLED, 'Cancelled on client');
}
getPeer() {
var _a, _b;
return (_b = (_a = this.call) === null || _a === void 0 ? void 0 : _a.getPeer()) !== null && _b !== void 0 ? _b : 'unknown';
}
getAuthContext() {
var _a, _b;
return (_b = (_a = this.call) === null || _a === void 0 ? void 0 : _a.getAuthContext()) !== null && _b !== void 0 ? _b : null;
}
_read(_size) {
var _a;
(_a = this.call) === null || _a === void 0 ? void 0 : _a.startRead();
}
}
exports.ClientReadableStreamImpl = ClientReadableStreamImpl;
class ClientWritableStreamImpl extends stream_1.Writable {
constructor(serialize) {
super({ objectMode: true });
this.serialize = serialize;
}
cancel() {
var _a;
(_a = this.call) === null || _a === void 0 ? void 0 : _a.cancelWithStatus(constants_1.Status.CANCELLED, 'Cancelled on client');
}
getPeer() {
var _a, _b;
return (_b = (_a = this.call) === null || _a === void 0 ? void 0 : _a.getPeer()) !== null && _b !== void 0 ? _b : 'unknown';
}
getAuthContext() {
var _a, _b;
return (_b = (_a = this.call) === null || _a === void 0 ? void 0 : _a.getAuthContext()) !== null && _b !== void 0 ? _b : null;
}
_write(chunk, encoding, cb) {
var _a;
const context = {
callback: cb,
};
const flags = Number(encoding);
if (!Number.isNaN(flags)) {
context.flags = flags;
}
(_a = this.call) === null || _a === void 0 ? void 0 : _a.sendMessageWithContext(context, chunk);
}
_final(cb) {
var _a;
(_a = this.call) === null || _a === void 0 ? void 0 : _a.halfClose();
cb();
}
}
exports.ClientWritableStreamImpl = ClientWritableStreamImpl;
class ClientDuplexStreamImpl extends stream_1.Duplex {
constructor(serialize, deserialize) {
super({ objectMode: true });
this.serialize = serialize;
this.deserialize = deserialize;
}
cancel() {
var _a;
(_a = this.call) === null || _a === void 0 ? void 0 : _a.cancelWithStatus(constants_1.Status.CANCELLED, 'Cancelled on client');
}
getPeer() {
var _a, _b;
return (_b = (_a = this.call) === null || _a === void 0 ? void 0 : _a.getPeer()) !== null && _b !== void 0 ? _b : 'unknown';
}
getAuthContext() {
var _a, _b;
return (_b = (_a = this.call) === null || _a === void 0 ? void 0 : _a.getAuthContext()) !== null && _b !== void 0 ? _b : null;
}
_read(_size) {
var _a;
(_a = this.call) === null || _a === void 0 ? void 0 : _a.startRead();
}
_write(chunk, encoding, cb) {
var _a;
const context = {
callback: cb,
};
const flags = Number(encoding);
if (!Number.isNaN(flags)) {
context.flags = flags;
}
(_a = this.call) === null || _a === void 0 ? void 0 : _a.sendMessageWithContext(context, chunk);
}
_final(cb) {
var _a;
(_a = this.call) === null || _a === void 0 ? void 0 : _a.halfClose();
cb();
}
}
exports.ClientDuplexStreamImpl = ClientDuplexStreamImpl;
//# sourceMappingURL=call.js.map

View File

@@ -0,0 +1,141 @@
"use strict";
/*
* Copyright 2024 gRPC authors.
*
* Licensed 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.FileWatcherCertificateProvider = void 0;
const fs = require("fs");
const logging = require("./logging");
const constants_1 = require("./constants");
const util_1 = require("util");
const TRACER_NAME = 'certificate_provider';
function trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, text);
}
const readFilePromise = (0, util_1.promisify)(fs.readFile);
class FileWatcherCertificateProvider {
constructor(config) {
this.config = config;
this.refreshTimer = null;
this.fileResultPromise = null;
this.latestCaUpdate = undefined;
this.caListeners = new Set();
this.latestIdentityUpdate = undefined;
this.identityListeners = new Set();
this.lastUpdateTime = null;
if ((config.certificateFile === undefined) !== (config.privateKeyFile === undefined)) {
throw new Error('certificateFile and privateKeyFile must be set or unset together');
}
if (config.certificateFile === undefined && config.caCertificateFile === undefined) {
throw new Error('At least one of certificateFile and caCertificateFile must be set');
}
trace('File watcher constructed with config ' + JSON.stringify(config));
}
updateCertificates() {
if (this.fileResultPromise) {
return;
}
this.fileResultPromise = Promise.allSettled([
this.config.certificateFile ? readFilePromise(this.config.certificateFile) : Promise.reject(),
this.config.privateKeyFile ? readFilePromise(this.config.privateKeyFile) : Promise.reject(),
this.config.caCertificateFile ? readFilePromise(this.config.caCertificateFile) : Promise.reject()
]);
this.fileResultPromise.then(([certificateResult, privateKeyResult, caCertificateResult]) => {
if (!this.refreshTimer) {
return;
}
trace('File watcher read certificates certificate ' + certificateResult.status + ', privateKey ' + privateKeyResult.status + ', CA certificate ' + caCertificateResult.status);
this.lastUpdateTime = new Date();
this.fileResultPromise = null;
if (certificateResult.status === 'fulfilled' && privateKeyResult.status === 'fulfilled') {
this.latestIdentityUpdate = {
certificate: certificateResult.value,
privateKey: privateKeyResult.value
};
}
else {
this.latestIdentityUpdate = null;
}
if (caCertificateResult.status === 'fulfilled') {
this.latestCaUpdate = {
caCertificate: caCertificateResult.value
};
}
else {
this.latestCaUpdate = null;
}
for (const listener of this.identityListeners) {
listener(this.latestIdentityUpdate);
}
for (const listener of this.caListeners) {
listener(this.latestCaUpdate);
}
});
trace('File watcher initiated certificate update');
}
maybeStartWatchingFiles() {
if (!this.refreshTimer) {
/* Perform the first read immediately, but only if there was not already
* a recent read, to avoid reading from the filesystem significantly more
* frequently than configured if the provider quickly switches between
* used and unused. */
const timeSinceLastUpdate = this.lastUpdateTime ? (new Date()).getTime() - this.lastUpdateTime.getTime() : Infinity;
if (timeSinceLastUpdate > this.config.refreshIntervalMs) {
this.updateCertificates();
}
if (timeSinceLastUpdate > this.config.refreshIntervalMs * 2) {
// Clear out old updates if they are definitely stale
this.latestCaUpdate = undefined;
this.latestIdentityUpdate = undefined;
}
this.refreshTimer = setInterval(() => this.updateCertificates(), this.config.refreshIntervalMs);
trace('File watcher started watching');
}
}
maybeStopWatchingFiles() {
if (this.caListeners.size === 0 && this.identityListeners.size === 0) {
this.fileResultPromise = null;
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
this.refreshTimer = null;
}
}
}
addCaCertificateListener(listener) {
this.caListeners.add(listener);
this.maybeStartWatchingFiles();
if (this.latestCaUpdate !== undefined) {
process.nextTick(listener, this.latestCaUpdate);
}
}
removeCaCertificateListener(listener) {
this.caListeners.delete(listener);
this.maybeStopWatchingFiles();
}
addIdentityCertificateListener(listener) {
this.identityListeners.add(listener);
this.maybeStartWatchingFiles();
if (this.latestIdentityUpdate !== undefined) {
process.nextTick(listener, this.latestIdentityUpdate);
}
}
removeIdentityCertificateListener(listener) {
this.identityListeners.delete(listener);
this.maybeStopWatchingFiles();
}
}
exports.FileWatcherCertificateProvider = FileWatcherCertificateProvider;
//# sourceMappingURL=certificate-provider.js.map

View File

@@ -0,0 +1,430 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.ChannelCredentials = void 0;
exports.createCertificateProviderChannelCredentials = createCertificateProviderChannelCredentials;
const tls_1 = require("tls");
const call_credentials_1 = require("./call-credentials");
const tls_helpers_1 = require("./tls-helpers");
const uri_parser_1 = require("./uri-parser");
const resolver_1 = require("./resolver");
const logging_1 = require("./logging");
const constants_1 = require("./constants");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function verifyIsBufferOrNull(obj, friendlyName) {
if (obj && !(obj instanceof Buffer)) {
throw new TypeError(`${friendlyName}, if provided, must be a Buffer.`);
}
}
/**
* A class that contains credentials for communicating over a channel, as well
* as a set of per-call credentials, which are applied to every method call made
* over a channel initialized with an instance of this class.
*/
class ChannelCredentials {
/**
* Returns a copy of this object with the included set of per-call credentials
* expanded to include callCredentials.
* @param callCredentials A CallCredentials object to associate with this
* instance.
*/
compose(callCredentials) {
return new ComposedChannelCredentialsImpl(this, callCredentials);
}
/**
* Return a new ChannelCredentials instance with a given set of credentials.
* The resulting instance can be used to construct a Channel that communicates
* over TLS.
* @param rootCerts The root certificate data.
* @param privateKey The client certificate private key, if available.
* @param certChain The client certificate key chain, if available.
* @param verifyOptions Additional options to modify certificate verification
*/
static createSsl(rootCerts, privateKey, certChain, verifyOptions) {
var _a;
verifyIsBufferOrNull(rootCerts, 'Root certificate');
verifyIsBufferOrNull(privateKey, 'Private key');
verifyIsBufferOrNull(certChain, 'Certificate chain');
if (privateKey && !certChain) {
throw new Error('Private key must be given with accompanying certificate chain');
}
if (!privateKey && certChain) {
throw new Error('Certificate chain must be given with accompanying private key');
}
const secureContext = (0, tls_1.createSecureContext)({
ca: (_a = rootCerts !== null && rootCerts !== void 0 ? rootCerts : (0, tls_helpers_1.getDefaultRootsData)()) !== null && _a !== void 0 ? _a : undefined,
key: privateKey !== null && privateKey !== void 0 ? privateKey : undefined,
cert: certChain !== null && certChain !== void 0 ? certChain : undefined,
ciphers: tls_helpers_1.CIPHER_SUITES,
});
return new SecureChannelCredentialsImpl(secureContext, verifyOptions !== null && verifyOptions !== void 0 ? verifyOptions : {});
}
/**
* Return a new ChannelCredentials instance with credentials created using
* the provided secureContext. The resulting instances can be used to
* construct a Channel that communicates over TLS. gRPC will not override
* anything in the provided secureContext, so the environment variables
* GRPC_SSL_CIPHER_SUITES and GRPC_DEFAULT_SSL_ROOTS_FILE_PATH will
* not be applied.
* @param secureContext The return value of tls.createSecureContext()
* @param verifyOptions Additional options to modify certificate verification
*/
static createFromSecureContext(secureContext, verifyOptions) {
return new SecureChannelCredentialsImpl(secureContext, verifyOptions !== null && verifyOptions !== void 0 ? verifyOptions : {});
}
/**
* Return a new ChannelCredentials instance with no credentials.
*/
static createInsecure() {
return new InsecureChannelCredentialsImpl();
}
}
exports.ChannelCredentials = ChannelCredentials;
class InsecureChannelCredentialsImpl extends ChannelCredentials {
constructor() {
super();
}
compose(callCredentials) {
throw new Error('Cannot compose insecure credentials');
}
_isSecure() {
return false;
}
_equals(other) {
return other instanceof InsecureChannelCredentialsImpl;
}
_createSecureConnector(channelTarget, options, callCredentials) {
return {
connect(socket) {
return Promise.resolve({
socket,
secure: false
});
},
waitForReady: () => {
return Promise.resolve();
},
getCallCredentials: () => {
return callCredentials !== null && callCredentials !== void 0 ? callCredentials : call_credentials_1.CallCredentials.createEmpty();
},
destroy() { }
};
}
}
function getConnectionOptions(secureContext, verifyOptions, channelTarget, options) {
var _a, _b;
const connectionOptions = {
secureContext: secureContext
};
let realTarget = channelTarget;
if ('grpc.http_connect_target' in options) {
const parsedTarget = (0, uri_parser_1.parseUri)(options['grpc.http_connect_target']);
if (parsedTarget) {
realTarget = parsedTarget;
}
}
const targetPath = (0, resolver_1.getDefaultAuthority)(realTarget);
const hostPort = (0, uri_parser_1.splitHostPort)(targetPath);
const remoteHost = (_a = hostPort === null || hostPort === void 0 ? void 0 : hostPort.host) !== null && _a !== void 0 ? _a : targetPath;
connectionOptions.host = remoteHost;
if (verifyOptions.checkServerIdentity) {
connectionOptions.checkServerIdentity = verifyOptions.checkServerIdentity;
}
if (verifyOptions.rejectUnauthorized !== undefined) {
connectionOptions.rejectUnauthorized = verifyOptions.rejectUnauthorized;
}
connectionOptions.ALPNProtocols = ['h2'];
if (options['grpc.ssl_target_name_override']) {
const sslTargetNameOverride = options['grpc.ssl_target_name_override'];
const originalCheckServerIdentity = (_b = connectionOptions.checkServerIdentity) !== null && _b !== void 0 ? _b : tls_1.checkServerIdentity;
connectionOptions.checkServerIdentity = (host, cert) => {
return originalCheckServerIdentity(sslTargetNameOverride, cert);
};
connectionOptions.servername = sslTargetNameOverride;
}
else {
connectionOptions.servername = remoteHost;
}
if (options['grpc-node.tls_enable_trace']) {
connectionOptions.enableTrace = true;
}
return connectionOptions;
}
class SecureConnectorImpl {
constructor(connectionOptions, callCredentials) {
this.connectionOptions = connectionOptions;
this.callCredentials = callCredentials;
}
connect(socket) {
const tlsConnectOptions = Object.assign({ socket: socket }, this.connectionOptions);
return new Promise((resolve, reject) => {
const tlsSocket = (0, tls_1.connect)(tlsConnectOptions, () => {
var _a;
if (((_a = this.connectionOptions.rejectUnauthorized) !== null && _a !== void 0 ? _a : true) && !tlsSocket.authorized) {
reject(tlsSocket.authorizationError);
return;
}
resolve({
socket: tlsSocket,
secure: true
});
});
tlsSocket.on('error', (error) => {
reject(error);
});
});
}
waitForReady() {
return Promise.resolve();
}
getCallCredentials() {
return this.callCredentials;
}
destroy() { }
}
class SecureChannelCredentialsImpl extends ChannelCredentials {
constructor(secureContext, verifyOptions) {
super();
this.secureContext = secureContext;
this.verifyOptions = verifyOptions;
}
_isSecure() {
return true;
}
_equals(other) {
if (this === other) {
return true;
}
if (other instanceof SecureChannelCredentialsImpl) {
return (this.secureContext === other.secureContext &&
this.verifyOptions.checkServerIdentity ===
other.verifyOptions.checkServerIdentity);
}
else {
return false;
}
}
_createSecureConnector(channelTarget, options, callCredentials) {
const connectionOptions = getConnectionOptions(this.secureContext, this.verifyOptions, channelTarget, options);
return new SecureConnectorImpl(connectionOptions, callCredentials !== null && callCredentials !== void 0 ? callCredentials : call_credentials_1.CallCredentials.createEmpty());
}
}
class CertificateProviderChannelCredentialsImpl extends ChannelCredentials {
constructor(caCertificateProvider, identityCertificateProvider, verifyOptions) {
super();
this.caCertificateProvider = caCertificateProvider;
this.identityCertificateProvider = identityCertificateProvider;
this.verifyOptions = verifyOptions;
this.refcount = 0;
/**
* `undefined` means that the certificates have not yet been loaded. `null`
* means that an attempt to load them has completed, and has failed.
*/
this.latestCaUpdate = undefined;
/**
* `undefined` means that the certificates have not yet been loaded. `null`
* means that an attempt to load them has completed, and has failed.
*/
this.latestIdentityUpdate = undefined;
this.caCertificateUpdateListener = this.handleCaCertificateUpdate.bind(this);
this.identityCertificateUpdateListener = this.handleIdentityCertitificateUpdate.bind(this);
this.secureContextWatchers = [];
}
_isSecure() {
return true;
}
_equals(other) {
var _a, _b;
if (this === other) {
return true;
}
if (other instanceof CertificateProviderChannelCredentialsImpl) {
return this.caCertificateProvider === other.caCertificateProvider &&
this.identityCertificateProvider === other.identityCertificateProvider &&
((_a = this.verifyOptions) === null || _a === void 0 ? void 0 : _a.checkServerIdentity) === ((_b = other.verifyOptions) === null || _b === void 0 ? void 0 : _b.checkServerIdentity);
}
else {
return false;
}
}
ref() {
var _a;
if (this.refcount === 0) {
this.caCertificateProvider.addCaCertificateListener(this.caCertificateUpdateListener);
(_a = this.identityCertificateProvider) === null || _a === void 0 ? void 0 : _a.addIdentityCertificateListener(this.identityCertificateUpdateListener);
}
this.refcount += 1;
}
unref() {
var _a;
this.refcount -= 1;
if (this.refcount === 0) {
this.caCertificateProvider.removeCaCertificateListener(this.caCertificateUpdateListener);
(_a = this.identityCertificateProvider) === null || _a === void 0 ? void 0 : _a.removeIdentityCertificateListener(this.identityCertificateUpdateListener);
}
}
_createSecureConnector(channelTarget, options, callCredentials) {
this.ref();
return new CertificateProviderChannelCredentialsImpl.SecureConnectorImpl(this, channelTarget, options, callCredentials !== null && callCredentials !== void 0 ? callCredentials : call_credentials_1.CallCredentials.createEmpty());
}
maybeUpdateWatchers() {
if (this.hasReceivedUpdates()) {
for (const watcher of this.secureContextWatchers) {
watcher(this.getLatestSecureContext());
}
this.secureContextWatchers = [];
}
}
handleCaCertificateUpdate(update) {
this.latestCaUpdate = update;
this.maybeUpdateWatchers();
}
handleIdentityCertitificateUpdate(update) {
this.latestIdentityUpdate = update;
this.maybeUpdateWatchers();
}
hasReceivedUpdates() {
if (this.latestCaUpdate === undefined) {
return false;
}
if (this.identityCertificateProvider && this.latestIdentityUpdate === undefined) {
return false;
}
return true;
}
getSecureContext() {
if (this.hasReceivedUpdates()) {
return Promise.resolve(this.getLatestSecureContext());
}
else {
return new Promise(resolve => {
this.secureContextWatchers.push(resolve);
});
}
}
getLatestSecureContext() {
var _a, _b;
if (!this.latestCaUpdate) {
return null;
}
if (this.identityCertificateProvider !== null && !this.latestIdentityUpdate) {
return null;
}
try {
return (0, tls_1.createSecureContext)({
ca: this.latestCaUpdate.caCertificate,
key: (_a = this.latestIdentityUpdate) === null || _a === void 0 ? void 0 : _a.privateKey,
cert: (_b = this.latestIdentityUpdate) === null || _b === void 0 ? void 0 : _b.certificate,
ciphers: tls_helpers_1.CIPHER_SUITES
});
}
catch (e) {
(0, logging_1.log)(constants_1.LogVerbosity.ERROR, 'Failed to createSecureContext with error ' + e.message);
return null;
}
}
}
CertificateProviderChannelCredentialsImpl.SecureConnectorImpl = class {
constructor(parent, channelTarget, options, callCredentials) {
this.parent = parent;
this.channelTarget = channelTarget;
this.options = options;
this.callCredentials = callCredentials;
}
connect(socket) {
return new Promise((resolve, reject) => {
const secureContext = this.parent.getLatestSecureContext();
if (!secureContext) {
reject(new Error('Failed to load credentials'));
return;
}
if (socket.closed) {
reject(new Error('Socket closed while loading credentials'));
}
const connnectionOptions = getConnectionOptions(secureContext, this.parent.verifyOptions, this.channelTarget, this.options);
const tlsConnectOptions = Object.assign({ socket: socket }, connnectionOptions);
const closeCallback = () => {
reject(new Error('Socket closed'));
};
const errorCallback = (error) => {
reject(error);
};
const tlsSocket = (0, tls_1.connect)(tlsConnectOptions, () => {
var _a;
tlsSocket.removeListener('close', closeCallback);
tlsSocket.removeListener('error', errorCallback);
if (((_a = this.parent.verifyOptions.rejectUnauthorized) !== null && _a !== void 0 ? _a : true) && !tlsSocket.authorized) {
reject(tlsSocket.authorizationError);
return;
}
resolve({
socket: tlsSocket,
secure: true
});
});
tlsSocket.once('close', closeCallback);
tlsSocket.once('error', errorCallback);
});
}
async waitForReady() {
await this.parent.getSecureContext();
}
getCallCredentials() {
return this.callCredentials;
}
destroy() {
this.parent.unref();
}
};
function createCertificateProviderChannelCredentials(caCertificateProvider, identityCertificateProvider, verifyOptions) {
return new CertificateProviderChannelCredentialsImpl(caCertificateProvider, identityCertificateProvider, verifyOptions !== null && verifyOptions !== void 0 ? verifyOptions : {});
}
class ComposedChannelCredentialsImpl extends ChannelCredentials {
constructor(channelCredentials, callCredentials) {
super();
this.channelCredentials = channelCredentials;
this.callCredentials = callCredentials;
if (!channelCredentials._isSecure()) {
throw new Error('Cannot compose insecure credentials');
}
}
compose(callCredentials) {
const combinedCallCredentials = this.callCredentials.compose(callCredentials);
return new ComposedChannelCredentialsImpl(this.channelCredentials, combinedCallCredentials);
}
_isSecure() {
return true;
}
_equals(other) {
if (this === other) {
return true;
}
if (other instanceof ComposedChannelCredentialsImpl) {
return (this.channelCredentials._equals(other.channelCredentials) &&
this.callCredentials._equals(other.callCredentials));
}
else {
return false;
}
}
_createSecureConnector(channelTarget, options, callCredentials) {
const combinedCallCredentials = this.callCredentials.compose(callCredentials !== null && callCredentials !== void 0 ? callCredentials : call_credentials_1.CallCredentials.createEmpty());
return this.channelCredentials._createSecureConnector(channelTarget, options, combinedCallCredentials);
}
}
//# sourceMappingURL=channel-credentials.js.map

View File

@@ -0,0 +1,73 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.recognizedOptions = void 0;
exports.channelOptionsEqual = channelOptionsEqual;
/**
* This is for checking provided options at runtime. This is an object for
* easier membership checking.
*/
exports.recognizedOptions = {
'grpc.ssl_target_name_override': true,
'grpc.primary_user_agent': true,
'grpc.secondary_user_agent': true,
'grpc.default_authority': true,
'grpc.keepalive_time_ms': true,
'grpc.keepalive_timeout_ms': true,
'grpc.keepalive_permit_without_calls': true,
'grpc.service_config': true,
'grpc.max_concurrent_streams': true,
'grpc.initial_reconnect_backoff_ms': true,
'grpc.max_reconnect_backoff_ms': true,
'grpc.use_local_subchannel_pool': true,
'grpc.max_send_message_length': true,
'grpc.max_receive_message_length': true,
'grpc.enable_http_proxy': true,
'grpc.enable_channelz': true,
'grpc.dns_min_time_between_resolutions_ms': true,
'grpc.enable_retries': true,
'grpc.per_rpc_retry_buffer_size': true,
'grpc.retry_buffer_size': true,
'grpc.max_connection_age_ms': true,
'grpc.max_connection_age_grace_ms': true,
'grpc-node.max_session_memory': true,
'grpc.service_config_disable_resolution': true,
'grpc.client_idle_timeout_ms': true,
'grpc-node.tls_enable_trace': true,
'grpc.lb.ring_hash.ring_size_cap': true,
'grpc-node.retry_max_attempts_limit': true,
'grpc-node.flow_control_window': true,
'grpc.server_call_metric_recording': true
};
function channelOptionsEqual(options1, options2) {
const keys1 = Object.keys(options1).sort();
const keys2 = Object.keys(options2).sort();
if (keys1.length !== keys2.length) {
return false;
}
for (let i = 0; i < keys1.length; i += 1) {
if (keys1[i] !== keys2[i]) {
return false;
}
if (options1[keys1[i]] !== options2[keys2[i]]) {
return false;
}
}
return true;
}
//# sourceMappingURL=channel-options.js.map

View File

@@ -0,0 +1,68 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.ChannelImplementation = void 0;
const channel_credentials_1 = require("./channel-credentials");
const internal_channel_1 = require("./internal-channel");
class ChannelImplementation {
constructor(target, credentials, options) {
if (typeof target !== 'string') {
throw new TypeError('Channel target must be a string');
}
if (!(credentials instanceof channel_credentials_1.ChannelCredentials)) {
throw new TypeError('Channel credentials must be a ChannelCredentials object');
}
if (options) {
if (typeof options !== 'object') {
throw new TypeError('Channel options must be an object');
}
}
this.internalChannel = new internal_channel_1.InternalChannel(target, credentials, options);
}
close() {
this.internalChannel.close();
}
getTarget() {
return this.internalChannel.getTarget();
}
getConnectivityState(tryToConnect) {
return this.internalChannel.getConnectivityState(tryToConnect);
}
watchConnectivityState(currentState, deadline, callback) {
this.internalChannel.watchConnectivityState(currentState, deadline, callback);
}
/**
* Get the channelz reference object for this channel. The returned value is
* garbage if channelz is disabled for this channel.
* @returns
*/
getChannelzRef() {
return this.internalChannel.getChannelzRef();
}
createCall(method, deadline, host, parentCall, propagateFlags) {
if (typeof method !== 'string') {
throw new TypeError('Channel#createCall: method must be a string');
}
if (!(typeof deadline === 'number' || deadline instanceof Date)) {
throw new TypeError('Channel#createCall: deadline must be a number or Date');
}
return this.internalChannel.createCall(method, deadline, host, parentCall, propagateFlags);
}
}
exports.ChannelImplementation = ChannelImplementation;
//# sourceMappingURL=channel.js.map

View File

@@ -0,0 +1,598 @@
"use strict";
/*
* Copyright 2021 gRPC authors.
*
* Licensed 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.registerChannelzSocket = exports.registerChannelzServer = exports.registerChannelzSubchannel = exports.registerChannelzChannel = exports.ChannelzCallTrackerStub = exports.ChannelzCallTracker = exports.ChannelzChildrenTrackerStub = exports.ChannelzChildrenTracker = exports.ChannelzTrace = exports.ChannelzTraceStub = void 0;
exports.unregisterChannelzRef = unregisterChannelzRef;
exports.getChannelzHandlers = getChannelzHandlers;
exports.getChannelzServiceDefinition = getChannelzServiceDefinition;
exports.setup = setup;
const net_1 = require("net");
const ordered_map_1 = require("@js-sdsl/ordered-map");
const connectivity_state_1 = require("./connectivity-state");
const constants_1 = require("./constants");
const subchannel_address_1 = require("./subchannel-address");
const admin_1 = require("./admin");
const make_client_1 = require("./make-client");
function channelRefToMessage(ref) {
return {
channel_id: ref.id,
name: ref.name,
};
}
function subchannelRefToMessage(ref) {
return {
subchannel_id: ref.id,
name: ref.name,
};
}
function serverRefToMessage(ref) {
return {
server_id: ref.id,
};
}
function socketRefToMessage(ref) {
return {
socket_id: ref.id,
name: ref.name,
};
}
/**
* The loose upper bound on the number of events that should be retained in a
* trace. This may be exceeded by up to a factor of 2. Arbitrarily chosen as a
* number that should be large enough to contain the recent relevant
* information, but small enough to not use excessive memory.
*/
const TARGET_RETAINED_TRACES = 32;
/**
* Default number of sockets/servers/channels/subchannels to return
*/
const DEFAULT_MAX_RESULTS = 100;
class ChannelzTraceStub {
constructor() {
this.events = [];
this.creationTimestamp = new Date();
this.eventsLogged = 0;
}
addTrace() { }
getTraceMessage() {
return {
creation_timestamp: dateToProtoTimestamp(this.creationTimestamp),
num_events_logged: this.eventsLogged,
events: [],
};
}
}
exports.ChannelzTraceStub = ChannelzTraceStub;
class ChannelzTrace {
constructor() {
this.events = [];
this.eventsLogged = 0;
this.creationTimestamp = new Date();
}
addTrace(severity, description, child) {
const timestamp = new Date();
this.events.push({
description: description,
severity: severity,
timestamp: timestamp,
childChannel: (child === null || child === void 0 ? void 0 : child.kind) === 'channel' ? child : undefined,
childSubchannel: (child === null || child === void 0 ? void 0 : child.kind) === 'subchannel' ? child : undefined,
});
// Whenever the trace array gets too large, discard the first half
if (this.events.length >= TARGET_RETAINED_TRACES * 2) {
this.events = this.events.slice(TARGET_RETAINED_TRACES);
}
this.eventsLogged += 1;
}
getTraceMessage() {
return {
creation_timestamp: dateToProtoTimestamp(this.creationTimestamp),
num_events_logged: this.eventsLogged,
events: this.events.map(event => {
return {
description: event.description,
severity: event.severity,
timestamp: dateToProtoTimestamp(event.timestamp),
channel_ref: event.childChannel
? channelRefToMessage(event.childChannel)
: null,
subchannel_ref: event.childSubchannel
? subchannelRefToMessage(event.childSubchannel)
: null,
};
}),
};
}
}
exports.ChannelzTrace = ChannelzTrace;
class ChannelzChildrenTracker {
constructor() {
this.channelChildren = new ordered_map_1.OrderedMap();
this.subchannelChildren = new ordered_map_1.OrderedMap();
this.socketChildren = new ordered_map_1.OrderedMap();
this.trackerMap = {
["channel" /* EntityTypes.channel */]: this.channelChildren,
["subchannel" /* EntityTypes.subchannel */]: this.subchannelChildren,
["socket" /* EntityTypes.socket */]: this.socketChildren,
};
}
refChild(child) {
const tracker = this.trackerMap[child.kind];
const trackedChild = tracker.find(child.id);
if (trackedChild.equals(tracker.end())) {
tracker.setElement(child.id, {
ref: child,
count: 1,
}, trackedChild);
}
else {
trackedChild.pointer[1].count += 1;
}
}
unrefChild(child) {
const tracker = this.trackerMap[child.kind];
const trackedChild = tracker.getElementByKey(child.id);
if (trackedChild !== undefined) {
trackedChild.count -= 1;
if (trackedChild.count === 0) {
tracker.eraseElementByKey(child.id);
}
}
}
getChildLists() {
return {
channels: this.channelChildren,
subchannels: this.subchannelChildren,
sockets: this.socketChildren,
};
}
}
exports.ChannelzChildrenTracker = ChannelzChildrenTracker;
class ChannelzChildrenTrackerStub extends ChannelzChildrenTracker {
refChild() { }
unrefChild() { }
}
exports.ChannelzChildrenTrackerStub = ChannelzChildrenTrackerStub;
class ChannelzCallTracker {
constructor() {
this.callsStarted = 0;
this.callsSucceeded = 0;
this.callsFailed = 0;
this.lastCallStartedTimestamp = null;
}
addCallStarted() {
this.callsStarted += 1;
this.lastCallStartedTimestamp = new Date();
}
addCallSucceeded() {
this.callsSucceeded += 1;
}
addCallFailed() {
this.callsFailed += 1;
}
}
exports.ChannelzCallTracker = ChannelzCallTracker;
class ChannelzCallTrackerStub extends ChannelzCallTracker {
addCallStarted() { }
addCallSucceeded() { }
addCallFailed() { }
}
exports.ChannelzCallTrackerStub = ChannelzCallTrackerStub;
const entityMaps = {
["channel" /* EntityTypes.channel */]: new ordered_map_1.OrderedMap(),
["subchannel" /* EntityTypes.subchannel */]: new ordered_map_1.OrderedMap(),
["server" /* EntityTypes.server */]: new ordered_map_1.OrderedMap(),
["socket" /* EntityTypes.socket */]: new ordered_map_1.OrderedMap(),
};
const generateRegisterFn = (kind) => {
let nextId = 1;
function getNextId() {
return nextId++;
}
const entityMap = entityMaps[kind];
return (name, getInfo, channelzEnabled) => {
const id = getNextId();
const ref = { id, name, kind };
if (channelzEnabled) {
entityMap.setElement(id, { ref, getInfo });
}
return ref;
};
};
exports.registerChannelzChannel = generateRegisterFn("channel" /* EntityTypes.channel */);
exports.registerChannelzSubchannel = generateRegisterFn("subchannel" /* EntityTypes.subchannel */);
exports.registerChannelzServer = generateRegisterFn("server" /* EntityTypes.server */);
exports.registerChannelzSocket = generateRegisterFn("socket" /* EntityTypes.socket */);
function unregisterChannelzRef(ref) {
entityMaps[ref.kind].eraseElementByKey(ref.id);
}
/**
* Parse a single section of an IPv6 address as two bytes
* @param addressSection A hexadecimal string of length up to 4
* @returns The pair of bytes representing this address section
*/
function parseIPv6Section(addressSection) {
const numberValue = Number.parseInt(addressSection, 16);
return [(numberValue / 256) | 0, numberValue % 256];
}
/**
* Parse a chunk of an IPv6 address string to some number of bytes
* @param addressChunk Some number of segments of up to 4 hexadecimal
* characters each, joined by colons.
* @returns The list of bytes representing this address chunk
*/
function parseIPv6Chunk(addressChunk) {
if (addressChunk === '') {
return [];
}
const bytePairs = addressChunk
.split(':')
.map(section => parseIPv6Section(section));
const result = [];
return result.concat(...bytePairs);
}
function isIPv6MappedIPv4(ipAddress) {
return (0, net_1.isIPv6)(ipAddress) && ipAddress.toLowerCase().startsWith('::ffff:') && (0, net_1.isIPv4)(ipAddress.substring(7));
}
/**
* Prerequisite: isIPv4(ipAddress)
* @param ipAddress
* @returns
*/
function ipv4AddressStringToBuffer(ipAddress) {
return Buffer.from(Uint8Array.from(ipAddress.split('.').map(segment => Number.parseInt(segment))));
}
/**
* Converts an IPv4 or IPv6 address from string representation to binary
* representation
* @param ipAddress an IP address in standard IPv4 or IPv6 text format
* @returns
*/
function ipAddressStringToBuffer(ipAddress) {
if ((0, net_1.isIPv4)(ipAddress)) {
return ipv4AddressStringToBuffer(ipAddress);
}
else if (isIPv6MappedIPv4(ipAddress)) {
return ipv4AddressStringToBuffer(ipAddress.substring(7));
}
else if ((0, net_1.isIPv6)(ipAddress)) {
let leftSection;
let rightSection;
const doubleColonIndex = ipAddress.indexOf('::');
if (doubleColonIndex === -1) {
leftSection = ipAddress;
rightSection = '';
}
else {
leftSection = ipAddress.substring(0, doubleColonIndex);
rightSection = ipAddress.substring(doubleColonIndex + 2);
}
const leftBuffer = Buffer.from(parseIPv6Chunk(leftSection));
const rightBuffer = Buffer.from(parseIPv6Chunk(rightSection));
const middleBuffer = Buffer.alloc(16 - leftBuffer.length - rightBuffer.length, 0);
return Buffer.concat([leftBuffer, middleBuffer, rightBuffer]);
}
else {
return null;
}
}
function connectivityStateToMessage(state) {
switch (state) {
case connectivity_state_1.ConnectivityState.CONNECTING:
return {
state: 'CONNECTING',
};
case connectivity_state_1.ConnectivityState.IDLE:
return {
state: 'IDLE',
};
case connectivity_state_1.ConnectivityState.READY:
return {
state: 'READY',
};
case connectivity_state_1.ConnectivityState.SHUTDOWN:
return {
state: 'SHUTDOWN',
};
case connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE:
return {
state: 'TRANSIENT_FAILURE',
};
default:
return {
state: 'UNKNOWN',
};
}
}
function dateToProtoTimestamp(date) {
if (!date) {
return null;
}
const millisSinceEpoch = date.getTime();
return {
seconds: (millisSinceEpoch / 1000) | 0,
nanos: (millisSinceEpoch % 1000) * 1000000,
};
}
function getChannelMessage(channelEntry) {
const resolvedInfo = channelEntry.getInfo();
const channelRef = [];
const subchannelRef = [];
resolvedInfo.children.channels.forEach(el => {
channelRef.push(channelRefToMessage(el[1].ref));
});
resolvedInfo.children.subchannels.forEach(el => {
subchannelRef.push(subchannelRefToMessage(el[1].ref));
});
return {
ref: channelRefToMessage(channelEntry.ref),
data: {
target: resolvedInfo.target,
state: connectivityStateToMessage(resolvedInfo.state),
calls_started: resolvedInfo.callTracker.callsStarted,
calls_succeeded: resolvedInfo.callTracker.callsSucceeded,
calls_failed: resolvedInfo.callTracker.callsFailed,
last_call_started_timestamp: dateToProtoTimestamp(resolvedInfo.callTracker.lastCallStartedTimestamp),
trace: resolvedInfo.trace.getTraceMessage(),
},
channel_ref: channelRef,
subchannel_ref: subchannelRef,
};
}
function GetChannel(call, callback) {
const channelId = parseInt(call.request.channel_id, 10);
const channelEntry = entityMaps["channel" /* EntityTypes.channel */].getElementByKey(channelId);
if (channelEntry === undefined) {
callback({
code: constants_1.Status.NOT_FOUND,
details: 'No channel data found for id ' + channelId,
});
return;
}
callback(null, { channel: getChannelMessage(channelEntry) });
}
function GetTopChannels(call, callback) {
const maxResults = parseInt(call.request.max_results, 10) || DEFAULT_MAX_RESULTS;
const resultList = [];
const startId = parseInt(call.request.start_channel_id, 10);
const channelEntries = entityMaps["channel" /* EntityTypes.channel */];
let i;
for (i = channelEntries.lowerBound(startId); !i.equals(channelEntries.end()) && resultList.length < maxResults; i = i.next()) {
resultList.push(getChannelMessage(i.pointer[1]));
}
callback(null, {
channel: resultList,
end: i.equals(channelEntries.end()),
});
}
function getServerMessage(serverEntry) {
const resolvedInfo = serverEntry.getInfo();
const listenSocket = [];
resolvedInfo.listenerChildren.sockets.forEach(el => {
listenSocket.push(socketRefToMessage(el[1].ref));
});
return {
ref: serverRefToMessage(serverEntry.ref),
data: {
calls_started: resolvedInfo.callTracker.callsStarted,
calls_succeeded: resolvedInfo.callTracker.callsSucceeded,
calls_failed: resolvedInfo.callTracker.callsFailed,
last_call_started_timestamp: dateToProtoTimestamp(resolvedInfo.callTracker.lastCallStartedTimestamp),
trace: resolvedInfo.trace.getTraceMessage(),
},
listen_socket: listenSocket,
};
}
function GetServer(call, callback) {
const serverId = parseInt(call.request.server_id, 10);
const serverEntries = entityMaps["server" /* EntityTypes.server */];
const serverEntry = serverEntries.getElementByKey(serverId);
if (serverEntry === undefined) {
callback({
code: constants_1.Status.NOT_FOUND,
details: 'No server data found for id ' + serverId,
});
return;
}
callback(null, { server: getServerMessage(serverEntry) });
}
function GetServers(call, callback) {
const maxResults = parseInt(call.request.max_results, 10) || DEFAULT_MAX_RESULTS;
const startId = parseInt(call.request.start_server_id, 10);
const serverEntries = entityMaps["server" /* EntityTypes.server */];
const resultList = [];
let i;
for (i = serverEntries.lowerBound(startId); !i.equals(serverEntries.end()) && resultList.length < maxResults; i = i.next()) {
resultList.push(getServerMessage(i.pointer[1]));
}
callback(null, {
server: resultList,
end: i.equals(serverEntries.end()),
});
}
function GetSubchannel(call, callback) {
const subchannelId = parseInt(call.request.subchannel_id, 10);
const subchannelEntry = entityMaps["subchannel" /* EntityTypes.subchannel */].getElementByKey(subchannelId);
if (subchannelEntry === undefined) {
callback({
code: constants_1.Status.NOT_FOUND,
details: 'No subchannel data found for id ' + subchannelId,
});
return;
}
const resolvedInfo = subchannelEntry.getInfo();
const listenSocket = [];
resolvedInfo.children.sockets.forEach(el => {
listenSocket.push(socketRefToMessage(el[1].ref));
});
const subchannelMessage = {
ref: subchannelRefToMessage(subchannelEntry.ref),
data: {
target: resolvedInfo.target,
state: connectivityStateToMessage(resolvedInfo.state),
calls_started: resolvedInfo.callTracker.callsStarted,
calls_succeeded: resolvedInfo.callTracker.callsSucceeded,
calls_failed: resolvedInfo.callTracker.callsFailed,
last_call_started_timestamp: dateToProtoTimestamp(resolvedInfo.callTracker.lastCallStartedTimestamp),
trace: resolvedInfo.trace.getTraceMessage(),
},
socket_ref: listenSocket,
};
callback(null, { subchannel: subchannelMessage });
}
function subchannelAddressToAddressMessage(subchannelAddress) {
var _a;
if ((0, subchannel_address_1.isTcpSubchannelAddress)(subchannelAddress)) {
return {
address: 'tcpip_address',
tcpip_address: {
ip_address: (_a = ipAddressStringToBuffer(subchannelAddress.host)) !== null && _a !== void 0 ? _a : undefined,
port: subchannelAddress.port,
},
};
}
else {
return {
address: 'uds_address',
uds_address: {
filename: subchannelAddress.path,
},
};
}
}
function GetSocket(call, callback) {
var _a, _b, _c, _d, _e;
const socketId = parseInt(call.request.socket_id, 10);
const socketEntry = entityMaps["socket" /* EntityTypes.socket */].getElementByKey(socketId);
if (socketEntry === undefined) {
callback({
code: constants_1.Status.NOT_FOUND,
details: 'No socket data found for id ' + socketId,
});
return;
}
const resolvedInfo = socketEntry.getInfo();
const securityMessage = resolvedInfo.security
? {
model: 'tls',
tls: {
cipher_suite: resolvedInfo.security.cipherSuiteStandardName
? 'standard_name'
: 'other_name',
standard_name: (_a = resolvedInfo.security.cipherSuiteStandardName) !== null && _a !== void 0 ? _a : undefined,
other_name: (_b = resolvedInfo.security.cipherSuiteOtherName) !== null && _b !== void 0 ? _b : undefined,
local_certificate: (_c = resolvedInfo.security.localCertificate) !== null && _c !== void 0 ? _c : undefined,
remote_certificate: (_d = resolvedInfo.security.remoteCertificate) !== null && _d !== void 0 ? _d : undefined,
},
}
: null;
const socketMessage = {
ref: socketRefToMessage(socketEntry.ref),
local: resolvedInfo.localAddress
? subchannelAddressToAddressMessage(resolvedInfo.localAddress)
: null,
remote: resolvedInfo.remoteAddress
? subchannelAddressToAddressMessage(resolvedInfo.remoteAddress)
: null,
remote_name: (_e = resolvedInfo.remoteName) !== null && _e !== void 0 ? _e : undefined,
security: securityMessage,
data: {
keep_alives_sent: resolvedInfo.keepAlivesSent,
streams_started: resolvedInfo.streamsStarted,
streams_succeeded: resolvedInfo.streamsSucceeded,
streams_failed: resolvedInfo.streamsFailed,
last_local_stream_created_timestamp: dateToProtoTimestamp(resolvedInfo.lastLocalStreamCreatedTimestamp),
last_remote_stream_created_timestamp: dateToProtoTimestamp(resolvedInfo.lastRemoteStreamCreatedTimestamp),
messages_received: resolvedInfo.messagesReceived,
messages_sent: resolvedInfo.messagesSent,
last_message_received_timestamp: dateToProtoTimestamp(resolvedInfo.lastMessageReceivedTimestamp),
last_message_sent_timestamp: dateToProtoTimestamp(resolvedInfo.lastMessageSentTimestamp),
local_flow_control_window: resolvedInfo.localFlowControlWindow
? { value: resolvedInfo.localFlowControlWindow }
: null,
remote_flow_control_window: resolvedInfo.remoteFlowControlWindow
? { value: resolvedInfo.remoteFlowControlWindow }
: null,
},
};
callback(null, { socket: socketMessage });
}
function GetServerSockets(call, callback) {
const serverId = parseInt(call.request.server_id, 10);
const serverEntry = entityMaps["server" /* EntityTypes.server */].getElementByKey(serverId);
if (serverEntry === undefined) {
callback({
code: constants_1.Status.NOT_FOUND,
details: 'No server data found for id ' + serverId,
});
return;
}
const startId = parseInt(call.request.start_socket_id, 10);
const maxResults = parseInt(call.request.max_results, 10) || DEFAULT_MAX_RESULTS;
const resolvedInfo = serverEntry.getInfo();
// If we wanted to include listener sockets in the result, this line would
// instead say
// const allSockets = resolvedInfo.listenerChildren.sockets.concat(resolvedInfo.sessionChildren.sockets).sort((ref1, ref2) => ref1.id - ref2.id);
const allSockets = resolvedInfo.sessionChildren.sockets;
const resultList = [];
let i;
for (i = allSockets.lowerBound(startId); !i.equals(allSockets.end()) && resultList.length < maxResults; i = i.next()) {
resultList.push(socketRefToMessage(i.pointer[1].ref));
}
callback(null, {
socket_ref: resultList,
end: i.equals(allSockets.end()),
});
}
function getChannelzHandlers() {
return {
GetChannel,
GetTopChannels,
GetServer,
GetServers,
GetSubchannel,
GetSocket,
GetServerSockets,
};
}
let loadedChannelzDefinition = null;
function getChannelzServiceDefinition() {
if (loadedChannelzDefinition) {
return loadedChannelzDefinition;
}
/* The purpose of this complexity is to avoid loading @grpc/proto-loader at
* runtime for users who will not use/enable channelz. */
const loaderLoadSync = require('@grpc/proto-loader')
.loadSync;
const loadedProto = loaderLoadSync('channelz.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: [`${__dirname}/../../proto`],
});
const channelzGrpcObject = (0, make_client_1.loadPackageDefinition)(loadedProto);
loadedChannelzDefinition =
channelzGrpcObject.grpc.channelz.v1.Channelz.service;
return loadedChannelzDefinition;
}
function setup() {
(0, admin_1.registerAdminService)(getChannelzServiceDefinition, getChannelzHandlers);
}
//# sourceMappingURL=channelz.js.map

View File

@@ -0,0 +1,434 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.InterceptingCall = exports.RequesterBuilder = exports.ListenerBuilder = exports.InterceptorConfigurationError = void 0;
exports.getInterceptingCall = getInterceptingCall;
const metadata_1 = require("./metadata");
const call_interface_1 = require("./call-interface");
const constants_1 = require("./constants");
const error_1 = require("./error");
/**
* Error class associated with passing both interceptors and interceptor
* providers to a client constructor or as call options.
*/
class InterceptorConfigurationError extends Error {
constructor(message) {
super(message);
this.name = 'InterceptorConfigurationError';
Error.captureStackTrace(this, InterceptorConfigurationError);
}
}
exports.InterceptorConfigurationError = InterceptorConfigurationError;
class ListenerBuilder {
constructor() {
this.metadata = undefined;
this.message = undefined;
this.status = undefined;
}
withOnReceiveMetadata(onReceiveMetadata) {
this.metadata = onReceiveMetadata;
return this;
}
withOnReceiveMessage(onReceiveMessage) {
this.message = onReceiveMessage;
return this;
}
withOnReceiveStatus(onReceiveStatus) {
this.status = onReceiveStatus;
return this;
}
build() {
return {
onReceiveMetadata: this.metadata,
onReceiveMessage: this.message,
onReceiveStatus: this.status,
};
}
}
exports.ListenerBuilder = ListenerBuilder;
class RequesterBuilder {
constructor() {
this.start = undefined;
this.message = undefined;
this.halfClose = undefined;
this.cancel = undefined;
}
withStart(start) {
this.start = start;
return this;
}
withSendMessage(sendMessage) {
this.message = sendMessage;
return this;
}
withHalfClose(halfClose) {
this.halfClose = halfClose;
return this;
}
withCancel(cancel) {
this.cancel = cancel;
return this;
}
build() {
return {
start: this.start,
sendMessage: this.message,
halfClose: this.halfClose,
cancel: this.cancel,
};
}
}
exports.RequesterBuilder = RequesterBuilder;
/**
* A Listener with a default pass-through implementation of each method. Used
* for filling out Listeners with some methods omitted.
*/
const defaultListener = {
onReceiveMetadata: (metadata, next) => {
next(metadata);
},
onReceiveMessage: (message, next) => {
next(message);
},
onReceiveStatus: (status, next) => {
next(status);
},
};
/**
* A Requester with a default pass-through implementation of each method. Used
* for filling out Requesters with some methods omitted.
*/
const defaultRequester = {
start: (metadata, listener, next) => {
next(metadata, listener);
},
sendMessage: (message, next) => {
next(message);
},
halfClose: next => {
next();
},
cancel: next => {
next();
},
};
class InterceptingCall {
constructor(nextCall, requester) {
var _a, _b, _c, _d;
this.nextCall = nextCall;
/**
* Indicates that metadata has been passed to the requester's start
* method but it has not been passed to the corresponding next callback
*/
this.processingMetadata = false;
/**
* Message context for a pending message that is waiting for
*/
this.pendingMessageContext = null;
/**
* Indicates that a message has been passed to the requester's sendMessage
* method but it has not been passed to the corresponding next callback
*/
this.processingMessage = false;
/**
* Indicates that a status was received but could not be propagated because
* a message was still being processed.
*/
this.pendingHalfClose = false;
if (requester) {
this.requester = {
start: (_a = requester.start) !== null && _a !== void 0 ? _a : defaultRequester.start,
sendMessage: (_b = requester.sendMessage) !== null && _b !== void 0 ? _b : defaultRequester.sendMessage,
halfClose: (_c = requester.halfClose) !== null && _c !== void 0 ? _c : defaultRequester.halfClose,
cancel: (_d = requester.cancel) !== null && _d !== void 0 ? _d : defaultRequester.cancel,
};
}
else {
this.requester = defaultRequester;
}
}
cancelWithStatus(status, details) {
this.requester.cancel(() => {
this.nextCall.cancelWithStatus(status, details);
});
}
getPeer() {
return this.nextCall.getPeer();
}
processPendingMessage() {
if (this.pendingMessageContext) {
this.nextCall.sendMessageWithContext(this.pendingMessageContext, this.pendingMessage);
this.pendingMessageContext = null;
this.pendingMessage = null;
}
}
processPendingHalfClose() {
if (this.pendingHalfClose) {
this.nextCall.halfClose();
}
}
start(metadata, interceptingListener) {
var _a, _b, _c, _d, _e, _f;
const fullInterceptingListener = {
onReceiveMetadata: (_b = (_a = interceptingListener === null || interceptingListener === void 0 ? void 0 : interceptingListener.onReceiveMetadata) === null || _a === void 0 ? void 0 : _a.bind(interceptingListener)) !== null && _b !== void 0 ? _b : (metadata => { }),
onReceiveMessage: (_d = (_c = interceptingListener === null || interceptingListener === void 0 ? void 0 : interceptingListener.onReceiveMessage) === null || _c === void 0 ? void 0 : _c.bind(interceptingListener)) !== null && _d !== void 0 ? _d : (message => { }),
onReceiveStatus: (_f = (_e = interceptingListener === null || interceptingListener === void 0 ? void 0 : interceptingListener.onReceiveStatus) === null || _e === void 0 ? void 0 : _e.bind(interceptingListener)) !== null && _f !== void 0 ? _f : (status => { }),
};
this.processingMetadata = true;
this.requester.start(metadata, fullInterceptingListener, (md, listener) => {
var _a, _b, _c;
this.processingMetadata = false;
let finalInterceptingListener;
if ((0, call_interface_1.isInterceptingListener)(listener)) {
finalInterceptingListener = listener;
}
else {
const fullListener = {
onReceiveMetadata: (_a = listener.onReceiveMetadata) !== null && _a !== void 0 ? _a : defaultListener.onReceiveMetadata,
onReceiveMessage: (_b = listener.onReceiveMessage) !== null && _b !== void 0 ? _b : defaultListener.onReceiveMessage,
onReceiveStatus: (_c = listener.onReceiveStatus) !== null && _c !== void 0 ? _c : defaultListener.onReceiveStatus,
};
finalInterceptingListener = new call_interface_1.InterceptingListenerImpl(fullListener, fullInterceptingListener);
}
this.nextCall.start(md, finalInterceptingListener);
this.processPendingMessage();
this.processPendingHalfClose();
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sendMessageWithContext(context, message) {
this.processingMessage = true;
this.requester.sendMessage(message, finalMessage => {
this.processingMessage = false;
if (this.processingMetadata) {
this.pendingMessageContext = context;
this.pendingMessage = message;
}
else {
this.nextCall.sendMessageWithContext(context, finalMessage);
this.processPendingHalfClose();
}
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sendMessage(message) {
this.sendMessageWithContext({}, message);
}
startRead() {
this.nextCall.startRead();
}
halfClose() {
this.requester.halfClose(() => {
if (this.processingMetadata || this.processingMessage) {
this.pendingHalfClose = true;
}
else {
this.nextCall.halfClose();
}
});
}
getAuthContext() {
return this.nextCall.getAuthContext();
}
}
exports.InterceptingCall = InterceptingCall;
function getCall(channel, path, options) {
var _a, _b;
const deadline = (_a = options.deadline) !== null && _a !== void 0 ? _a : Infinity;
const host = options.host;
const parent = (_b = options.parent) !== null && _b !== void 0 ? _b : null;
const propagateFlags = options.propagate_flags;
const credentials = options.credentials;
const call = channel.createCall(path, deadline, host, parent, propagateFlags);
if (credentials) {
call.setCredentials(credentials);
}
return call;
}
/**
* InterceptingCall implementation that directly owns the underlying Call
* object and handles serialization and deseraizliation.
*/
class BaseInterceptingCall {
constructor(call,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
methodDefinition) {
this.call = call;
this.methodDefinition = methodDefinition;
}
cancelWithStatus(status, details) {
this.call.cancelWithStatus(status, details);
}
getPeer() {
return this.call.getPeer();
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sendMessageWithContext(context, message) {
let serialized;
try {
serialized = this.methodDefinition.requestSerialize(message);
}
catch (e) {
this.call.cancelWithStatus(constants_1.Status.INTERNAL, `Request message serialization failure: ${(0, error_1.getErrorMessage)(e)}`);
return;
}
this.call.sendMessageWithContext(context, serialized);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sendMessage(message) {
this.sendMessageWithContext({}, message);
}
start(metadata, interceptingListener) {
let readError = null;
this.call.start(metadata, {
onReceiveMetadata: metadata => {
var _a;
(_a = interceptingListener === null || interceptingListener === void 0 ? void 0 : interceptingListener.onReceiveMetadata) === null || _a === void 0 ? void 0 : _a.call(interceptingListener, metadata);
},
onReceiveMessage: message => {
var _a;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let deserialized;
try {
deserialized = this.methodDefinition.responseDeserialize(message);
}
catch (e) {
readError = {
code: constants_1.Status.INTERNAL,
details: `Response message parsing error: ${(0, error_1.getErrorMessage)(e)}`,
metadata: new metadata_1.Metadata(),
};
this.call.cancelWithStatus(readError.code, readError.details);
return;
}
(_a = interceptingListener === null || interceptingListener === void 0 ? void 0 : interceptingListener.onReceiveMessage) === null || _a === void 0 ? void 0 : _a.call(interceptingListener, deserialized);
},
onReceiveStatus: status => {
var _a, _b;
if (readError) {
(_a = interceptingListener === null || interceptingListener === void 0 ? void 0 : interceptingListener.onReceiveStatus) === null || _a === void 0 ? void 0 : _a.call(interceptingListener, readError);
}
else {
(_b = interceptingListener === null || interceptingListener === void 0 ? void 0 : interceptingListener.onReceiveStatus) === null || _b === void 0 ? void 0 : _b.call(interceptingListener, status);
}
},
});
}
startRead() {
this.call.startRead();
}
halfClose() {
this.call.halfClose();
}
getAuthContext() {
return this.call.getAuthContext();
}
}
/**
* BaseInterceptingCall with special-cased behavior for methods with unary
* responses.
*/
class BaseUnaryInterceptingCall extends BaseInterceptingCall {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(call, methodDefinition) {
super(call, methodDefinition);
}
start(metadata, listener) {
var _a, _b;
let receivedMessage = false;
const wrapperListener = {
onReceiveMetadata: (_b = (_a = listener === null || listener === void 0 ? void 0 : listener.onReceiveMetadata) === null || _a === void 0 ? void 0 : _a.bind(listener)) !== null && _b !== void 0 ? _b : (metadata => { }),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onReceiveMessage: (message) => {
var _a;
receivedMessage = true;
(_a = listener === null || listener === void 0 ? void 0 : listener.onReceiveMessage) === null || _a === void 0 ? void 0 : _a.call(listener, message);
},
onReceiveStatus: (status) => {
var _a, _b;
if (!receivedMessage) {
(_a = listener === null || listener === void 0 ? void 0 : listener.onReceiveMessage) === null || _a === void 0 ? void 0 : _a.call(listener, null);
}
(_b = listener === null || listener === void 0 ? void 0 : listener.onReceiveStatus) === null || _b === void 0 ? void 0 : _b.call(listener, status);
},
};
super.start(metadata, wrapperListener);
this.call.startRead();
}
}
/**
* BaseInterceptingCall with special-cased behavior for methods with streaming
* responses.
*/
class BaseStreamingInterceptingCall extends BaseInterceptingCall {
}
function getBottomInterceptingCall(channel, options,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
methodDefinition) {
const call = getCall(channel, methodDefinition.path, options);
if (methodDefinition.responseStream) {
return new BaseStreamingInterceptingCall(call, methodDefinition);
}
else {
return new BaseUnaryInterceptingCall(call, methodDefinition);
}
}
function getInterceptingCall(interceptorArgs,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
methodDefinition, options, channel) {
if (interceptorArgs.clientInterceptors.length > 0 &&
interceptorArgs.clientInterceptorProviders.length > 0) {
throw new InterceptorConfigurationError('Both interceptors and interceptor_providers were passed as options ' +
'to the client constructor. Only one of these is allowed.');
}
if (interceptorArgs.callInterceptors.length > 0 &&
interceptorArgs.callInterceptorProviders.length > 0) {
throw new InterceptorConfigurationError('Both interceptors and interceptor_providers were passed as call ' +
'options. Only one of these is allowed.');
}
let interceptors = [];
// Interceptors passed to the call override interceptors passed to the client constructor
if (interceptorArgs.callInterceptors.length > 0 ||
interceptorArgs.callInterceptorProviders.length > 0) {
interceptors = []
.concat(interceptorArgs.callInterceptors, interceptorArgs.callInterceptorProviders.map(provider => provider(methodDefinition)))
.filter(interceptor => interceptor);
// Filter out falsy values when providers return nothing
}
else {
interceptors = []
.concat(interceptorArgs.clientInterceptors, interceptorArgs.clientInterceptorProviders.map(provider => provider(methodDefinition)))
.filter(interceptor => interceptor);
// Filter out falsy values when providers return nothing
}
const interceptorOptions = Object.assign({}, options, {
method_definition: methodDefinition,
});
/* For each interceptor in the list, the nextCall function passed to it is
* based on the next interceptor in the list, using a nextCall function
* constructed with the following interceptor in the list, and so on. The
* initialValue, which is effectively at the end of the list, is a nextCall
* function that invokes getBottomInterceptingCall, the result of which
* handles (de)serialization and also gets the underlying call from the
* channel. */
const getCall = interceptors.reduceRight((nextCall, nextInterceptor) => {
return currentOptions => nextInterceptor(currentOptions, nextCall);
}, (finalOptions) => getBottomInterceptingCall(channel, finalOptions, methodDefinition));
return getCall(interceptorOptions);
}
//# sourceMappingURL=client-interceptors.js.map

View File

@@ -0,0 +1,433 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.Client = void 0;
const call_1 = require("./call");
const channel_1 = require("./channel");
const connectivity_state_1 = require("./connectivity-state");
const constants_1 = require("./constants");
const metadata_1 = require("./metadata");
const client_interceptors_1 = require("./client-interceptors");
const CHANNEL_SYMBOL = Symbol();
const INTERCEPTOR_SYMBOL = Symbol();
const INTERCEPTOR_PROVIDER_SYMBOL = Symbol();
const CALL_INVOCATION_TRANSFORMER_SYMBOL = Symbol();
function isFunction(arg) {
return typeof arg === 'function';
}
function getErrorStackString(error) {
var _a;
return ((_a = error.stack) === null || _a === void 0 ? void 0 : _a.split('\n').slice(1).join('\n')) || 'no stack trace available';
}
/**
* A generic gRPC client. Primarily useful as a base class for all generated
* clients.
*/
class Client {
constructor(address, credentials, options = {}) {
var _a, _b;
options = Object.assign({}, options);
this[INTERCEPTOR_SYMBOL] = (_a = options.interceptors) !== null && _a !== void 0 ? _a : [];
delete options.interceptors;
this[INTERCEPTOR_PROVIDER_SYMBOL] = (_b = options.interceptor_providers) !== null && _b !== void 0 ? _b : [];
delete options.interceptor_providers;
if (this[INTERCEPTOR_SYMBOL].length > 0 &&
this[INTERCEPTOR_PROVIDER_SYMBOL].length > 0) {
throw new Error('Both interceptors and interceptor_providers were passed as options ' +
'to the client constructor. Only one of these is allowed.');
}
this[CALL_INVOCATION_TRANSFORMER_SYMBOL] =
options.callInvocationTransformer;
delete options.callInvocationTransformer;
if (options.channelOverride) {
this[CHANNEL_SYMBOL] = options.channelOverride;
}
else if (options.channelFactoryOverride) {
const channelFactoryOverride = options.channelFactoryOverride;
delete options.channelFactoryOverride;
this[CHANNEL_SYMBOL] = channelFactoryOverride(address, credentials, options);
}
else {
this[CHANNEL_SYMBOL] = new channel_1.ChannelImplementation(address, credentials, options);
}
}
close() {
this[CHANNEL_SYMBOL].close();
}
getChannel() {
return this[CHANNEL_SYMBOL];
}
waitForReady(deadline, callback) {
const checkState = (err) => {
if (err) {
callback(new Error('Failed to connect before the deadline'));
return;
}
let newState;
try {
newState = this[CHANNEL_SYMBOL].getConnectivityState(true);
}
catch (e) {
callback(new Error('The channel has been closed'));
return;
}
if (newState === connectivity_state_1.ConnectivityState.READY) {
callback();
}
else {
try {
this[CHANNEL_SYMBOL].watchConnectivityState(newState, deadline, checkState);
}
catch (e) {
callback(new Error('The channel has been closed'));
}
}
};
setImmediate(checkState);
}
checkOptionalUnaryResponseArguments(arg1, arg2, arg3) {
if (isFunction(arg1)) {
return { metadata: new metadata_1.Metadata(), options: {}, callback: arg1 };
}
else if (isFunction(arg2)) {
if (arg1 instanceof metadata_1.Metadata) {
return { metadata: arg1, options: {}, callback: arg2 };
}
else {
return { metadata: new metadata_1.Metadata(), options: arg1, callback: arg2 };
}
}
else {
if (!(arg1 instanceof metadata_1.Metadata &&
arg2 instanceof Object &&
isFunction(arg3))) {
throw new Error('Incorrect arguments passed');
}
return { metadata: arg1, options: arg2, callback: arg3 };
}
}
makeUnaryRequest(method, serialize, deserialize, argument, metadata, options, callback) {
var _a, _b;
const checkedArguments = this.checkOptionalUnaryResponseArguments(metadata, options, callback);
const methodDefinition = {
path: method,
requestStream: false,
responseStream: false,
requestSerialize: serialize,
responseDeserialize: deserialize,
};
let callProperties = {
argument: argument,
metadata: checkedArguments.metadata,
call: new call_1.ClientUnaryCallImpl(),
channel: this[CHANNEL_SYMBOL],
methodDefinition: methodDefinition,
callOptions: checkedArguments.options,
callback: checkedArguments.callback,
};
if (this[CALL_INVOCATION_TRANSFORMER_SYMBOL]) {
callProperties = this[CALL_INVOCATION_TRANSFORMER_SYMBOL](callProperties);
}
const emitter = callProperties.call;
const interceptorArgs = {
clientInterceptors: this[INTERCEPTOR_SYMBOL],
clientInterceptorProviders: this[INTERCEPTOR_PROVIDER_SYMBOL],
callInterceptors: (_a = callProperties.callOptions.interceptors) !== null && _a !== void 0 ? _a : [],
callInterceptorProviders: (_b = callProperties.callOptions.interceptor_providers) !== null && _b !== void 0 ? _b : [],
};
const call = (0, client_interceptors_1.getInterceptingCall)(interceptorArgs, callProperties.methodDefinition, callProperties.callOptions, callProperties.channel);
/* This needs to happen before the emitter is used. Unfortunately we can't
* enforce this with the type system. We need to construct this emitter
* before calling the CallInvocationTransformer, and we need to create the
* call after that. */
emitter.call = call;
let responseMessage = null;
let receivedStatus = false;
let callerStackError = new Error();
call.start(callProperties.metadata, {
onReceiveMetadata: metadata => {
emitter.emit('metadata', metadata);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onReceiveMessage(message) {
if (responseMessage !== null) {
call.cancelWithStatus(constants_1.Status.UNIMPLEMENTED, 'Too many responses received');
}
responseMessage = message;
},
onReceiveStatus(status) {
if (receivedStatus) {
return;
}
receivedStatus = true;
if (status.code === constants_1.Status.OK) {
if (responseMessage === null) {
const callerStack = getErrorStackString(callerStackError);
callProperties.callback((0, call_1.callErrorFromStatus)({
code: constants_1.Status.UNIMPLEMENTED,
details: 'No message received',
metadata: status.metadata,
}, callerStack));
}
else {
callProperties.callback(null, responseMessage);
}
}
else {
const callerStack = getErrorStackString(callerStackError);
callProperties.callback((0, call_1.callErrorFromStatus)(status, callerStack));
}
/* Avoid retaining the callerStackError object in the call context of
* the status event handler. */
callerStackError = null;
emitter.emit('status', status);
},
});
call.sendMessage(argument);
call.halfClose();
return emitter;
}
makeClientStreamRequest(method, serialize, deserialize, metadata, options, callback) {
var _a, _b;
const checkedArguments = this.checkOptionalUnaryResponseArguments(metadata, options, callback);
const methodDefinition = {
path: method,
requestStream: true,
responseStream: false,
requestSerialize: serialize,
responseDeserialize: deserialize,
};
let callProperties = {
metadata: checkedArguments.metadata,
call: new call_1.ClientWritableStreamImpl(serialize),
channel: this[CHANNEL_SYMBOL],
methodDefinition: methodDefinition,
callOptions: checkedArguments.options,
callback: checkedArguments.callback,
};
if (this[CALL_INVOCATION_TRANSFORMER_SYMBOL]) {
callProperties = this[CALL_INVOCATION_TRANSFORMER_SYMBOL](callProperties);
}
const emitter = callProperties.call;
const interceptorArgs = {
clientInterceptors: this[INTERCEPTOR_SYMBOL],
clientInterceptorProviders: this[INTERCEPTOR_PROVIDER_SYMBOL],
callInterceptors: (_a = callProperties.callOptions.interceptors) !== null && _a !== void 0 ? _a : [],
callInterceptorProviders: (_b = callProperties.callOptions.interceptor_providers) !== null && _b !== void 0 ? _b : [],
};
const call = (0, client_interceptors_1.getInterceptingCall)(interceptorArgs, callProperties.methodDefinition, callProperties.callOptions, callProperties.channel);
/* This needs to happen before the emitter is used. Unfortunately we can't
* enforce this with the type system. We need to construct this emitter
* before calling the CallInvocationTransformer, and we need to create the
* call after that. */
emitter.call = call;
let responseMessage = null;
let receivedStatus = false;
let callerStackError = new Error();
call.start(callProperties.metadata, {
onReceiveMetadata: metadata => {
emitter.emit('metadata', metadata);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onReceiveMessage(message) {
if (responseMessage !== null) {
call.cancelWithStatus(constants_1.Status.UNIMPLEMENTED, 'Too many responses received');
}
responseMessage = message;
call.startRead();
},
onReceiveStatus(status) {
if (receivedStatus) {
return;
}
receivedStatus = true;
if (status.code === constants_1.Status.OK) {
if (responseMessage === null) {
const callerStack = getErrorStackString(callerStackError);
callProperties.callback((0, call_1.callErrorFromStatus)({
code: constants_1.Status.UNIMPLEMENTED,
details: 'No message received',
metadata: status.metadata,
}, callerStack));
}
else {
callProperties.callback(null, responseMessage);
}
}
else {
const callerStack = getErrorStackString(callerStackError);
callProperties.callback((0, call_1.callErrorFromStatus)(status, callerStack));
}
/* Avoid retaining the callerStackError object in the call context of
* the status event handler. */
callerStackError = null;
emitter.emit('status', status);
},
});
return emitter;
}
checkMetadataAndOptions(arg1, arg2) {
let metadata;
let options;
if (arg1 instanceof metadata_1.Metadata) {
metadata = arg1;
if (arg2) {
options = arg2;
}
else {
options = {};
}
}
else {
if (arg1) {
options = arg1;
}
else {
options = {};
}
metadata = new metadata_1.Metadata();
}
return { metadata, options };
}
makeServerStreamRequest(method, serialize, deserialize, argument, metadata, options) {
var _a, _b;
const checkedArguments = this.checkMetadataAndOptions(metadata, options);
const methodDefinition = {
path: method,
requestStream: false,
responseStream: true,
requestSerialize: serialize,
responseDeserialize: deserialize,
};
let callProperties = {
argument: argument,
metadata: checkedArguments.metadata,
call: new call_1.ClientReadableStreamImpl(deserialize),
channel: this[CHANNEL_SYMBOL],
methodDefinition: methodDefinition,
callOptions: checkedArguments.options,
};
if (this[CALL_INVOCATION_TRANSFORMER_SYMBOL]) {
callProperties = this[CALL_INVOCATION_TRANSFORMER_SYMBOL](callProperties);
}
const stream = callProperties.call;
const interceptorArgs = {
clientInterceptors: this[INTERCEPTOR_SYMBOL],
clientInterceptorProviders: this[INTERCEPTOR_PROVIDER_SYMBOL],
callInterceptors: (_a = callProperties.callOptions.interceptors) !== null && _a !== void 0 ? _a : [],
callInterceptorProviders: (_b = callProperties.callOptions.interceptor_providers) !== null && _b !== void 0 ? _b : [],
};
const call = (0, client_interceptors_1.getInterceptingCall)(interceptorArgs, callProperties.methodDefinition, callProperties.callOptions, callProperties.channel);
/* This needs to happen before the emitter is used. Unfortunately we can't
* enforce this with the type system. We need to construct this emitter
* before calling the CallInvocationTransformer, and we need to create the
* call after that. */
stream.call = call;
let receivedStatus = false;
let callerStackError = new Error();
call.start(callProperties.metadata, {
onReceiveMetadata(metadata) {
stream.emit('metadata', metadata);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onReceiveMessage(message) {
stream.push(message);
},
onReceiveStatus(status) {
if (receivedStatus) {
return;
}
receivedStatus = true;
stream.push(null);
if (status.code !== constants_1.Status.OK) {
const callerStack = getErrorStackString(callerStackError);
stream.emit('error', (0, call_1.callErrorFromStatus)(status, callerStack));
}
/* Avoid retaining the callerStackError object in the call context of
* the status event handler. */
callerStackError = null;
stream.emit('status', status);
},
});
call.sendMessage(argument);
call.halfClose();
return stream;
}
makeBidiStreamRequest(method, serialize, deserialize, metadata, options) {
var _a, _b;
const checkedArguments = this.checkMetadataAndOptions(metadata, options);
const methodDefinition = {
path: method,
requestStream: true,
responseStream: true,
requestSerialize: serialize,
responseDeserialize: deserialize,
};
let callProperties = {
metadata: checkedArguments.metadata,
call: new call_1.ClientDuplexStreamImpl(serialize, deserialize),
channel: this[CHANNEL_SYMBOL],
methodDefinition: methodDefinition,
callOptions: checkedArguments.options,
};
if (this[CALL_INVOCATION_TRANSFORMER_SYMBOL]) {
callProperties = this[CALL_INVOCATION_TRANSFORMER_SYMBOL](callProperties);
}
const stream = callProperties.call;
const interceptorArgs = {
clientInterceptors: this[INTERCEPTOR_SYMBOL],
clientInterceptorProviders: this[INTERCEPTOR_PROVIDER_SYMBOL],
callInterceptors: (_a = callProperties.callOptions.interceptors) !== null && _a !== void 0 ? _a : [],
callInterceptorProviders: (_b = callProperties.callOptions.interceptor_providers) !== null && _b !== void 0 ? _b : [],
};
const call = (0, client_interceptors_1.getInterceptingCall)(interceptorArgs, callProperties.methodDefinition, callProperties.callOptions, callProperties.channel);
/* This needs to happen before the emitter is used. Unfortunately we can't
* enforce this with the type system. We need to construct this emitter
* before calling the CallInvocationTransformer, and we need to create the
* call after that. */
stream.call = call;
let receivedStatus = false;
let callerStackError = new Error();
call.start(callProperties.metadata, {
onReceiveMetadata(metadata) {
stream.emit('metadata', metadata);
},
onReceiveMessage(message) {
stream.push(message);
},
onReceiveStatus(status) {
if (receivedStatus) {
return;
}
receivedStatus = true;
stream.push(null);
if (status.code !== constants_1.Status.OK) {
const callerStack = getErrorStackString(callerStackError);
stream.emit('error', (0, call_1.callErrorFromStatus)(status, callerStack));
}
/* Avoid retaining the callerStackError object in the call context of
* the status event handler. */
callerStackError = null;
stream.emit('status', status);
},
});
return stream;
}
}
exports.Client = Client;
//# sourceMappingURL=client.js.map

View File

@@ -0,0 +1,26 @@
"use strict";
/*
* Copyright 2021 gRPC authors.
*
* Licensed 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.CompressionAlgorithms = void 0;
var CompressionAlgorithms;
(function (CompressionAlgorithms) {
CompressionAlgorithms[CompressionAlgorithms["identity"] = 0] = "identity";
CompressionAlgorithms[CompressionAlgorithms["deflate"] = 1] = "deflate";
CompressionAlgorithms[CompressionAlgorithms["gzip"] = 2] = "gzip";
})(CompressionAlgorithms || (exports.CompressionAlgorithms = CompressionAlgorithms = {}));
//# sourceMappingURL=compression-algorithms.js.map

View File

@@ -0,0 +1,295 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.CompressionFilterFactory = exports.CompressionFilter = void 0;
const zlib = require("zlib");
const compression_algorithms_1 = require("./compression-algorithms");
const constants_1 = require("./constants");
const filter_1 = require("./filter");
const logging = require("./logging");
const isCompressionAlgorithmKey = (key) => {
return (typeof key === 'number' && typeof compression_algorithms_1.CompressionAlgorithms[key] === 'string');
};
class CompressionHandler {
/**
* @param message Raw uncompressed message bytes
* @param compress Indicates whether the message should be compressed
* @return Framed message, compressed if applicable
*/
async writeMessage(message, compress) {
let messageBuffer = message;
if (compress) {
messageBuffer = await this.compressMessage(messageBuffer);
}
const output = Buffer.allocUnsafe(messageBuffer.length + 5);
output.writeUInt8(compress ? 1 : 0, 0);
output.writeUInt32BE(messageBuffer.length, 1);
messageBuffer.copy(output, 5);
return output;
}
/**
* @param data Framed message, possibly compressed
* @return Uncompressed message
*/
async readMessage(data) {
const compressed = data.readUInt8(0) === 1;
let messageBuffer = data.slice(5);
if (compressed) {
messageBuffer = await this.decompressMessage(messageBuffer);
}
return messageBuffer;
}
}
class IdentityHandler extends CompressionHandler {
async compressMessage(message) {
return message;
}
async writeMessage(message, compress) {
const output = Buffer.allocUnsafe(message.length + 5);
/* With "identity" compression, messages should always be marked as
* uncompressed */
output.writeUInt8(0, 0);
output.writeUInt32BE(message.length, 1);
message.copy(output, 5);
return output;
}
decompressMessage(message) {
return Promise.reject(new Error('Received compressed message but "grpc-encoding" header was identity'));
}
}
class DeflateHandler extends CompressionHandler {
constructor(maxRecvMessageLength) {
super();
this.maxRecvMessageLength = maxRecvMessageLength;
}
compressMessage(message) {
return new Promise((resolve, reject) => {
zlib.deflate(message, (err, output) => {
if (err) {
reject(err);
}
else {
resolve(output);
}
});
});
}
decompressMessage(message) {
return new Promise((resolve, reject) => {
let totalLength = 0;
const messageParts = [];
const decompresser = zlib.createInflate();
decompresser.on('data', (chunk) => {
messageParts.push(chunk);
totalLength += chunk.byteLength;
if (this.maxRecvMessageLength !== -1 && totalLength > this.maxRecvMessageLength) {
decompresser.destroy();
reject({
code: constants_1.Status.RESOURCE_EXHAUSTED,
details: `Received message that decompresses to a size larger than ${this.maxRecvMessageLength}`
});
}
});
decompresser.on('end', () => {
resolve(Buffer.concat(messageParts));
});
decompresser.write(message);
decompresser.end();
});
}
}
class GzipHandler extends CompressionHandler {
constructor(maxRecvMessageLength) {
super();
this.maxRecvMessageLength = maxRecvMessageLength;
}
compressMessage(message) {
return new Promise((resolve, reject) => {
zlib.gzip(message, (err, output) => {
if (err) {
reject(err);
}
else {
resolve(output);
}
});
});
}
decompressMessage(message) {
return new Promise((resolve, reject) => {
let totalLength = 0;
const messageParts = [];
const decompresser = zlib.createGunzip();
decompresser.on('data', (chunk) => {
messageParts.push(chunk);
totalLength += chunk.byteLength;
if (this.maxRecvMessageLength !== -1 && totalLength > this.maxRecvMessageLength) {
decompresser.destroy();
reject({
code: constants_1.Status.RESOURCE_EXHAUSTED,
details: `Received message that decompresses to a size larger than ${this.maxRecvMessageLength}`
});
}
});
decompresser.on('end', () => {
resolve(Buffer.concat(messageParts));
});
decompresser.write(message);
decompresser.end();
});
}
}
class UnknownHandler extends CompressionHandler {
constructor(compressionName) {
super();
this.compressionName = compressionName;
}
compressMessage(message) {
return Promise.reject(new Error(`Received message compressed with unsupported compression method ${this.compressionName}`));
}
decompressMessage(message) {
// This should be unreachable
return Promise.reject(new Error(`Compression method not supported: ${this.compressionName}`));
}
}
function getCompressionHandler(compressionName, maxReceiveMessageSize) {
switch (compressionName) {
case 'identity':
return new IdentityHandler();
case 'deflate':
return new DeflateHandler(maxReceiveMessageSize);
case 'gzip':
return new GzipHandler(maxReceiveMessageSize);
default:
return new UnknownHandler(compressionName);
}
}
class CompressionFilter extends filter_1.BaseFilter {
constructor(channelOptions, sharedFilterConfig) {
var _a, _b, _c;
super();
this.sharedFilterConfig = sharedFilterConfig;
this.sendCompression = new IdentityHandler();
this.receiveCompression = new IdentityHandler();
this.currentCompressionAlgorithm = 'identity';
const compressionAlgorithmKey = channelOptions['grpc.default_compression_algorithm'];
this.maxReceiveMessageLength = (_a = channelOptions['grpc.max_receive_message_length']) !== null && _a !== void 0 ? _a : constants_1.DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH;
this.maxSendMessageLength = (_b = channelOptions['grpc.max_send_message_length']) !== null && _b !== void 0 ? _b : constants_1.DEFAULT_MAX_SEND_MESSAGE_LENGTH;
if (compressionAlgorithmKey !== undefined) {
if (isCompressionAlgorithmKey(compressionAlgorithmKey)) {
const clientSelectedEncoding = compression_algorithms_1.CompressionAlgorithms[compressionAlgorithmKey];
const serverSupportedEncodings = (_c = sharedFilterConfig.serverSupportedEncodingHeader) === null || _c === void 0 ? void 0 : _c.split(',');
/**
* There are two possible situations here:
* 1) We don't have any info yet from the server about what compression it supports
* In that case we should just use what the client tells us to use
* 2) We've previously received a response from the server including a grpc-accept-encoding header
* In that case we only want to use the encoding chosen by the client if the server supports it
*/
if (!serverSupportedEncodings ||
serverSupportedEncodings.includes(clientSelectedEncoding)) {
this.currentCompressionAlgorithm = clientSelectedEncoding;
this.sendCompression = getCompressionHandler(this.currentCompressionAlgorithm, -1);
}
}
else {
logging.log(constants_1.LogVerbosity.ERROR, `Invalid value provided for grpc.default_compression_algorithm option: ${compressionAlgorithmKey}`);
}
}
}
async sendMetadata(metadata) {
const headers = await metadata;
headers.set('grpc-accept-encoding', 'identity,deflate,gzip');
headers.set('accept-encoding', 'identity');
// No need to send the header if it's "identity" - behavior is identical; save the bandwidth
if (this.currentCompressionAlgorithm === 'identity') {
headers.remove('grpc-encoding');
}
else {
headers.set('grpc-encoding', this.currentCompressionAlgorithm);
}
return headers;
}
receiveMetadata(metadata) {
const receiveEncoding = metadata.get('grpc-encoding');
if (receiveEncoding.length > 0) {
const encoding = receiveEncoding[0];
if (typeof encoding === 'string') {
this.receiveCompression = getCompressionHandler(encoding, this.maxReceiveMessageLength);
}
}
metadata.remove('grpc-encoding');
/* Check to see if the compression we're using to send messages is supported by the server
* If not, reset the sendCompression filter and have it use the default IdentityHandler */
const serverSupportedEncodingsHeader = metadata.get('grpc-accept-encoding')[0];
if (serverSupportedEncodingsHeader) {
this.sharedFilterConfig.serverSupportedEncodingHeader =
serverSupportedEncodingsHeader;
const serverSupportedEncodings = serverSupportedEncodingsHeader.split(',');
if (!serverSupportedEncodings.includes(this.currentCompressionAlgorithm)) {
this.sendCompression = new IdentityHandler();
this.currentCompressionAlgorithm = 'identity';
}
}
metadata.remove('grpc-accept-encoding');
return metadata;
}
async sendMessage(message) {
var _a;
/* This filter is special. The input message is the bare message bytes,
* and the output is a framed and possibly compressed message. For this
* reason, this filter should be at the bottom of the filter stack */
const resolvedMessage = await message;
if (this.maxSendMessageLength !== -1 && resolvedMessage.message.length > this.maxSendMessageLength) {
throw {
code: constants_1.Status.RESOURCE_EXHAUSTED,
details: `Attempted to send message with a size larger than ${this.maxSendMessageLength}`
};
}
let compress;
if (this.sendCompression instanceof IdentityHandler) {
compress = false;
}
else {
compress = (((_a = resolvedMessage.flags) !== null && _a !== void 0 ? _a : 0) & 2 /* WriteFlags.NoCompress */) === 0;
}
return {
message: await this.sendCompression.writeMessage(resolvedMessage.message, compress),
flags: resolvedMessage.flags,
};
}
async receiveMessage(message) {
/* This filter is also special. The input message is framed and possibly
* compressed, and the output message is deframed and uncompressed. So
* this is another reason that this filter should be at the bottom of the
* filter stack. */
return this.receiveCompression.readMessage(await message);
}
}
exports.CompressionFilter = CompressionFilter;
class CompressionFilterFactory {
constructor(channel, options) {
this.options = options;
this.sharedFilterConfig = {};
}
createFilter() {
return new CompressionFilter(this.options, this.sharedFilterConfig);
}
}
exports.CompressionFilterFactory = CompressionFilterFactory;
//# sourceMappingURL=compression-filter.js.map

View File

@@ -0,0 +1,28 @@
"use strict";
/*
* Copyright 2021 gRPC authors.
*
* Licensed 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.ConnectivityState = void 0;
var ConnectivityState;
(function (ConnectivityState) {
ConnectivityState[ConnectivityState["IDLE"] = 0] = "IDLE";
ConnectivityState[ConnectivityState["CONNECTING"] = 1] = "CONNECTING";
ConnectivityState[ConnectivityState["READY"] = 2] = "READY";
ConnectivityState[ConnectivityState["TRANSIENT_FAILURE"] = 3] = "TRANSIENT_FAILURE";
ConnectivityState[ConnectivityState["SHUTDOWN"] = 4] = "SHUTDOWN";
})(ConnectivityState || (exports.ConnectivityState = ConnectivityState = {}));
//# sourceMappingURL=connectivity-state.js.map

View File

@@ -0,0 +1,64 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH = exports.DEFAULT_MAX_SEND_MESSAGE_LENGTH = exports.Propagate = exports.LogVerbosity = exports.Status = void 0;
var Status;
(function (Status) {
Status[Status["OK"] = 0] = "OK";
Status[Status["CANCELLED"] = 1] = "CANCELLED";
Status[Status["UNKNOWN"] = 2] = "UNKNOWN";
Status[Status["INVALID_ARGUMENT"] = 3] = "INVALID_ARGUMENT";
Status[Status["DEADLINE_EXCEEDED"] = 4] = "DEADLINE_EXCEEDED";
Status[Status["NOT_FOUND"] = 5] = "NOT_FOUND";
Status[Status["ALREADY_EXISTS"] = 6] = "ALREADY_EXISTS";
Status[Status["PERMISSION_DENIED"] = 7] = "PERMISSION_DENIED";
Status[Status["RESOURCE_EXHAUSTED"] = 8] = "RESOURCE_EXHAUSTED";
Status[Status["FAILED_PRECONDITION"] = 9] = "FAILED_PRECONDITION";
Status[Status["ABORTED"] = 10] = "ABORTED";
Status[Status["OUT_OF_RANGE"] = 11] = "OUT_OF_RANGE";
Status[Status["UNIMPLEMENTED"] = 12] = "UNIMPLEMENTED";
Status[Status["INTERNAL"] = 13] = "INTERNAL";
Status[Status["UNAVAILABLE"] = 14] = "UNAVAILABLE";
Status[Status["DATA_LOSS"] = 15] = "DATA_LOSS";
Status[Status["UNAUTHENTICATED"] = 16] = "UNAUTHENTICATED";
})(Status || (exports.Status = Status = {}));
var LogVerbosity;
(function (LogVerbosity) {
LogVerbosity[LogVerbosity["DEBUG"] = 0] = "DEBUG";
LogVerbosity[LogVerbosity["INFO"] = 1] = "INFO";
LogVerbosity[LogVerbosity["ERROR"] = 2] = "ERROR";
LogVerbosity[LogVerbosity["NONE"] = 3] = "NONE";
})(LogVerbosity || (exports.LogVerbosity = LogVerbosity = {}));
/**
* NOTE: This enum is not currently used in any implemented API in this
* library. It is included only for type parity with the other implementation.
*/
var Propagate;
(function (Propagate) {
Propagate[Propagate["DEADLINE"] = 1] = "DEADLINE";
Propagate[Propagate["CENSUS_STATS_CONTEXT"] = 2] = "CENSUS_STATS_CONTEXT";
Propagate[Propagate["CENSUS_TRACING_CONTEXT"] = 4] = "CENSUS_TRACING_CONTEXT";
Propagate[Propagate["CANCELLATION"] = 8] = "CANCELLATION";
// https://github.com/grpc/grpc/blob/master/include/grpc/impl/codegen/propagation_bits.h#L43
Propagate[Propagate["DEFAULTS"] = 65535] = "DEFAULTS";
})(Propagate || (exports.Propagate = Propagate = {}));
// -1 means unlimited
exports.DEFAULT_MAX_SEND_MESSAGE_LENGTH = -1;
// 4 MB default
exports.DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH = 4 * 1024 * 1024;
//# sourceMappingURL=constants.js.map

View File

@@ -0,0 +1,42 @@
"use strict";
/*
* Copyright 2022 gRPC authors.
*
* Licensed 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.restrictControlPlaneStatusCode = restrictControlPlaneStatusCode;
const constants_1 = require("./constants");
const INAPPROPRIATE_CONTROL_PLANE_CODES = [
constants_1.Status.OK,
constants_1.Status.INVALID_ARGUMENT,
constants_1.Status.NOT_FOUND,
constants_1.Status.ALREADY_EXISTS,
constants_1.Status.FAILED_PRECONDITION,
constants_1.Status.ABORTED,
constants_1.Status.OUT_OF_RANGE,
constants_1.Status.DATA_LOSS,
];
function restrictControlPlaneStatusCode(code, details) {
if (INAPPROPRIATE_CONTROL_PLANE_CODES.includes(code)) {
return {
code: constants_1.Status.INTERNAL,
details: `Invalid status from control plane: ${code} ${constants_1.Status[code]} ${details}`,
};
}
else {
return { code, details };
}
}
//# sourceMappingURL=control-plane-status.js.map

View File

@@ -0,0 +1,108 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.minDeadline = minDeadline;
exports.getDeadlineTimeoutString = getDeadlineTimeoutString;
exports.getRelativeTimeout = getRelativeTimeout;
exports.deadlineToString = deadlineToString;
exports.formatDateDifference = formatDateDifference;
function minDeadline(...deadlineList) {
let minValue = Infinity;
for (const deadline of deadlineList) {
const deadlineMsecs = deadline instanceof Date ? deadline.getTime() : deadline;
if (deadlineMsecs < minValue) {
minValue = deadlineMsecs;
}
}
return minValue;
}
const units = [
['m', 1],
['S', 1000],
['M', 60 * 1000],
['H', 60 * 60 * 1000],
];
function getDeadlineTimeoutString(deadline) {
const now = new Date().getTime();
if (deadline instanceof Date) {
deadline = deadline.getTime();
}
const timeoutMs = Math.max(deadline - now, 0);
for (const [unit, factor] of units) {
const amount = timeoutMs / factor;
if (amount < 1e8) {
return String(Math.ceil(amount)) + unit;
}
}
throw new Error('Deadline is too far in the future');
}
/**
* See https://nodejs.org/api/timers.html#settimeoutcallback-delay-args
* In particular, "When delay is larger than 2147483647 or less than 1, the
* delay will be set to 1. Non-integer delays are truncated to an integer."
* This number of milliseconds is almost 25 days.
*/
const MAX_TIMEOUT_TIME = 2147483647;
/**
* Get the timeout value that should be passed to setTimeout now for the timer
* to end at the deadline. For any deadline before now, the timer should end
* immediately, represented by a value of 0. For any deadline more than
* MAX_TIMEOUT_TIME milliseconds in the future, a timer cannot be set that will
* end at that time, so it is treated as infinitely far in the future.
* @param deadline
* @returns
*/
function getRelativeTimeout(deadline) {
const deadlineMs = deadline instanceof Date ? deadline.getTime() : deadline;
const now = new Date().getTime();
const timeout = deadlineMs - now;
if (timeout < 0) {
return 0;
}
else if (timeout > MAX_TIMEOUT_TIME) {
return Infinity;
}
else {
return timeout;
}
}
function deadlineToString(deadline) {
if (deadline instanceof Date) {
return deadline.toISOString();
}
else {
const dateDeadline = new Date(deadline);
if (Number.isNaN(dateDeadline.getTime())) {
return '' + deadline;
}
else {
return dateDeadline.toISOString();
}
}
}
/**
* Calculate the difference between two dates as a number of seconds and format
* it as a string.
* @param startDate
* @param endDate
* @returns
*/
function formatDateDifference(startDate, endDate) {
return ((endDate.getTime() - startDate.getTime()) / 1000).toFixed(3) + 's';
}
//# sourceMappingURL=deadline.js.map

View File

@@ -0,0 +1,74 @@
"use strict";
/*
* Copyright 2022 gRPC authors.
*
* Licensed 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.durationMessageToDuration = durationMessageToDuration;
exports.msToDuration = msToDuration;
exports.durationToMs = durationToMs;
exports.isDuration = isDuration;
exports.isDurationMessage = isDurationMessage;
exports.parseDuration = parseDuration;
exports.durationToString = durationToString;
function durationMessageToDuration(message) {
return {
seconds: Number.parseInt(message.seconds),
nanos: message.nanos
};
}
function msToDuration(millis) {
return {
seconds: (millis / 1000) | 0,
nanos: ((millis % 1000) * 1000000) | 0,
};
}
function durationToMs(duration) {
return (duration.seconds * 1000 + duration.nanos / 1000000) | 0;
}
function isDuration(value) {
return typeof value.seconds === 'number' && typeof value.nanos === 'number';
}
function isDurationMessage(value) {
return typeof value.seconds === 'string' && typeof value.nanos === 'number';
}
const durationRegex = /^(\d+)(?:\.(\d+))?s$/;
function parseDuration(value) {
const match = value.match(durationRegex);
if (!match) {
return null;
}
return {
seconds: Number.parseInt(match[1], 10),
nanos: match[2] ? Number.parseInt(match[2].padEnd(9, '0'), 10) : 0
};
}
function durationToString(duration) {
if (duration.nanos === 0) {
return `${duration.seconds}s`;
}
let scaleFactor;
if (duration.nanos % 1000000 === 0) {
scaleFactor = 1000000;
}
else if (duration.nanos % 1000 === 0) {
scaleFactor = 1000;
}
else {
scaleFactor = 1;
}
return `${duration.seconds}.${duration.nanos / scaleFactor}s`;
}
//# sourceMappingURL=duration.js.map

View File

@@ -0,0 +1,22 @@
"use strict";
/*
* Copyright 2024 gRPC authors.
*
* Licensed 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 });
exports.GRPC_NODE_USE_ALTERNATIVE_RESOLVER = void 0;
exports.GRPC_NODE_USE_ALTERNATIVE_RESOLVER = ((_a = process.env.GRPC_NODE_USE_ALTERNATIVE_RESOLVER) !== null && _a !== void 0 ? _a : 'false') === 'true';
//# sourceMappingURL=environment.js.map

View File

@@ -0,0 +1,40 @@
"use strict";
/*
* Copyright 2022 gRPC authors.
*
* Licensed 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.getErrorMessage = getErrorMessage;
exports.getErrorCode = getErrorCode;
function getErrorMessage(error) {
if (error instanceof Error) {
return error.message;
}
else {
return String(error);
}
}
function getErrorCode(error) {
if (typeof error === 'object' &&
error !== null &&
'code' in error &&
typeof error.code === 'number') {
return error.code;
}
else {
return null;
}
}
//# sourceMappingURL=error.js.map

View File

@@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SUBCHANNEL_ARGS_EXCLUDE_KEY_PREFIX = exports.createCertificateProviderChannelCredentials = exports.FileWatcherCertificateProvider = exports.createCertificateProviderServerCredentials = exports.createServerCredentialsWithInterceptors = exports.BaseSubchannelWrapper = exports.registerAdminService = exports.FilterStackFactory = exports.BaseFilter = exports.statusOrFromError = exports.statusOrFromValue = exports.PickResultType = exports.QueuePicker = exports.UnavailablePicker = exports.ChildLoadBalancerHandler = exports.EndpointMap = exports.endpointHasAddress = exports.endpointToString = exports.subchannelAddressToString = exports.LeafLoadBalancer = exports.isLoadBalancerNameRegistered = exports.parseLoadBalancingConfig = exports.selectLbConfigFromList = exports.registerLoadBalancerType = exports.createChildChannelControlHelper = exports.BackoffTimeout = exports.parseDuration = exports.durationToMs = exports.splitHostPort = exports.uriToString = exports.CHANNEL_ARGS_CONFIG_SELECTOR_KEY = exports.createResolver = exports.registerResolver = exports.log = exports.trace = void 0;
var logging_1 = require("./logging");
Object.defineProperty(exports, "trace", { enumerable: true, get: function () { return logging_1.trace; } });
Object.defineProperty(exports, "log", { enumerable: true, get: function () { return logging_1.log; } });
var resolver_1 = require("./resolver");
Object.defineProperty(exports, "registerResolver", { enumerable: true, get: function () { return resolver_1.registerResolver; } });
Object.defineProperty(exports, "createResolver", { enumerable: true, get: function () { return resolver_1.createResolver; } });
Object.defineProperty(exports, "CHANNEL_ARGS_CONFIG_SELECTOR_KEY", { enumerable: true, get: function () { return resolver_1.CHANNEL_ARGS_CONFIG_SELECTOR_KEY; } });
var uri_parser_1 = require("./uri-parser");
Object.defineProperty(exports, "uriToString", { enumerable: true, get: function () { return uri_parser_1.uriToString; } });
Object.defineProperty(exports, "splitHostPort", { enumerable: true, get: function () { return uri_parser_1.splitHostPort; } });
var duration_1 = require("./duration");
Object.defineProperty(exports, "durationToMs", { enumerable: true, get: function () { return duration_1.durationToMs; } });
Object.defineProperty(exports, "parseDuration", { enumerable: true, get: function () { return duration_1.parseDuration; } });
var backoff_timeout_1 = require("./backoff-timeout");
Object.defineProperty(exports, "BackoffTimeout", { enumerable: true, get: function () { return backoff_timeout_1.BackoffTimeout; } });
var load_balancer_1 = require("./load-balancer");
Object.defineProperty(exports, "createChildChannelControlHelper", { enumerable: true, get: function () { return load_balancer_1.createChildChannelControlHelper; } });
Object.defineProperty(exports, "registerLoadBalancerType", { enumerable: true, get: function () { return load_balancer_1.registerLoadBalancerType; } });
Object.defineProperty(exports, "selectLbConfigFromList", { enumerable: true, get: function () { return load_balancer_1.selectLbConfigFromList; } });
Object.defineProperty(exports, "parseLoadBalancingConfig", { enumerable: true, get: function () { return load_balancer_1.parseLoadBalancingConfig; } });
Object.defineProperty(exports, "isLoadBalancerNameRegistered", { enumerable: true, get: function () { return load_balancer_1.isLoadBalancerNameRegistered; } });
var load_balancer_pick_first_1 = require("./load-balancer-pick-first");
Object.defineProperty(exports, "LeafLoadBalancer", { enumerable: true, get: function () { return load_balancer_pick_first_1.LeafLoadBalancer; } });
var subchannel_address_1 = require("./subchannel-address");
Object.defineProperty(exports, "subchannelAddressToString", { enumerable: true, get: function () { return subchannel_address_1.subchannelAddressToString; } });
Object.defineProperty(exports, "endpointToString", { enumerable: true, get: function () { return subchannel_address_1.endpointToString; } });
Object.defineProperty(exports, "endpointHasAddress", { enumerable: true, get: function () { return subchannel_address_1.endpointHasAddress; } });
Object.defineProperty(exports, "EndpointMap", { enumerable: true, get: function () { return subchannel_address_1.EndpointMap; } });
var load_balancer_child_handler_1 = require("./load-balancer-child-handler");
Object.defineProperty(exports, "ChildLoadBalancerHandler", { enumerable: true, get: function () { return load_balancer_child_handler_1.ChildLoadBalancerHandler; } });
var picker_1 = require("./picker");
Object.defineProperty(exports, "UnavailablePicker", { enumerable: true, get: function () { return picker_1.UnavailablePicker; } });
Object.defineProperty(exports, "QueuePicker", { enumerable: true, get: function () { return picker_1.QueuePicker; } });
Object.defineProperty(exports, "PickResultType", { enumerable: true, get: function () { return picker_1.PickResultType; } });
var call_interface_1 = require("./call-interface");
Object.defineProperty(exports, "statusOrFromValue", { enumerable: true, get: function () { return call_interface_1.statusOrFromValue; } });
Object.defineProperty(exports, "statusOrFromError", { enumerable: true, get: function () { return call_interface_1.statusOrFromError; } });
var filter_1 = require("./filter");
Object.defineProperty(exports, "BaseFilter", { enumerable: true, get: function () { return filter_1.BaseFilter; } });
var filter_stack_1 = require("./filter-stack");
Object.defineProperty(exports, "FilterStackFactory", { enumerable: true, get: function () { return filter_stack_1.FilterStackFactory; } });
var admin_1 = require("./admin");
Object.defineProperty(exports, "registerAdminService", { enumerable: true, get: function () { return admin_1.registerAdminService; } });
var subchannel_interface_1 = require("./subchannel-interface");
Object.defineProperty(exports, "BaseSubchannelWrapper", { enumerable: true, get: function () { return subchannel_interface_1.BaseSubchannelWrapper; } });
var server_credentials_1 = require("./server-credentials");
Object.defineProperty(exports, "createServerCredentialsWithInterceptors", { enumerable: true, get: function () { return server_credentials_1.createServerCredentialsWithInterceptors; } });
Object.defineProperty(exports, "createCertificateProviderServerCredentials", { enumerable: true, get: function () { return server_credentials_1.createCertificateProviderServerCredentials; } });
var certificate_provider_1 = require("./certificate-provider");
Object.defineProperty(exports, "FileWatcherCertificateProvider", { enumerable: true, get: function () { return certificate_provider_1.FileWatcherCertificateProvider; } });
var channel_credentials_1 = require("./channel-credentials");
Object.defineProperty(exports, "createCertificateProviderChannelCredentials", { enumerable: true, get: function () { return channel_credentials_1.createCertificateProviderChannelCredentials; } });
var internal_channel_1 = require("./internal-channel");
Object.defineProperty(exports, "SUBCHANNEL_ARGS_EXCLUDE_KEY_PREFIX", { enumerable: true, get: function () { return internal_channel_1.SUBCHANNEL_ARGS_EXCLUDE_KEY_PREFIX; } });
//# sourceMappingURL=experimental.js.map

View File

@@ -0,0 +1,82 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.FilterStackFactory = exports.FilterStack = void 0;
class FilterStack {
constructor(filters) {
this.filters = filters;
}
sendMetadata(metadata) {
let result = metadata;
for (let i = 0; i < this.filters.length; i++) {
result = this.filters[i].sendMetadata(result);
}
return result;
}
receiveMetadata(metadata) {
let result = metadata;
for (let i = this.filters.length - 1; i >= 0; i--) {
result = this.filters[i].receiveMetadata(result);
}
return result;
}
sendMessage(message) {
let result = message;
for (let i = 0; i < this.filters.length; i++) {
result = this.filters[i].sendMessage(result);
}
return result;
}
receiveMessage(message) {
let result = message;
for (let i = this.filters.length - 1; i >= 0; i--) {
result = this.filters[i].receiveMessage(result);
}
return result;
}
receiveTrailers(status) {
let result = status;
for (let i = this.filters.length - 1; i >= 0; i--) {
result = this.filters[i].receiveTrailers(result);
}
return result;
}
push(filters) {
this.filters.unshift(...filters);
}
getFilters() {
return this.filters;
}
}
exports.FilterStack = FilterStack;
class FilterStackFactory {
constructor(factories) {
this.factories = factories;
}
push(filterFactories) {
this.factories.unshift(...filterFactories);
}
clone() {
return new FilterStackFactory([...this.factories]);
}
createFilter() {
return new FilterStack(this.factories.map(factory => factory.createFilter()));
}
}
exports.FilterStackFactory = FilterStackFactory;
//# sourceMappingURL=filter-stack.js.map

View File

@@ -0,0 +1,38 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.BaseFilter = void 0;
class BaseFilter {
async sendMetadata(metadata) {
return metadata;
}
receiveMetadata(metadata) {
return metadata;
}
async sendMessage(message) {
return message;
}
async receiveMessage(message) {
return message;
}
receiveTrailers(status) {
return status;
}
}
exports.BaseFilter = BaseFilter;
//# sourceMappingURL=filter.js.map

View File

@@ -0,0 +1,274 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.parseCIDR = parseCIDR;
exports.mapProxyName = mapProxyName;
exports.getProxiedConnection = getProxiedConnection;
const logging_1 = require("./logging");
const constants_1 = require("./constants");
const net_1 = require("net");
const http = require("http");
const logging = require("./logging");
const subchannel_address_1 = require("./subchannel-address");
const uri_parser_1 = require("./uri-parser");
const url_1 = require("url");
const resolver_dns_1 = require("./resolver-dns");
const TRACER_NAME = 'proxy';
function trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, text);
}
function getProxyInfo() {
let proxyEnv = '';
let envVar = '';
/* Prefer using 'grpc_proxy'. Fallback on 'http_proxy' if it is not set.
* Also prefer using 'https_proxy' with fallback on 'http_proxy'. The
* fallback behavior can be removed if there's a demand for it.
*/
if (process.env.grpc_proxy) {
envVar = 'grpc_proxy';
proxyEnv = process.env.grpc_proxy;
}
else if (process.env.https_proxy) {
envVar = 'https_proxy';
proxyEnv = process.env.https_proxy;
}
else if (process.env.http_proxy) {
envVar = 'http_proxy';
proxyEnv = process.env.http_proxy;
}
else {
return {};
}
let proxyUrl;
try {
proxyUrl = new url_1.URL(proxyEnv);
}
catch (e) {
(0, logging_1.log)(constants_1.LogVerbosity.ERROR, `cannot parse value of "${envVar}" env var`);
return {};
}
if (proxyUrl.protocol !== 'http:') {
(0, logging_1.log)(constants_1.LogVerbosity.ERROR, `"${proxyUrl.protocol}" scheme not supported in proxy URI`);
return {};
}
let userCred = null;
if (proxyUrl.username) {
if (proxyUrl.password) {
(0, logging_1.log)(constants_1.LogVerbosity.INFO, 'userinfo found in proxy URI');
userCred = decodeURIComponent(`${proxyUrl.username}:${proxyUrl.password}`);
}
else {
userCred = proxyUrl.username;
}
}
const hostname = proxyUrl.hostname;
let port = proxyUrl.port;
/* The proxy URL uses the scheme "http:", which has a default port number of
* 80. We need to set that explicitly here if it is omitted because otherwise
* it will use gRPC's default port 443. */
if (port === '') {
port = '80';
}
const result = {
address: `${hostname}:${port}`,
};
if (userCred) {
result.creds = userCred;
}
trace('Proxy server ' + result.address + ' set by environment variable ' + envVar);
return result;
}
function getNoProxyHostList() {
/* Prefer using 'no_grpc_proxy'. Fallback on 'no_proxy' if it is not set. */
let noProxyStr = process.env.no_grpc_proxy;
let envVar = 'no_grpc_proxy';
if (!noProxyStr) {
noProxyStr = process.env.no_proxy;
envVar = 'no_proxy';
}
if (noProxyStr) {
trace('No proxy server list set by environment variable ' + envVar);
return noProxyStr.split(',');
}
else {
return [];
}
}
/*
* The groups correspond to CIDR parts as follows:
* 1. ip
* 2. prefixLength
*/
function parseCIDR(cidrString) {
const splitRange = cidrString.split('/');
if (splitRange.length !== 2) {
return null;
}
const prefixLength = parseInt(splitRange[1], 10);
if (!(0, net_1.isIPv4)(splitRange[0]) || Number.isNaN(prefixLength) || prefixLength < 0 || prefixLength > 32) {
return null;
}
return {
ip: ipToInt(splitRange[0]),
prefixLength: prefixLength
};
}
function ipToInt(ip) {
return ip.split(".").reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0);
}
function isIpInCIDR(cidr, serverHost) {
const ip = cidr.ip;
const mask = -1 << (32 - cidr.prefixLength);
const hostIP = ipToInt(serverHost);
return (hostIP & mask) === (ip & mask);
}
function hostMatchesNoProxyList(serverHost) {
for (const host of getNoProxyHostList()) {
const parsedCIDR = parseCIDR(host);
// host is a CIDR and serverHost is an IP address
if ((0, net_1.isIPv4)(serverHost) && parsedCIDR && isIpInCIDR(parsedCIDR, serverHost)) {
return true;
}
else if (serverHost.endsWith(host)) {
// host is a single IP or a domain name suffix
return true;
}
}
return false;
}
function mapProxyName(target, options) {
var _a;
const noProxyResult = {
target: target,
extraOptions: {},
};
if (((_a = options['grpc.enable_http_proxy']) !== null && _a !== void 0 ? _a : 1) === 0) {
return noProxyResult;
}
if (target.scheme === 'unix') {
return noProxyResult;
}
const proxyInfo = getProxyInfo();
if (!proxyInfo.address) {
return noProxyResult;
}
const hostPort = (0, uri_parser_1.splitHostPort)(target.path);
if (!hostPort) {
return noProxyResult;
}
const serverHost = hostPort.host;
if (hostMatchesNoProxyList(serverHost)) {
trace('Not using proxy for target in no_proxy list: ' + (0, uri_parser_1.uriToString)(target));
return noProxyResult;
}
const extraOptions = {
'grpc.http_connect_target': (0, uri_parser_1.uriToString)(target),
};
if (proxyInfo.creds) {
extraOptions['grpc.http_connect_creds'] = proxyInfo.creds;
}
return {
target: {
scheme: 'dns',
path: proxyInfo.address,
},
extraOptions: extraOptions,
};
}
function getProxiedConnection(address, channelOptions) {
var _a;
if (!('grpc.http_connect_target' in channelOptions)) {
return Promise.resolve(null);
}
const realTarget = channelOptions['grpc.http_connect_target'];
const parsedTarget = (0, uri_parser_1.parseUri)(realTarget);
if (parsedTarget === null) {
return Promise.resolve(null);
}
const splitHostPost = (0, uri_parser_1.splitHostPort)(parsedTarget.path);
if (splitHostPost === null) {
return Promise.resolve(null);
}
const hostPort = `${splitHostPost.host}:${(_a = splitHostPost.port) !== null && _a !== void 0 ? _a : resolver_dns_1.DEFAULT_PORT}`;
const options = {
method: 'CONNECT',
path: hostPort,
};
const headers = {
Host: hostPort,
};
// Connect to the subchannel address as a proxy
if ((0, subchannel_address_1.isTcpSubchannelAddress)(address)) {
options.host = address.host;
options.port = address.port;
}
else {
options.socketPath = address.path;
}
if ('grpc.http_connect_creds' in channelOptions) {
headers['Proxy-Authorization'] =
'Basic ' +
Buffer.from(channelOptions['grpc.http_connect_creds']).toString('base64');
}
options.headers = headers;
const proxyAddressString = (0, subchannel_address_1.subchannelAddressToString)(address);
trace('Using proxy ' + proxyAddressString + ' to connect to ' + options.path);
return new Promise((resolve, reject) => {
const request = http.request(options);
request.once('connect', (res, socket, head) => {
request.removeAllListeners();
socket.removeAllListeners();
if (res.statusCode === 200) {
trace('Successfully connected to ' +
options.path +
' through proxy ' +
proxyAddressString);
// The HTTP client may have already read a few bytes of the proxied
// connection. If that's the case, put them back into the socket.
// See https://github.com/grpc/grpc-node/issues/2744.
if (head.length > 0) {
socket.unshift(head);
}
trace('Successfully established a plaintext connection to ' +
options.path +
' through proxy ' +
proxyAddressString);
resolve(socket);
}
else {
(0, logging_1.log)(constants_1.LogVerbosity.ERROR, 'Failed to connect to ' +
options.path +
' through proxy ' +
proxyAddressString +
' with status ' +
res.statusCode);
reject();
}
});
request.once('error', err => {
request.removeAllListeners();
(0, logging_1.log)(constants_1.LogVerbosity.ERROR, 'Failed to connect to proxy ' +
proxyAddressString +
' with error ' +
err.message);
reject();
});
request.end();
});
}
//# sourceMappingURL=http_proxy.js.map

View File

@@ -0,0 +1,148 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.experimental = exports.ServerMetricRecorder = exports.ServerInterceptingCall = exports.ResponderBuilder = exports.ServerListenerBuilder = exports.addAdminServicesToServer = exports.getChannelzHandlers = exports.getChannelzServiceDefinition = exports.InterceptorConfigurationError = exports.InterceptingCall = exports.RequesterBuilder = exports.ListenerBuilder = exports.StatusBuilder = exports.getClientChannel = exports.ServerCredentials = exports.Server = exports.setLogVerbosity = exports.setLogger = exports.load = exports.loadObject = exports.CallCredentials = exports.ChannelCredentials = exports.waitForClientReady = exports.closeClient = exports.Channel = exports.makeGenericClientConstructor = exports.makeClientConstructor = exports.loadPackageDefinition = exports.Client = exports.compressionAlgorithms = exports.propagate = exports.connectivityState = exports.status = exports.logVerbosity = exports.Metadata = exports.credentials = void 0;
const call_credentials_1 = require("./call-credentials");
Object.defineProperty(exports, "CallCredentials", { enumerable: true, get: function () { return call_credentials_1.CallCredentials; } });
const channel_1 = require("./channel");
Object.defineProperty(exports, "Channel", { enumerable: true, get: function () { return channel_1.ChannelImplementation; } });
const compression_algorithms_1 = require("./compression-algorithms");
Object.defineProperty(exports, "compressionAlgorithms", { enumerable: true, get: function () { return compression_algorithms_1.CompressionAlgorithms; } });
const connectivity_state_1 = require("./connectivity-state");
Object.defineProperty(exports, "connectivityState", { enumerable: true, get: function () { return connectivity_state_1.ConnectivityState; } });
const channel_credentials_1 = require("./channel-credentials");
Object.defineProperty(exports, "ChannelCredentials", { enumerable: true, get: function () { return channel_credentials_1.ChannelCredentials; } });
const client_1 = require("./client");
Object.defineProperty(exports, "Client", { enumerable: true, get: function () { return client_1.Client; } });
const constants_1 = require("./constants");
Object.defineProperty(exports, "logVerbosity", { enumerable: true, get: function () { return constants_1.LogVerbosity; } });
Object.defineProperty(exports, "status", { enumerable: true, get: function () { return constants_1.Status; } });
Object.defineProperty(exports, "propagate", { enumerable: true, get: function () { return constants_1.Propagate; } });
const logging = require("./logging");
const make_client_1 = require("./make-client");
Object.defineProperty(exports, "loadPackageDefinition", { enumerable: true, get: function () { return make_client_1.loadPackageDefinition; } });
Object.defineProperty(exports, "makeClientConstructor", { enumerable: true, get: function () { return make_client_1.makeClientConstructor; } });
Object.defineProperty(exports, "makeGenericClientConstructor", { enumerable: true, get: function () { return make_client_1.makeClientConstructor; } });
const metadata_1 = require("./metadata");
Object.defineProperty(exports, "Metadata", { enumerable: true, get: function () { return metadata_1.Metadata; } });
const server_1 = require("./server");
Object.defineProperty(exports, "Server", { enumerable: true, get: function () { return server_1.Server; } });
const server_credentials_1 = require("./server-credentials");
Object.defineProperty(exports, "ServerCredentials", { enumerable: true, get: function () { return server_credentials_1.ServerCredentials; } });
const status_builder_1 = require("./status-builder");
Object.defineProperty(exports, "StatusBuilder", { enumerable: true, get: function () { return status_builder_1.StatusBuilder; } });
/**** Client Credentials ****/
// Using assign only copies enumerable properties, which is what we want
exports.credentials = {
/**
* Combine a ChannelCredentials with any number of CallCredentials into a
* single ChannelCredentials object.
* @param channelCredentials The ChannelCredentials object.
* @param callCredentials Any number of CallCredentials objects.
* @return The resulting ChannelCredentials object.
*/
combineChannelCredentials: (channelCredentials, ...callCredentials) => {
return callCredentials.reduce((acc, other) => acc.compose(other), channelCredentials);
},
/**
* Combine any number of CallCredentials into a single CallCredentials
* object.
* @param first The first CallCredentials object.
* @param additional Any number of additional CallCredentials objects.
* @return The resulting CallCredentials object.
*/
combineCallCredentials: (first, ...additional) => {
return additional.reduce((acc, other) => acc.compose(other), first);
},
// from channel-credentials.ts
createInsecure: channel_credentials_1.ChannelCredentials.createInsecure,
createSsl: channel_credentials_1.ChannelCredentials.createSsl,
createFromSecureContext: channel_credentials_1.ChannelCredentials.createFromSecureContext,
// from call-credentials.ts
createFromMetadataGenerator: call_credentials_1.CallCredentials.createFromMetadataGenerator,
createFromGoogleCredential: call_credentials_1.CallCredentials.createFromGoogleCredential,
createEmpty: call_credentials_1.CallCredentials.createEmpty,
};
/**
* Close a Client object.
* @param client The client to close.
*/
const closeClient = (client) => client.close();
exports.closeClient = closeClient;
const waitForClientReady = (client, deadline, callback) => client.waitForReady(deadline, callback);
exports.waitForClientReady = waitForClientReady;
/* eslint-enable @typescript-eslint/no-explicit-any */
/**** Unimplemented function stubs ****/
/* eslint-disable @typescript-eslint/no-explicit-any */
const loadObject = (value, options) => {
throw new Error('Not available in this library. Use @grpc/proto-loader and loadPackageDefinition instead');
};
exports.loadObject = loadObject;
const load = (filename, format, options) => {
throw new Error('Not available in this library. Use @grpc/proto-loader and loadPackageDefinition instead');
};
exports.load = load;
const setLogger = (logger) => {
logging.setLogger(logger);
};
exports.setLogger = setLogger;
const setLogVerbosity = (verbosity) => {
logging.setLoggerVerbosity(verbosity);
};
exports.setLogVerbosity = setLogVerbosity;
const getClientChannel = (client) => {
return client_1.Client.prototype.getChannel.call(client);
};
exports.getClientChannel = getClientChannel;
var client_interceptors_1 = require("./client-interceptors");
Object.defineProperty(exports, "ListenerBuilder", { enumerable: true, get: function () { return client_interceptors_1.ListenerBuilder; } });
Object.defineProperty(exports, "RequesterBuilder", { enumerable: true, get: function () { return client_interceptors_1.RequesterBuilder; } });
Object.defineProperty(exports, "InterceptingCall", { enumerable: true, get: function () { return client_interceptors_1.InterceptingCall; } });
Object.defineProperty(exports, "InterceptorConfigurationError", { enumerable: true, get: function () { return client_interceptors_1.InterceptorConfigurationError; } });
var channelz_1 = require("./channelz");
Object.defineProperty(exports, "getChannelzServiceDefinition", { enumerable: true, get: function () { return channelz_1.getChannelzServiceDefinition; } });
Object.defineProperty(exports, "getChannelzHandlers", { enumerable: true, get: function () { return channelz_1.getChannelzHandlers; } });
var admin_1 = require("./admin");
Object.defineProperty(exports, "addAdminServicesToServer", { enumerable: true, get: function () { return admin_1.addAdminServicesToServer; } });
var server_interceptors_1 = require("./server-interceptors");
Object.defineProperty(exports, "ServerListenerBuilder", { enumerable: true, get: function () { return server_interceptors_1.ServerListenerBuilder; } });
Object.defineProperty(exports, "ResponderBuilder", { enumerable: true, get: function () { return server_interceptors_1.ResponderBuilder; } });
Object.defineProperty(exports, "ServerInterceptingCall", { enumerable: true, get: function () { return server_interceptors_1.ServerInterceptingCall; } });
var orca_1 = require("./orca");
Object.defineProperty(exports, "ServerMetricRecorder", { enumerable: true, get: function () { return orca_1.ServerMetricRecorder; } });
const experimental = require("./experimental");
exports.experimental = experimental;
const resolver_dns = require("./resolver-dns");
const resolver_uds = require("./resolver-uds");
const resolver_ip = require("./resolver-ip");
const load_balancer_pick_first = require("./load-balancer-pick-first");
const load_balancer_round_robin = require("./load-balancer-round-robin");
const load_balancer_outlier_detection = require("./load-balancer-outlier-detection");
const load_balancer_weighted_round_robin = require("./load-balancer-weighted-round-robin");
const channelz = require("./channelz");
(() => {
resolver_dns.setup();
resolver_uds.setup();
resolver_ip.setup();
load_balancer_pick_first.setup();
load_balancer_round_robin.setup();
load_balancer_outlier_detection.setup();
load_balancer_weighted_round_robin.setup();
channelz.setup();
})();
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1,605 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.InternalChannel = exports.SUBCHANNEL_ARGS_EXCLUDE_KEY_PREFIX = void 0;
const channel_credentials_1 = require("./channel-credentials");
const resolving_load_balancer_1 = require("./resolving-load-balancer");
const subchannel_pool_1 = require("./subchannel-pool");
const picker_1 = require("./picker");
const metadata_1 = require("./metadata");
const constants_1 = require("./constants");
const filter_stack_1 = require("./filter-stack");
const compression_filter_1 = require("./compression-filter");
const resolver_1 = require("./resolver");
const logging_1 = require("./logging");
const http_proxy_1 = require("./http_proxy");
const uri_parser_1 = require("./uri-parser");
const connectivity_state_1 = require("./connectivity-state");
const channelz_1 = require("./channelz");
const load_balancing_call_1 = require("./load-balancing-call");
const deadline_1 = require("./deadline");
const resolving_call_1 = require("./resolving-call");
const call_number_1 = require("./call-number");
const control_plane_status_1 = require("./control-plane-status");
const retrying_call_1 = require("./retrying-call");
const subchannel_interface_1 = require("./subchannel-interface");
/**
* See https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args
*/
const MAX_TIMEOUT_TIME = 2147483647;
const MIN_IDLE_TIMEOUT_MS = 1000;
// 30 minutes
const DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
const RETRY_THROTTLER_MAP = new Map();
const DEFAULT_RETRY_BUFFER_SIZE_BYTES = 1 << 24; // 16 MB
const DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES = 1 << 20; // 1 MB
class ChannelSubchannelWrapper extends subchannel_interface_1.BaseSubchannelWrapper {
constructor(childSubchannel, channel) {
super(childSubchannel);
this.channel = channel;
this.refCount = 0;
this.subchannelStateListener = (subchannel, previousState, newState, keepaliveTime) => {
channel.throttleKeepalive(keepaliveTime);
};
}
ref() {
if (this.refCount === 0) {
this.child.addConnectivityStateListener(this.subchannelStateListener);
this.channel.addWrappedSubchannel(this);
}
this.child.ref();
this.refCount += 1;
}
unref() {
this.child.unref();
this.refCount -= 1;
if (this.refCount <= 0) {
this.child.removeConnectivityStateListener(this.subchannelStateListener);
this.channel.removeWrappedSubchannel(this);
}
}
}
class ShutdownPicker {
pick(pickArgs) {
return {
pickResultType: picker_1.PickResultType.DROP,
status: {
code: constants_1.Status.UNAVAILABLE,
details: 'Channel closed before call started',
metadata: new metadata_1.Metadata()
},
subchannel: null,
onCallStarted: null,
onCallEnded: null
};
}
}
exports.SUBCHANNEL_ARGS_EXCLUDE_KEY_PREFIX = 'grpc.internal.no_subchannel';
class ChannelzInfoTracker {
constructor(target) {
this.target = target;
this.trace = new channelz_1.ChannelzTrace();
this.callTracker = new channelz_1.ChannelzCallTracker();
this.childrenTracker = new channelz_1.ChannelzChildrenTracker();
this.state = connectivity_state_1.ConnectivityState.IDLE;
}
getChannelzInfoCallback() {
return () => {
return {
target: this.target,
state: this.state,
trace: this.trace,
callTracker: this.callTracker,
children: this.childrenTracker.getChildLists()
};
};
}
}
class InternalChannel {
constructor(target, credentials, options) {
var _a, _b, _c, _d, _e, _f;
this.credentials = credentials;
this.options = options;
this.connectivityState = connectivity_state_1.ConnectivityState.IDLE;
this.currentPicker = new picker_1.UnavailablePicker();
/**
* Calls queued up to get a call config. Should only be populated before the
* first time the resolver returns a result, which includes the ConfigSelector.
*/
this.configSelectionQueue = [];
this.pickQueue = [];
this.connectivityStateWatchers = [];
/**
* This timer does not do anything on its own. Its purpose is to hold the
* event loop open while there are any pending calls for the channel that
* have not yet been assigned to specific subchannels. In other words,
* the invariant is that callRefTimer is reffed if and only if pickQueue
* is non-empty. In addition, the timer is null while the state is IDLE or
* SHUTDOWN and there are no pending calls.
*/
this.callRefTimer = null;
this.configSelector = null;
/**
* This is the error from the name resolver if it failed most recently. It
* is only used to end calls that start while there is no config selector
* and the name resolver is in backoff, so it should be nulled if
* configSelector becomes set or the channel state becomes anything other
* than TRANSIENT_FAILURE.
*/
this.currentResolutionError = null;
this.wrappedSubchannels = new Set();
this.callCount = 0;
this.idleTimer = null;
// Channelz info
this.channelzEnabled = true;
/**
* Randomly generated ID to be passed to the config selector, for use by
* ring_hash in xDS. An integer distributed approximately uniformly between
* 0 and MAX_SAFE_INTEGER.
*/
this.randomChannelId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
if (typeof target !== 'string') {
throw new TypeError('Channel target must be a string');
}
if (!(credentials instanceof channel_credentials_1.ChannelCredentials)) {
throw new TypeError('Channel credentials must be a ChannelCredentials object');
}
if (options) {
if (typeof options !== 'object') {
throw new TypeError('Channel options must be an object');
}
}
this.channelzInfoTracker = new ChannelzInfoTracker(target);
const originalTargetUri = (0, uri_parser_1.parseUri)(target);
if (originalTargetUri === null) {
throw new Error(`Could not parse target name "${target}"`);
}
/* This ensures that the target has a scheme that is registered with the
* resolver */
const defaultSchemeMapResult = (0, resolver_1.mapUriDefaultScheme)(originalTargetUri);
if (defaultSchemeMapResult === null) {
throw new Error(`Could not find a default scheme for target name "${target}"`);
}
if (this.options['grpc.enable_channelz'] === 0) {
this.channelzEnabled = false;
}
this.channelzRef = (0, channelz_1.registerChannelzChannel)(target, this.channelzInfoTracker.getChannelzInfoCallback(), this.channelzEnabled);
if (this.channelzEnabled) {
this.channelzInfoTracker.trace.addTrace('CT_INFO', 'Channel created');
}
if (this.options['grpc.default_authority']) {
this.defaultAuthority = this.options['grpc.default_authority'];
}
else {
this.defaultAuthority = (0, resolver_1.getDefaultAuthority)(defaultSchemeMapResult);
}
const proxyMapResult = (0, http_proxy_1.mapProxyName)(defaultSchemeMapResult, options);
this.target = proxyMapResult.target;
this.options = Object.assign({}, this.options, proxyMapResult.extraOptions);
/* The global boolean parameter to getSubchannelPool has the inverse meaning to what
* the grpc.use_local_subchannel_pool channel option means. */
this.subchannelPool = (0, subchannel_pool_1.getSubchannelPool)(((_a = this.options['grpc.use_local_subchannel_pool']) !== null && _a !== void 0 ? _a : 0) === 0);
this.retryBufferTracker = new retrying_call_1.MessageBufferTracker((_b = this.options['grpc.retry_buffer_size']) !== null && _b !== void 0 ? _b : DEFAULT_RETRY_BUFFER_SIZE_BYTES, (_c = this.options['grpc.per_rpc_retry_buffer_size']) !== null && _c !== void 0 ? _c : DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES);
this.keepaliveTime = (_d = this.options['grpc.keepalive_time_ms']) !== null && _d !== void 0 ? _d : -1;
this.idleTimeoutMs = Math.max((_e = this.options['grpc.client_idle_timeout_ms']) !== null && _e !== void 0 ? _e : DEFAULT_IDLE_TIMEOUT_MS, MIN_IDLE_TIMEOUT_MS);
const channelControlHelper = {
createSubchannel: (subchannelAddress, subchannelArgs) => {
const finalSubchannelArgs = {};
for (const [key, value] of Object.entries(subchannelArgs)) {
if (!key.startsWith(exports.SUBCHANNEL_ARGS_EXCLUDE_KEY_PREFIX)) {
finalSubchannelArgs[key] = value;
}
}
const subchannel = this.subchannelPool.getOrCreateSubchannel(this.target, subchannelAddress, finalSubchannelArgs, this.credentials);
subchannel.throttleKeepalive(this.keepaliveTime);
if (this.channelzEnabled) {
this.channelzInfoTracker.trace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef());
}
const wrappedSubchannel = new ChannelSubchannelWrapper(subchannel, this);
return wrappedSubchannel;
},
updateState: (connectivityState, picker) => {
this.currentPicker = picker;
const queueCopy = this.pickQueue.slice();
this.pickQueue = [];
if (queueCopy.length > 0) {
this.callRefTimerUnref();
}
for (const call of queueCopy) {
call.doPick();
}
this.updateState(connectivityState);
},
requestReresolution: () => {
// This should never be called.
throw new Error('Resolving load balancer should never call requestReresolution');
},
addChannelzChild: (child) => {
if (this.channelzEnabled) {
this.channelzInfoTracker.childrenTracker.refChild(child);
}
},
removeChannelzChild: (child) => {
if (this.channelzEnabled) {
this.channelzInfoTracker.childrenTracker.unrefChild(child);
}
},
};
this.resolvingLoadBalancer = new resolving_load_balancer_1.ResolvingLoadBalancer(this.target, channelControlHelper, this.options, (serviceConfig, configSelector) => {
var _a;
if (serviceConfig.retryThrottling) {
RETRY_THROTTLER_MAP.set(this.getTarget(), new retrying_call_1.RetryThrottler(serviceConfig.retryThrottling.maxTokens, serviceConfig.retryThrottling.tokenRatio, RETRY_THROTTLER_MAP.get(this.getTarget())));
}
else {
RETRY_THROTTLER_MAP.delete(this.getTarget());
}
if (this.channelzEnabled) {
this.channelzInfoTracker.trace.addTrace('CT_INFO', 'Address resolution succeeded');
}
(_a = this.configSelector) === null || _a === void 0 ? void 0 : _a.unref();
this.configSelector = configSelector;
this.currentResolutionError = null;
/* We process the queue asynchronously to ensure that the corresponding
* load balancer update has completed. */
process.nextTick(() => {
const localQueue = this.configSelectionQueue;
this.configSelectionQueue = [];
if (localQueue.length > 0) {
this.callRefTimerUnref();
}
for (const call of localQueue) {
call.getConfig();
}
});
}, status => {
if (this.channelzEnabled) {
this.channelzInfoTracker.trace.addTrace('CT_WARNING', 'Address resolution failed with code ' +
status.code +
' and details "' +
status.details +
'"');
}
if (this.configSelectionQueue.length > 0) {
this.trace('Name resolution failed with calls queued for config selection');
}
if (this.configSelector === null) {
this.currentResolutionError = Object.assign(Object.assign({}, (0, control_plane_status_1.restrictControlPlaneStatusCode)(status.code, status.details)), { metadata: status.metadata });
}
const localQueue = this.configSelectionQueue;
this.configSelectionQueue = [];
if (localQueue.length > 0) {
this.callRefTimerUnref();
}
for (const call of localQueue) {
call.reportResolverError(status);
}
});
this.filterStackFactory = new filter_stack_1.FilterStackFactory([
new compression_filter_1.CompressionFilterFactory(this, this.options),
]);
this.trace('Channel constructed with options ' +
JSON.stringify(options, undefined, 2));
const error = new Error();
if ((0, logging_1.isTracerEnabled)('channel_stacktrace')) {
(0, logging_1.trace)(constants_1.LogVerbosity.DEBUG, 'channel_stacktrace', '(' +
this.channelzRef.id +
') ' +
'Channel constructed \n' +
((_f = error.stack) === null || _f === void 0 ? void 0 : _f.substring(error.stack.indexOf('\n') + 1)));
}
this.lastActivityTimestamp = new Date();
}
trace(text, verbosityOverride) {
(0, logging_1.trace)(verbosityOverride !== null && verbosityOverride !== void 0 ? verbosityOverride : constants_1.LogVerbosity.DEBUG, 'channel', '(' + this.channelzRef.id + ') ' + (0, uri_parser_1.uriToString)(this.target) + ' ' + text);
}
callRefTimerRef() {
var _a, _b, _c, _d;
if (!this.callRefTimer) {
this.callRefTimer = setInterval(() => { }, MAX_TIMEOUT_TIME);
}
// If the hasRef function does not exist, always run the code
if (!((_b = (_a = this.callRefTimer).hasRef) === null || _b === void 0 ? void 0 : _b.call(_a))) {
this.trace('callRefTimer.ref | configSelectionQueue.length=' +
this.configSelectionQueue.length +
' pickQueue.length=' +
this.pickQueue.length);
(_d = (_c = this.callRefTimer).ref) === null || _d === void 0 ? void 0 : _d.call(_c);
}
}
callRefTimerUnref() {
var _a, _b, _c;
// If the timer or the hasRef function does not exist, always run the code
if (!((_a = this.callRefTimer) === null || _a === void 0 ? void 0 : _a.hasRef) || this.callRefTimer.hasRef()) {
this.trace('callRefTimer.unref | configSelectionQueue.length=' +
this.configSelectionQueue.length +
' pickQueue.length=' +
this.pickQueue.length);
(_c = (_b = this.callRefTimer) === null || _b === void 0 ? void 0 : _b.unref) === null || _c === void 0 ? void 0 : _c.call(_b);
}
}
removeConnectivityStateWatcher(watcherObject) {
const watcherIndex = this.connectivityStateWatchers.findIndex(value => value === watcherObject);
if (watcherIndex >= 0) {
this.connectivityStateWatchers.splice(watcherIndex, 1);
}
}
updateState(newState) {
(0, logging_1.trace)(constants_1.LogVerbosity.DEBUG, 'connectivity_state', '(' +
this.channelzRef.id +
') ' +
(0, uri_parser_1.uriToString)(this.target) +
' ' +
connectivity_state_1.ConnectivityState[this.connectivityState] +
' -> ' +
connectivity_state_1.ConnectivityState[newState]);
if (this.channelzEnabled) {
this.channelzInfoTracker.trace.addTrace('CT_INFO', 'Connectivity state change to ' + connectivity_state_1.ConnectivityState[newState]);
}
this.connectivityState = newState;
this.channelzInfoTracker.state = newState;
const watchersCopy = this.connectivityStateWatchers.slice();
for (const watcherObject of watchersCopy) {
if (newState !== watcherObject.currentState) {
if (watcherObject.timer) {
clearTimeout(watcherObject.timer);
}
this.removeConnectivityStateWatcher(watcherObject);
watcherObject.callback();
}
}
if (newState !== connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) {
this.currentResolutionError = null;
}
}
throttleKeepalive(newKeepaliveTime) {
if (newKeepaliveTime > this.keepaliveTime) {
this.keepaliveTime = newKeepaliveTime;
for (const wrappedSubchannel of this.wrappedSubchannels) {
wrappedSubchannel.throttleKeepalive(newKeepaliveTime);
}
}
}
addWrappedSubchannel(wrappedSubchannel) {
this.wrappedSubchannels.add(wrappedSubchannel);
}
removeWrappedSubchannel(wrappedSubchannel) {
this.wrappedSubchannels.delete(wrappedSubchannel);
}
doPick(metadata, extraPickInfo) {
return this.currentPicker.pick({
metadata: metadata,
extraPickInfo: extraPickInfo,
});
}
queueCallForPick(call) {
this.pickQueue.push(call);
this.callRefTimerRef();
}
getConfig(method, metadata) {
if (this.connectivityState !== connectivity_state_1.ConnectivityState.SHUTDOWN) {
this.resolvingLoadBalancer.exitIdle();
}
if (this.configSelector) {
return {
type: 'SUCCESS',
config: this.configSelector.invoke(method, metadata, this.randomChannelId),
};
}
else {
if (this.currentResolutionError) {
return {
type: 'ERROR',
error: this.currentResolutionError,
};
}
else {
return {
type: 'NONE',
};
}
}
}
queueCallForConfig(call) {
this.configSelectionQueue.push(call);
this.callRefTimerRef();
}
enterIdle() {
this.resolvingLoadBalancer.destroy();
this.updateState(connectivity_state_1.ConnectivityState.IDLE);
this.currentPicker = new picker_1.QueuePicker(this.resolvingLoadBalancer);
if (this.idleTimer) {
clearTimeout(this.idleTimer);
this.idleTimer = null;
}
if (this.callRefTimer) {
clearInterval(this.callRefTimer);
this.callRefTimer = null;
}
}
startIdleTimeout(timeoutMs) {
var _a, _b;
this.idleTimer = setTimeout(() => {
if (this.callCount > 0) {
/* If there is currently a call, the channel will not go idle for a
* period of at least idleTimeoutMs, so check again after that time.
*/
this.startIdleTimeout(this.idleTimeoutMs);
return;
}
const now = new Date();
const timeSinceLastActivity = now.valueOf() - this.lastActivityTimestamp.valueOf();
if (timeSinceLastActivity >= this.idleTimeoutMs) {
this.trace('Idle timer triggered after ' +
this.idleTimeoutMs +
'ms of inactivity');
this.enterIdle();
}
else {
/* Whenever the timer fires with the latest activity being too recent,
* set the timer again for the time when the time since the last
* activity is equal to the timeout. This should result in the timer
* firing no more than once every idleTimeoutMs/2 on average. */
this.startIdleTimeout(this.idleTimeoutMs - timeSinceLastActivity);
}
}, timeoutMs);
(_b = (_a = this.idleTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
}
maybeStartIdleTimer() {
if (this.connectivityState !== connectivity_state_1.ConnectivityState.SHUTDOWN &&
!this.idleTimer) {
this.startIdleTimeout(this.idleTimeoutMs);
}
}
onCallStart() {
if (this.channelzEnabled) {
this.channelzInfoTracker.callTracker.addCallStarted();
}
this.callCount += 1;
}
onCallEnd(status) {
if (this.channelzEnabled) {
if (status.code === constants_1.Status.OK) {
this.channelzInfoTracker.callTracker.addCallSucceeded();
}
else {
this.channelzInfoTracker.callTracker.addCallFailed();
}
}
this.callCount -= 1;
this.lastActivityTimestamp = new Date();
this.maybeStartIdleTimer();
}
createLoadBalancingCall(callConfig, method, host, credentials, deadline) {
const callNumber = (0, call_number_1.getNextCallNumber)();
this.trace('createLoadBalancingCall [' + callNumber + '] method="' + method + '"');
return new load_balancing_call_1.LoadBalancingCall(this, callConfig, method, host, credentials, deadline, callNumber);
}
createRetryingCall(callConfig, method, host, credentials, deadline) {
const callNumber = (0, call_number_1.getNextCallNumber)();
this.trace('createRetryingCall [' + callNumber + '] method="' + method + '"');
return new retrying_call_1.RetryingCall(this, callConfig, method, host, credentials, deadline, callNumber, this.retryBufferTracker, RETRY_THROTTLER_MAP.get(this.getTarget()));
}
createResolvingCall(method, deadline, host, parentCall, propagateFlags) {
const callNumber = (0, call_number_1.getNextCallNumber)();
this.trace('createResolvingCall [' +
callNumber +
'] method="' +
method +
'", deadline=' +
(0, deadline_1.deadlineToString)(deadline));
const finalOptions = {
deadline: deadline,
flags: propagateFlags !== null && propagateFlags !== void 0 ? propagateFlags : constants_1.Propagate.DEFAULTS,
host: host !== null && host !== void 0 ? host : this.defaultAuthority,
parentCall: parentCall,
};
const call = new resolving_call_1.ResolvingCall(this, method, finalOptions, this.filterStackFactory.clone(), callNumber);
this.onCallStart();
call.addStatusWatcher(status => {
this.onCallEnd(status);
});
return call;
}
close() {
var _a;
this.resolvingLoadBalancer.destroy();
this.updateState(connectivity_state_1.ConnectivityState.SHUTDOWN);
this.currentPicker = new ShutdownPicker();
for (const call of this.configSelectionQueue) {
call.cancelWithStatus(constants_1.Status.UNAVAILABLE, 'Channel closed before call started');
}
this.configSelectionQueue = [];
for (const call of this.pickQueue) {
call.cancelWithStatus(constants_1.Status.UNAVAILABLE, 'Channel closed before call started');
}
this.pickQueue = [];
if (this.callRefTimer) {
clearInterval(this.callRefTimer);
}
if (this.idleTimer) {
clearTimeout(this.idleTimer);
}
if (this.channelzEnabled) {
(0, channelz_1.unregisterChannelzRef)(this.channelzRef);
}
this.subchannelPool.unrefUnusedSubchannels();
(_a = this.configSelector) === null || _a === void 0 ? void 0 : _a.unref();
this.configSelector = null;
}
getTarget() {
return (0, uri_parser_1.uriToString)(this.target);
}
getConnectivityState(tryToConnect) {
const connectivityState = this.connectivityState;
if (tryToConnect) {
this.resolvingLoadBalancer.exitIdle();
this.lastActivityTimestamp = new Date();
this.maybeStartIdleTimer();
}
return connectivityState;
}
watchConnectivityState(currentState, deadline, callback) {
if (this.connectivityState === connectivity_state_1.ConnectivityState.SHUTDOWN) {
throw new Error('Channel has been shut down');
}
let timer = null;
if (deadline !== Infinity) {
const deadlineDate = deadline instanceof Date ? deadline : new Date(deadline);
const now = new Date();
if (deadline === -Infinity || deadlineDate <= now) {
process.nextTick(callback, new Error('Deadline passed without connectivity state change'));
return;
}
timer = setTimeout(() => {
this.removeConnectivityStateWatcher(watcherObject);
callback(new Error('Deadline passed without connectivity state change'));
}, deadlineDate.getTime() - now.getTime());
}
const watcherObject = {
currentState,
callback,
timer,
};
this.connectivityStateWatchers.push(watcherObject);
}
/**
* Get the channelz reference object for this channel. The returned value is
* garbage if channelz is disabled for this channel.
* @returns
*/
getChannelzRef() {
return this.channelzRef;
}
createCall(method, deadline, host, parentCall, propagateFlags) {
if (typeof method !== 'string') {
throw new TypeError('Channel#createCall: method must be a string');
}
if (!(typeof deadline === 'number' || deadline instanceof Date)) {
throw new TypeError('Channel#createCall: deadline must be a number or Date');
}
if (this.connectivityState === connectivity_state_1.ConnectivityState.SHUTDOWN) {
throw new Error('Channel has been shut down');
}
return this.createResolvingCall(method, deadline, host, parentCall, propagateFlags);
}
getOptions() {
return this.options;
}
}
exports.InternalChannel = InternalChannel;
//# sourceMappingURL=internal-channel.js.map

View File

@@ -0,0 +1,151 @@
"use strict";
/*
* Copyright 2020 gRPC authors.
*
* Licensed 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.ChildLoadBalancerHandler = void 0;
const load_balancer_1 = require("./load-balancer");
const connectivity_state_1 = require("./connectivity-state");
const TYPE_NAME = 'child_load_balancer_helper';
class ChildLoadBalancerHandler {
constructor(channelControlHelper) {
this.channelControlHelper = channelControlHelper;
this.currentChild = null;
this.pendingChild = null;
this.latestConfig = null;
this.ChildPolicyHelper = class {
constructor(parent) {
this.parent = parent;
this.child = null;
}
createSubchannel(subchannelAddress, subchannelArgs) {
return this.parent.channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs);
}
updateState(connectivityState, picker, errorMessage) {
var _a;
if (this.calledByPendingChild()) {
if (connectivityState === connectivity_state_1.ConnectivityState.CONNECTING) {
return;
}
(_a = this.parent.currentChild) === null || _a === void 0 ? void 0 : _a.destroy();
this.parent.currentChild = this.parent.pendingChild;
this.parent.pendingChild = null;
}
else if (!this.calledByCurrentChild()) {
return;
}
this.parent.channelControlHelper.updateState(connectivityState, picker, errorMessage);
}
requestReresolution() {
var _a;
const latestChild = (_a = this.parent.pendingChild) !== null && _a !== void 0 ? _a : this.parent.currentChild;
if (this.child === latestChild) {
this.parent.channelControlHelper.requestReresolution();
}
}
setChild(newChild) {
this.child = newChild;
}
addChannelzChild(child) {
this.parent.channelControlHelper.addChannelzChild(child);
}
removeChannelzChild(child) {
this.parent.channelControlHelper.removeChannelzChild(child);
}
calledByPendingChild() {
return this.child === this.parent.pendingChild;
}
calledByCurrentChild() {
return this.child === this.parent.currentChild;
}
};
}
configUpdateRequiresNewPolicyInstance(oldConfig, newConfig) {
return oldConfig.getLoadBalancerName() !== newConfig.getLoadBalancerName();
}
/**
* Prerequisites: lbConfig !== null and lbConfig.name is registered
* @param endpointList
* @param lbConfig
* @param attributes
*/
updateAddressList(endpointList, lbConfig, options, resolutionNote) {
let childToUpdate;
if (this.currentChild === null ||
this.latestConfig === null ||
this.configUpdateRequiresNewPolicyInstance(this.latestConfig, lbConfig)) {
const newHelper = new this.ChildPolicyHelper(this);
const newChild = (0, load_balancer_1.createLoadBalancer)(lbConfig, newHelper);
newHelper.setChild(newChild);
if (this.currentChild === null) {
this.currentChild = newChild;
childToUpdate = this.currentChild;
}
else {
if (this.pendingChild) {
this.pendingChild.destroy();
}
this.pendingChild = newChild;
childToUpdate = this.pendingChild;
}
}
else {
if (this.pendingChild === null) {
childToUpdate = this.currentChild;
}
else {
childToUpdate = this.pendingChild;
}
}
this.latestConfig = lbConfig;
return childToUpdate.updateAddressList(endpointList, lbConfig, options, resolutionNote);
}
exitIdle() {
if (this.currentChild) {
this.currentChild.exitIdle();
if (this.pendingChild) {
this.pendingChild.exitIdle();
}
}
}
resetBackoff() {
if (this.currentChild) {
this.currentChild.resetBackoff();
if (this.pendingChild) {
this.pendingChild.resetBackoff();
}
}
}
destroy() {
/* Note: state updates are only propagated from the child balancer if that
* object is equal to this.currentChild or this.pendingChild. Since this
* function sets both of those to null, no further state updates will
* occur after this function returns. */
if (this.currentChild) {
this.currentChild.destroy();
this.currentChild = null;
}
if (this.pendingChild) {
this.pendingChild.destroy();
this.pendingChild = null;
}
}
getTypeName() {
return TYPE_NAME;
}
}
exports.ChildLoadBalancerHandler = ChildLoadBalancerHandler;
//# sourceMappingURL=load-balancer-child-handler.js.map

View File

@@ -0,0 +1,571 @@
"use strict";
/*
* Copyright 2022 gRPC authors.
*
* Licensed 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 });
exports.OutlierDetectionLoadBalancer = exports.OutlierDetectionLoadBalancingConfig = void 0;
exports.setup = setup;
const connectivity_state_1 = require("./connectivity-state");
const constants_1 = require("./constants");
const duration_1 = require("./duration");
const experimental_1 = require("./experimental");
const load_balancer_1 = require("./load-balancer");
const load_balancer_child_handler_1 = require("./load-balancer-child-handler");
const picker_1 = require("./picker");
const subchannel_address_1 = require("./subchannel-address");
const subchannel_interface_1 = require("./subchannel-interface");
const logging = require("./logging");
const TRACER_NAME = 'outlier_detection';
function trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, text);
}
const TYPE_NAME = 'outlier_detection';
const OUTLIER_DETECTION_ENABLED = ((_a = process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION) !== null && _a !== void 0 ? _a : 'true') === 'true';
const defaultSuccessRateEjectionConfig = {
stdev_factor: 1900,
enforcement_percentage: 100,
minimum_hosts: 5,
request_volume: 100,
};
const defaultFailurePercentageEjectionConfig = {
threshold: 85,
enforcement_percentage: 100,
minimum_hosts: 5,
request_volume: 50,
};
function validateFieldType(obj, fieldName, expectedType, objectName) {
if (fieldName in obj &&
obj[fieldName] !== undefined &&
typeof obj[fieldName] !== expectedType) {
const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName;
throw new Error(`outlier detection config ${fullFieldName} parse error: expected ${expectedType}, got ${typeof obj[fieldName]}`);
}
}
function validatePositiveDuration(obj, fieldName, objectName) {
const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName;
if (fieldName in obj && obj[fieldName] !== undefined) {
if (!(0, duration_1.isDuration)(obj[fieldName])) {
throw new Error(`outlier detection config ${fullFieldName} parse error: expected Duration, got ${typeof obj[fieldName]}`);
}
if (!(obj[fieldName].seconds >= 0 &&
obj[fieldName].seconds <= 315576000000 &&
obj[fieldName].nanos >= 0 &&
obj[fieldName].nanos <= 999999999)) {
throw new Error(`outlier detection config ${fullFieldName} parse error: values out of range for non-negative Duaration`);
}
}
}
function validatePercentage(obj, fieldName, objectName) {
const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName;
validateFieldType(obj, fieldName, 'number', objectName);
if (fieldName in obj &&
obj[fieldName] !== undefined &&
!(obj[fieldName] >= 0 && obj[fieldName] <= 100)) {
throw new Error(`outlier detection config ${fullFieldName} parse error: value out of range for percentage (0-100)`);
}
}
class OutlierDetectionLoadBalancingConfig {
constructor(intervalMs, baseEjectionTimeMs, maxEjectionTimeMs, maxEjectionPercent, successRateEjection, failurePercentageEjection, childPolicy) {
this.childPolicy = childPolicy;
if (childPolicy.getLoadBalancerName() === 'pick_first') {
throw new Error('outlier_detection LB policy cannot have a pick_first child policy');
}
this.intervalMs = intervalMs !== null && intervalMs !== void 0 ? intervalMs : 10000;
this.baseEjectionTimeMs = baseEjectionTimeMs !== null && baseEjectionTimeMs !== void 0 ? baseEjectionTimeMs : 30000;
this.maxEjectionTimeMs = maxEjectionTimeMs !== null && maxEjectionTimeMs !== void 0 ? maxEjectionTimeMs : 300000;
this.maxEjectionPercent = maxEjectionPercent !== null && maxEjectionPercent !== void 0 ? maxEjectionPercent : 10;
this.successRateEjection = successRateEjection
? Object.assign(Object.assign({}, defaultSuccessRateEjectionConfig), successRateEjection) : null;
this.failurePercentageEjection = failurePercentageEjection
? Object.assign(Object.assign({}, defaultFailurePercentageEjectionConfig), failurePercentageEjection) : null;
}
getLoadBalancerName() {
return TYPE_NAME;
}
toJsonObject() {
var _a, _b;
return {
outlier_detection: {
interval: (0, duration_1.msToDuration)(this.intervalMs),
base_ejection_time: (0, duration_1.msToDuration)(this.baseEjectionTimeMs),
max_ejection_time: (0, duration_1.msToDuration)(this.maxEjectionTimeMs),
max_ejection_percent: this.maxEjectionPercent,
success_rate_ejection: (_a = this.successRateEjection) !== null && _a !== void 0 ? _a : undefined,
failure_percentage_ejection: (_b = this.failurePercentageEjection) !== null && _b !== void 0 ? _b : undefined,
child_policy: [this.childPolicy.toJsonObject()],
},
};
}
getIntervalMs() {
return this.intervalMs;
}
getBaseEjectionTimeMs() {
return this.baseEjectionTimeMs;
}
getMaxEjectionTimeMs() {
return this.maxEjectionTimeMs;
}
getMaxEjectionPercent() {
return this.maxEjectionPercent;
}
getSuccessRateEjectionConfig() {
return this.successRateEjection;
}
getFailurePercentageEjectionConfig() {
return this.failurePercentageEjection;
}
getChildPolicy() {
return this.childPolicy;
}
static createFromJson(obj) {
var _a;
validatePositiveDuration(obj, 'interval');
validatePositiveDuration(obj, 'base_ejection_time');
validatePositiveDuration(obj, 'max_ejection_time');
validatePercentage(obj, 'max_ejection_percent');
if ('success_rate_ejection' in obj &&
obj.success_rate_ejection !== undefined) {
if (typeof obj.success_rate_ejection !== 'object') {
throw new Error('outlier detection config success_rate_ejection must be an object');
}
validateFieldType(obj.success_rate_ejection, 'stdev_factor', 'number', 'success_rate_ejection');
validatePercentage(obj.success_rate_ejection, 'enforcement_percentage', 'success_rate_ejection');
validateFieldType(obj.success_rate_ejection, 'minimum_hosts', 'number', 'success_rate_ejection');
validateFieldType(obj.success_rate_ejection, 'request_volume', 'number', 'success_rate_ejection');
}
if ('failure_percentage_ejection' in obj &&
obj.failure_percentage_ejection !== undefined) {
if (typeof obj.failure_percentage_ejection !== 'object') {
throw new Error('outlier detection config failure_percentage_ejection must be an object');
}
validatePercentage(obj.failure_percentage_ejection, 'threshold', 'failure_percentage_ejection');
validatePercentage(obj.failure_percentage_ejection, 'enforcement_percentage', 'failure_percentage_ejection');
validateFieldType(obj.failure_percentage_ejection, 'minimum_hosts', 'number', 'failure_percentage_ejection');
validateFieldType(obj.failure_percentage_ejection, 'request_volume', 'number', 'failure_percentage_ejection');
}
if (!('child_policy' in obj) || !Array.isArray(obj.child_policy)) {
throw new Error('outlier detection config child_policy must be an array');
}
const childPolicy = (0, load_balancer_1.selectLbConfigFromList)(obj.child_policy);
if (!childPolicy) {
throw new Error('outlier detection config child_policy: no valid recognized policy found');
}
return new OutlierDetectionLoadBalancingConfig(obj.interval ? (0, duration_1.durationToMs)(obj.interval) : null, obj.base_ejection_time ? (0, duration_1.durationToMs)(obj.base_ejection_time) : null, obj.max_ejection_time ? (0, duration_1.durationToMs)(obj.max_ejection_time) : null, (_a = obj.max_ejection_percent) !== null && _a !== void 0 ? _a : null, obj.success_rate_ejection, obj.failure_percentage_ejection, childPolicy);
}
}
exports.OutlierDetectionLoadBalancingConfig = OutlierDetectionLoadBalancingConfig;
class OutlierDetectionSubchannelWrapper extends subchannel_interface_1.BaseSubchannelWrapper {
constructor(childSubchannel, mapEntry) {
super(childSubchannel);
this.mapEntry = mapEntry;
this.refCount = 0;
}
ref() {
this.child.ref();
this.refCount += 1;
}
unref() {
this.child.unref();
this.refCount -= 1;
if (this.refCount <= 0) {
if (this.mapEntry) {
const index = this.mapEntry.subchannelWrappers.indexOf(this);
if (index >= 0) {
this.mapEntry.subchannelWrappers.splice(index, 1);
}
}
}
}
eject() {
this.setHealthy(false);
}
uneject() {
this.setHealthy(true);
}
getMapEntry() {
return this.mapEntry;
}
getWrappedSubchannel() {
return this.child;
}
}
function createEmptyBucket() {
return {
success: 0,
failure: 0,
};
}
class CallCounter {
constructor() {
this.activeBucket = createEmptyBucket();
this.inactiveBucket = createEmptyBucket();
}
addSuccess() {
this.activeBucket.success += 1;
}
addFailure() {
this.activeBucket.failure += 1;
}
switchBuckets() {
this.inactiveBucket = this.activeBucket;
this.activeBucket = createEmptyBucket();
}
getLastSuccesses() {
return this.inactiveBucket.success;
}
getLastFailures() {
return this.inactiveBucket.failure;
}
}
class OutlierDetectionPicker {
constructor(wrappedPicker, countCalls) {
this.wrappedPicker = wrappedPicker;
this.countCalls = countCalls;
}
pick(pickArgs) {
const wrappedPick = this.wrappedPicker.pick(pickArgs);
if (wrappedPick.pickResultType === picker_1.PickResultType.COMPLETE) {
const subchannelWrapper = wrappedPick.subchannel;
const mapEntry = subchannelWrapper.getMapEntry();
if (mapEntry) {
let onCallEnded = wrappedPick.onCallEnded;
if (this.countCalls) {
onCallEnded = (statusCode, details, metadata) => {
var _a;
if (statusCode === constants_1.Status.OK) {
mapEntry.counter.addSuccess();
}
else {
mapEntry.counter.addFailure();
}
(_a = wrappedPick.onCallEnded) === null || _a === void 0 ? void 0 : _a.call(wrappedPick, statusCode, details, metadata);
};
}
return Object.assign(Object.assign({}, wrappedPick), { subchannel: subchannelWrapper.getWrappedSubchannel(), onCallEnded: onCallEnded });
}
else {
return Object.assign(Object.assign({}, wrappedPick), { subchannel: subchannelWrapper.getWrappedSubchannel() });
}
}
else {
return wrappedPick;
}
}
}
class OutlierDetectionLoadBalancer {
constructor(channelControlHelper) {
this.entryMap = new subchannel_address_1.EndpointMap();
this.latestConfig = null;
this.timerStartTime = null;
this.childBalancer = new load_balancer_child_handler_1.ChildLoadBalancerHandler((0, experimental_1.createChildChannelControlHelper)(channelControlHelper, {
createSubchannel: (subchannelAddress, subchannelArgs) => {
const originalSubchannel = channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs);
const mapEntry = this.entryMap.getForSubchannelAddress(subchannelAddress);
const subchannelWrapper = new OutlierDetectionSubchannelWrapper(originalSubchannel, mapEntry);
if ((mapEntry === null || mapEntry === void 0 ? void 0 : mapEntry.currentEjectionTimestamp) !== null) {
// If the address is ejected, propagate that to the new subchannel wrapper
subchannelWrapper.eject();
}
mapEntry === null || mapEntry === void 0 ? void 0 : mapEntry.subchannelWrappers.push(subchannelWrapper);
return subchannelWrapper;
},
updateState: (connectivityState, picker, errorMessage) => {
if (connectivityState === connectivity_state_1.ConnectivityState.READY) {
channelControlHelper.updateState(connectivityState, new OutlierDetectionPicker(picker, this.isCountingEnabled()), errorMessage);
}
else {
channelControlHelper.updateState(connectivityState, picker, errorMessage);
}
},
}));
this.ejectionTimer = setInterval(() => { }, 0);
clearInterval(this.ejectionTimer);
}
isCountingEnabled() {
return (this.latestConfig !== null &&
(this.latestConfig.getSuccessRateEjectionConfig() !== null ||
this.latestConfig.getFailurePercentageEjectionConfig() !== null));
}
getCurrentEjectionPercent() {
let ejectionCount = 0;
for (const mapEntry of this.entryMap.values()) {
if (mapEntry.currentEjectionTimestamp !== null) {
ejectionCount += 1;
}
}
return (ejectionCount * 100) / this.entryMap.size;
}
runSuccessRateCheck(ejectionTimestamp) {
if (!this.latestConfig) {
return;
}
const successRateConfig = this.latestConfig.getSuccessRateEjectionConfig();
if (!successRateConfig) {
return;
}
trace('Running success rate check');
// Step 1
const targetRequestVolume = successRateConfig.request_volume;
let addresesWithTargetVolume = 0;
const successRates = [];
for (const [endpoint, mapEntry] of this.entryMap.entries()) {
const successes = mapEntry.counter.getLastSuccesses();
const failures = mapEntry.counter.getLastFailures();
trace('Stats for ' +
(0, subchannel_address_1.endpointToString)(endpoint) +
': successes=' +
successes +
' failures=' +
failures +
' targetRequestVolume=' +
targetRequestVolume);
if (successes + failures >= targetRequestVolume) {
addresesWithTargetVolume += 1;
successRates.push(successes / (successes + failures));
}
}
trace('Found ' +
addresesWithTargetVolume +
' success rate candidates; currentEjectionPercent=' +
this.getCurrentEjectionPercent() +
' successRates=[' +
successRates +
']');
if (addresesWithTargetVolume < successRateConfig.minimum_hosts) {
return;
}
// Step 2
const successRateMean = successRates.reduce((a, b) => a + b) / successRates.length;
let successRateDeviationSum = 0;
for (const rate of successRates) {
const deviation = rate - successRateMean;
successRateDeviationSum += deviation * deviation;
}
const successRateVariance = successRateDeviationSum / successRates.length;
const successRateStdev = Math.sqrt(successRateVariance);
const ejectionThreshold = successRateMean -
successRateStdev * (successRateConfig.stdev_factor / 1000);
trace('stdev=' + successRateStdev + ' ejectionThreshold=' + ejectionThreshold);
// Step 3
for (const [address, mapEntry] of this.entryMap.entries()) {
// Step 3.i
if (this.getCurrentEjectionPercent() >=
this.latestConfig.getMaxEjectionPercent()) {
break;
}
// Step 3.ii
const successes = mapEntry.counter.getLastSuccesses();
const failures = mapEntry.counter.getLastFailures();
if (successes + failures < targetRequestVolume) {
continue;
}
// Step 3.iii
const successRate = successes / (successes + failures);
trace('Checking candidate ' + address + ' successRate=' + successRate);
if (successRate < ejectionThreshold) {
const randomNumber = Math.random() * 100;
trace('Candidate ' +
address +
' randomNumber=' +
randomNumber +
' enforcement_percentage=' +
successRateConfig.enforcement_percentage);
if (randomNumber < successRateConfig.enforcement_percentage) {
trace('Ejecting candidate ' + address);
this.eject(mapEntry, ejectionTimestamp);
}
}
}
}
runFailurePercentageCheck(ejectionTimestamp) {
if (!this.latestConfig) {
return;
}
const failurePercentageConfig = this.latestConfig.getFailurePercentageEjectionConfig();
if (!failurePercentageConfig) {
return;
}
trace('Running failure percentage check. threshold=' +
failurePercentageConfig.threshold +
' request volume threshold=' +
failurePercentageConfig.request_volume);
// Step 1
let addressesWithTargetVolume = 0;
for (const mapEntry of this.entryMap.values()) {
const successes = mapEntry.counter.getLastSuccesses();
const failures = mapEntry.counter.getLastFailures();
if (successes + failures >= failurePercentageConfig.request_volume) {
addressesWithTargetVolume += 1;
}
}
if (addressesWithTargetVolume < failurePercentageConfig.minimum_hosts) {
return;
}
// Step 2
for (const [address, mapEntry] of this.entryMap.entries()) {
// Step 2.i
if (this.getCurrentEjectionPercent() >=
this.latestConfig.getMaxEjectionPercent()) {
break;
}
// Step 2.ii
const successes = mapEntry.counter.getLastSuccesses();
const failures = mapEntry.counter.getLastFailures();
trace('Candidate successes=' + successes + ' failures=' + failures);
if (successes + failures < failurePercentageConfig.request_volume) {
continue;
}
// Step 2.iii
const failurePercentage = (failures * 100) / (failures + successes);
if (failurePercentage > failurePercentageConfig.threshold) {
const randomNumber = Math.random() * 100;
trace('Candidate ' +
address +
' randomNumber=' +
randomNumber +
' enforcement_percentage=' +
failurePercentageConfig.enforcement_percentage);
if (randomNumber < failurePercentageConfig.enforcement_percentage) {
trace('Ejecting candidate ' + address);
this.eject(mapEntry, ejectionTimestamp);
}
}
}
}
eject(mapEntry, ejectionTimestamp) {
mapEntry.currentEjectionTimestamp = new Date();
mapEntry.ejectionTimeMultiplier += 1;
for (const subchannelWrapper of mapEntry.subchannelWrappers) {
subchannelWrapper.eject();
}
}
uneject(mapEntry) {
mapEntry.currentEjectionTimestamp = null;
for (const subchannelWrapper of mapEntry.subchannelWrappers) {
subchannelWrapper.uneject();
}
}
switchAllBuckets() {
for (const mapEntry of this.entryMap.values()) {
mapEntry.counter.switchBuckets();
}
}
startTimer(delayMs) {
var _a, _b;
this.ejectionTimer = setTimeout(() => this.runChecks(), delayMs);
(_b = (_a = this.ejectionTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
}
runChecks() {
const ejectionTimestamp = new Date();
trace('Ejection timer running');
this.switchAllBuckets();
if (!this.latestConfig) {
return;
}
this.timerStartTime = ejectionTimestamp;
this.startTimer(this.latestConfig.getIntervalMs());
this.runSuccessRateCheck(ejectionTimestamp);
this.runFailurePercentageCheck(ejectionTimestamp);
for (const [address, mapEntry] of this.entryMap.entries()) {
if (mapEntry.currentEjectionTimestamp === null) {
if (mapEntry.ejectionTimeMultiplier > 0) {
mapEntry.ejectionTimeMultiplier -= 1;
}
}
else {
const baseEjectionTimeMs = this.latestConfig.getBaseEjectionTimeMs();
const maxEjectionTimeMs = this.latestConfig.getMaxEjectionTimeMs();
const returnTime = new Date(mapEntry.currentEjectionTimestamp.getTime());
returnTime.setMilliseconds(returnTime.getMilliseconds() +
Math.min(baseEjectionTimeMs * mapEntry.ejectionTimeMultiplier, Math.max(baseEjectionTimeMs, maxEjectionTimeMs)));
if (returnTime < new Date()) {
trace('Unejecting ' + address);
this.uneject(mapEntry);
}
}
}
}
updateAddressList(endpointList, lbConfig, options, resolutionNote) {
if (!(lbConfig instanceof OutlierDetectionLoadBalancingConfig)) {
return false;
}
trace('Received update with config: ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
if (endpointList.ok) {
for (const endpoint of endpointList.value) {
if (!this.entryMap.has(endpoint)) {
trace('Adding map entry for ' + (0, subchannel_address_1.endpointToString)(endpoint));
this.entryMap.set(endpoint, {
counter: new CallCounter(),
currentEjectionTimestamp: null,
ejectionTimeMultiplier: 0,
subchannelWrappers: [],
});
}
}
this.entryMap.deleteMissing(endpointList.value);
}
const childPolicy = lbConfig.getChildPolicy();
this.childBalancer.updateAddressList(endpointList, childPolicy, options, resolutionNote);
if (lbConfig.getSuccessRateEjectionConfig() ||
lbConfig.getFailurePercentageEjectionConfig()) {
if (this.timerStartTime) {
trace('Previous timer existed. Replacing timer');
clearTimeout(this.ejectionTimer);
const remainingDelay = lbConfig.getIntervalMs() -
(new Date().getTime() - this.timerStartTime.getTime());
this.startTimer(remainingDelay);
}
else {
trace('Starting new timer');
this.timerStartTime = new Date();
this.startTimer(lbConfig.getIntervalMs());
this.switchAllBuckets();
}
}
else {
trace('Counting disabled. Cancelling timer.');
this.timerStartTime = null;
clearTimeout(this.ejectionTimer);
for (const mapEntry of this.entryMap.values()) {
this.uneject(mapEntry);
mapEntry.ejectionTimeMultiplier = 0;
}
}
this.latestConfig = lbConfig;
return true;
}
exitIdle() {
this.childBalancer.exitIdle();
}
resetBackoff() {
this.childBalancer.resetBackoff();
}
destroy() {
clearTimeout(this.ejectionTimer);
this.childBalancer.destroy();
}
getTypeName() {
return TYPE_NAME;
}
}
exports.OutlierDetectionLoadBalancer = OutlierDetectionLoadBalancer;
function setup() {
if (OUTLIER_DETECTION_ENABLED) {
(0, experimental_1.registerLoadBalancerType)(TYPE_NAME, OutlierDetectionLoadBalancer, OutlierDetectionLoadBalancingConfig);
}
}
//# sourceMappingURL=load-balancer-outlier-detection.js.map

View File

@@ -0,0 +1,514 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.LeafLoadBalancer = exports.PickFirstLoadBalancer = exports.PickFirstLoadBalancingConfig = void 0;
exports.shuffled = shuffled;
exports.setup = setup;
const load_balancer_1 = require("./load-balancer");
const connectivity_state_1 = require("./connectivity-state");
const picker_1 = require("./picker");
const subchannel_address_1 = require("./subchannel-address");
const logging = require("./logging");
const constants_1 = require("./constants");
const subchannel_address_2 = require("./subchannel-address");
const net_1 = require("net");
const call_interface_1 = require("./call-interface");
const TRACER_NAME = 'pick_first';
function trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, text);
}
const TYPE_NAME = 'pick_first';
/**
* Delay after starting a connection on a subchannel before starting a
* connection on the next subchannel in the list, for Happy Eyeballs algorithm.
*/
const CONNECTION_DELAY_INTERVAL_MS = 250;
class PickFirstLoadBalancingConfig {
constructor(shuffleAddressList) {
this.shuffleAddressList = shuffleAddressList;
}
getLoadBalancerName() {
return TYPE_NAME;
}
toJsonObject() {
return {
[TYPE_NAME]: {
shuffleAddressList: this.shuffleAddressList,
},
};
}
getShuffleAddressList() {
return this.shuffleAddressList;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static createFromJson(obj) {
if ('shuffleAddressList' in obj &&
!(typeof obj.shuffleAddressList === 'boolean')) {
throw new Error('pick_first config field shuffleAddressList must be a boolean if provided');
}
return new PickFirstLoadBalancingConfig(obj.shuffleAddressList === true);
}
}
exports.PickFirstLoadBalancingConfig = PickFirstLoadBalancingConfig;
/**
* Picker for a `PickFirstLoadBalancer` in the READY state. Always returns the
* picked subchannel.
*/
class PickFirstPicker {
constructor(subchannel) {
this.subchannel = subchannel;
}
pick(pickArgs) {
return {
pickResultType: picker_1.PickResultType.COMPLETE,
subchannel: this.subchannel,
status: null,
onCallStarted: null,
onCallEnded: null,
};
}
}
/**
* Return a new array with the elements of the input array in a random order
* @param list The input array
* @returns A shuffled array of the elements of list
*/
function shuffled(list) {
const result = list.slice();
for (let i = result.length - 1; i > 1; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = result[i];
result[i] = result[j];
result[j] = temp;
}
return result;
}
/**
* Interleave addresses in addressList by family in accordance with RFC-8304 section 4
* @param addressList
* @returns
*/
function interleaveAddressFamilies(addressList) {
if (addressList.length === 0) {
return [];
}
const result = [];
const ipv6Addresses = [];
const ipv4Addresses = [];
const ipv6First = (0, subchannel_address_2.isTcpSubchannelAddress)(addressList[0]) && (0, net_1.isIPv6)(addressList[0].host);
for (const address of addressList) {
if ((0, subchannel_address_2.isTcpSubchannelAddress)(address) && (0, net_1.isIPv6)(address.host)) {
ipv6Addresses.push(address);
}
else {
ipv4Addresses.push(address);
}
}
const firstList = ipv6First ? ipv6Addresses : ipv4Addresses;
const secondList = ipv6First ? ipv4Addresses : ipv6Addresses;
for (let i = 0; i < Math.max(firstList.length, secondList.length); i++) {
if (i < firstList.length) {
result.push(firstList[i]);
}
if (i < secondList.length) {
result.push(secondList[i]);
}
}
return result;
}
const REPORT_HEALTH_STATUS_OPTION_NAME = 'grpc-node.internal.pick-first.report_health_status';
class PickFirstLoadBalancer {
/**
* Load balancer that attempts to connect to each backend in the address list
* in order, and picks the first one that connects, using it for every
* request.
* @param channelControlHelper `ChannelControlHelper` instance provided by
* this load balancer's owner.
*/
constructor(channelControlHelper) {
this.channelControlHelper = channelControlHelper;
/**
* The list of subchannels this load balancer is currently attempting to
* connect to.
*/
this.children = [];
/**
* The current connectivity state of the load balancer.
*/
this.currentState = connectivity_state_1.ConnectivityState.IDLE;
/**
* The index within the `subchannels` array of the subchannel with the most
* recently started connection attempt.
*/
this.currentSubchannelIndex = 0;
/**
* The currently picked subchannel used for making calls. Populated if
* and only if the load balancer's current state is READY. In that case,
* the subchannel's current state is also READY.
*/
this.currentPick = null;
/**
* Listener callback attached to each subchannel in the `subchannels` list
* while establishing a connection.
*/
this.subchannelStateListener = (subchannel, previousState, newState, keepaliveTime, errorMessage) => {
this.onSubchannelStateUpdate(subchannel, previousState, newState, errorMessage);
};
this.pickedSubchannelHealthListener = () => this.calculateAndReportNewState();
/**
* The LB policy enters sticky TRANSIENT_FAILURE mode when all
* subchannels have failed to connect at least once, and it stays in that
* mode until a connection attempt is successful. While in sticky TF mode,
* the LB policy continuously attempts to connect to all of its subchannels.
*/
this.stickyTransientFailureMode = false;
this.reportHealthStatus = false;
/**
* The most recent error reported by any subchannel as it transitioned to
* TRANSIENT_FAILURE.
*/
this.lastError = null;
this.latestAddressList = null;
this.latestOptions = {};
this.latestResolutionNote = '';
this.connectionDelayTimeout = setTimeout(() => { }, 0);
clearTimeout(this.connectionDelayTimeout);
}
allChildrenHaveReportedTF() {
return this.children.every(child => child.hasReportedTransientFailure);
}
resetChildrenReportedTF() {
this.children.every(child => child.hasReportedTransientFailure = false);
}
calculateAndReportNewState() {
var _a;
if (this.currentPick) {
if (this.reportHealthStatus && !this.currentPick.isHealthy()) {
const errorMessage = `Picked subchannel ${this.currentPick.getAddress()} is unhealthy`;
this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
details: errorMessage,
}), errorMessage);
}
else {
this.updateState(connectivity_state_1.ConnectivityState.READY, new PickFirstPicker(this.currentPick), null);
}
}
else if (((_a = this.latestAddressList) === null || _a === void 0 ? void 0 : _a.length) === 0) {
const errorMessage = `No connection established. Last error: ${this.lastError}. Resolution note: ${this.latestResolutionNote}`;
this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
details: errorMessage,
}), errorMessage);
}
else if (this.children.length === 0) {
this.updateState(connectivity_state_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this), null);
}
else {
if (this.stickyTransientFailureMode) {
const errorMessage = `No connection established. Last error: ${this.lastError}. Resolution note: ${this.latestResolutionNote}`;
this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
details: errorMessage,
}), errorMessage);
}
else {
this.updateState(connectivity_state_1.ConnectivityState.CONNECTING, new picker_1.QueuePicker(this), null);
}
}
}
requestReresolution() {
this.channelControlHelper.requestReresolution();
}
maybeEnterStickyTransientFailureMode() {
if (!this.allChildrenHaveReportedTF()) {
return;
}
this.requestReresolution();
this.resetChildrenReportedTF();
if (this.stickyTransientFailureMode) {
this.calculateAndReportNewState();
return;
}
this.stickyTransientFailureMode = true;
for (const { subchannel } of this.children) {
subchannel.startConnecting();
}
this.calculateAndReportNewState();
}
removeCurrentPick() {
if (this.currentPick !== null) {
this.currentPick.removeConnectivityStateListener(this.subchannelStateListener);
this.channelControlHelper.removeChannelzChild(this.currentPick.getChannelzRef());
this.currentPick.removeHealthStateWatcher(this.pickedSubchannelHealthListener);
// Unref last, to avoid triggering listeners
this.currentPick.unref();
this.currentPick = null;
}
}
onSubchannelStateUpdate(subchannel, previousState, newState, errorMessage) {
var _a;
if ((_a = this.currentPick) === null || _a === void 0 ? void 0 : _a.realSubchannelEquals(subchannel)) {
if (newState !== connectivity_state_1.ConnectivityState.READY) {
this.removeCurrentPick();
this.calculateAndReportNewState();
}
return;
}
for (const [index, child] of this.children.entries()) {
if (subchannel.realSubchannelEquals(child.subchannel)) {
if (newState === connectivity_state_1.ConnectivityState.READY) {
this.pickSubchannel(child.subchannel);
}
if (newState === connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) {
child.hasReportedTransientFailure = true;
if (errorMessage) {
this.lastError = errorMessage;
}
this.maybeEnterStickyTransientFailureMode();
if (index === this.currentSubchannelIndex) {
this.startNextSubchannelConnecting(index + 1);
}
}
child.subchannel.startConnecting();
return;
}
}
}
startNextSubchannelConnecting(startIndex) {
clearTimeout(this.connectionDelayTimeout);
for (const [index, child] of this.children.entries()) {
if (index >= startIndex) {
const subchannelState = child.subchannel.getConnectivityState();
if (subchannelState === connectivity_state_1.ConnectivityState.IDLE ||
subchannelState === connectivity_state_1.ConnectivityState.CONNECTING) {
this.startConnecting(index);
return;
}
}
}
this.maybeEnterStickyTransientFailureMode();
}
/**
* Have a single subchannel in the `subchannels` list start connecting.
* @param subchannelIndex The index into the `subchannels` list.
*/
startConnecting(subchannelIndex) {
var _a, _b;
clearTimeout(this.connectionDelayTimeout);
this.currentSubchannelIndex = subchannelIndex;
if (this.children[subchannelIndex].subchannel.getConnectivityState() ===
connectivity_state_1.ConnectivityState.IDLE) {
trace('Start connecting to subchannel with address ' +
this.children[subchannelIndex].subchannel.getAddress());
process.nextTick(() => {
var _a;
(_a = this.children[subchannelIndex]) === null || _a === void 0 ? void 0 : _a.subchannel.startConnecting();
});
}
this.connectionDelayTimeout = setTimeout(() => {
this.startNextSubchannelConnecting(subchannelIndex + 1);
}, CONNECTION_DELAY_INTERVAL_MS);
(_b = (_a = this.connectionDelayTimeout).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
}
/**
* Declare that the specified subchannel should be used to make requests.
* This functions the same independent of whether subchannel is a member of
* this.children and whether it is equal to this.currentPick.
* Prerequisite: subchannel.getConnectivityState() === READY.
* @param subchannel
*/
pickSubchannel(subchannel) {
trace('Pick subchannel with address ' + subchannel.getAddress());
this.stickyTransientFailureMode = false;
/* Ref before removeCurrentPick and resetSubchannelList to avoid the
* refcount dropping to 0 during this process. */
subchannel.ref();
this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef());
this.removeCurrentPick();
this.resetSubchannelList();
subchannel.addConnectivityStateListener(this.subchannelStateListener);
subchannel.addHealthStateWatcher(this.pickedSubchannelHealthListener);
this.currentPick = subchannel;
clearTimeout(this.connectionDelayTimeout);
this.calculateAndReportNewState();
}
updateState(newState, picker, errorMessage) {
trace(connectivity_state_1.ConnectivityState[this.currentState] +
' -> ' +
connectivity_state_1.ConnectivityState[newState]);
this.currentState = newState;
this.channelControlHelper.updateState(newState, picker, errorMessage);
}
resetSubchannelList() {
for (const child of this.children) {
/* Always remoev the connectivity state listener. If the subchannel is
getting picked, it will be re-added then. */
child.subchannel.removeConnectivityStateListener(this.subchannelStateListener);
/* Refs are counted independently for the children list and the
* currentPick, so we call unref whether or not the child is the
* currentPick. Channelz child references are also refcounted, so
* removeChannelzChild can be handled the same way. */
child.subchannel.unref();
this.channelControlHelper.removeChannelzChild(child.subchannel.getChannelzRef());
}
this.currentSubchannelIndex = 0;
this.children = [];
}
connectToAddressList(addressList, options) {
trace('connectToAddressList([' + addressList.map(address => (0, subchannel_address_1.subchannelAddressToString)(address)) + '])');
const newChildrenList = addressList.map(address => ({
subchannel: this.channelControlHelper.createSubchannel(address, options),
hasReportedTransientFailure: false,
}));
for (const { subchannel } of newChildrenList) {
if (subchannel.getConnectivityState() === connectivity_state_1.ConnectivityState.READY) {
this.pickSubchannel(subchannel);
return;
}
}
/* Ref each subchannel before resetting the list, to ensure that
* subchannels shared between the list don't drop to 0 refs during the
* transition. */
for (const { subchannel } of newChildrenList) {
subchannel.ref();
this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef());
}
this.resetSubchannelList();
this.children = newChildrenList;
for (const { subchannel } of this.children) {
subchannel.addConnectivityStateListener(this.subchannelStateListener);
}
for (const child of this.children) {
if (child.subchannel.getConnectivityState() ===
connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) {
child.hasReportedTransientFailure = true;
}
}
this.startNextSubchannelConnecting(0);
this.calculateAndReportNewState();
}
updateAddressList(maybeEndpointList, lbConfig, options, resolutionNote) {
if (!(lbConfig instanceof PickFirstLoadBalancingConfig)) {
return false;
}
if (!maybeEndpointList.ok) {
if (this.children.length === 0 && this.currentPick === null) {
this.channelControlHelper.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker(maybeEndpointList.error), maybeEndpointList.error.details);
}
return true;
}
let endpointList = maybeEndpointList.value;
this.reportHealthStatus = options[REPORT_HEALTH_STATUS_OPTION_NAME];
/* Previously, an update would be discarded if it was identical to the
* previous update, to minimize churn. Now the DNS resolver is
* rate-limited, so that is less of a concern. */
if (lbConfig.getShuffleAddressList()) {
endpointList = shuffled(endpointList);
}
const rawAddressList = [].concat(...endpointList.map(endpoint => endpoint.addresses));
trace('updateAddressList([' + rawAddressList.map(address => (0, subchannel_address_1.subchannelAddressToString)(address)) + '])');
const addressList = interleaveAddressFamilies(rawAddressList);
this.latestAddressList = addressList;
this.latestOptions = options;
this.connectToAddressList(addressList, options);
this.latestResolutionNote = resolutionNote;
if (rawAddressList.length > 0) {
return true;
}
else {
this.lastError = 'No addresses resolved';
return false;
}
}
exitIdle() {
if (this.currentState === connectivity_state_1.ConnectivityState.IDLE &&
this.latestAddressList) {
this.connectToAddressList(this.latestAddressList, this.latestOptions);
}
}
resetBackoff() {
/* The pick first load balancer does not have a connection backoff, so this
* does nothing */
}
destroy() {
this.resetSubchannelList();
this.removeCurrentPick();
}
getTypeName() {
return TYPE_NAME;
}
}
exports.PickFirstLoadBalancer = PickFirstLoadBalancer;
const LEAF_CONFIG = new PickFirstLoadBalancingConfig(false);
/**
* This class handles the leaf load balancing operations for a single endpoint.
* It is a thin wrapper around a PickFirstLoadBalancer with a different API
* that more closely reflects how it will be used as a leaf balancer.
*/
class LeafLoadBalancer {
constructor(endpoint, channelControlHelper, options, resolutionNote) {
this.endpoint = endpoint;
this.options = options;
this.resolutionNote = resolutionNote;
this.latestState = connectivity_state_1.ConnectivityState.IDLE;
const childChannelControlHelper = (0, load_balancer_1.createChildChannelControlHelper)(channelControlHelper, {
updateState: (connectivityState, picker, errorMessage) => {
this.latestState = connectivityState;
this.latestPicker = picker;
channelControlHelper.updateState(connectivityState, picker, errorMessage);
},
});
this.pickFirstBalancer = new PickFirstLoadBalancer(childChannelControlHelper);
this.latestPicker = new picker_1.QueuePicker(this.pickFirstBalancer);
}
startConnecting() {
this.pickFirstBalancer.updateAddressList((0, call_interface_1.statusOrFromValue)([this.endpoint]), LEAF_CONFIG, Object.assign(Object.assign({}, this.options), { [REPORT_HEALTH_STATUS_OPTION_NAME]: true }), this.resolutionNote);
}
/**
* Update the endpoint associated with this LeafLoadBalancer to a new
* endpoint. Does not trigger connection establishment if a connection
* attempt is not already in progress.
* @param newEndpoint
*/
updateEndpoint(newEndpoint, newOptions) {
this.options = newOptions;
this.endpoint = newEndpoint;
if (this.latestState !== connectivity_state_1.ConnectivityState.IDLE) {
this.startConnecting();
}
}
getConnectivityState() {
return this.latestState;
}
getPicker() {
return this.latestPicker;
}
getEndpoint() {
return this.endpoint;
}
exitIdle() {
this.pickFirstBalancer.exitIdle();
}
destroy() {
this.pickFirstBalancer.destroy();
}
}
exports.LeafLoadBalancer = LeafLoadBalancer;
function setup() {
(0, load_balancer_1.registerLoadBalancerType)(TYPE_NAME, PickFirstLoadBalancer, PickFirstLoadBalancingConfig);
(0, load_balancer_1.registerDefaultLoadBalancerType)(TYPE_NAME);
}
//# sourceMappingURL=load-balancer-pick-first.js.map

View File

@@ -0,0 +1,204 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.RoundRobinLoadBalancer = void 0;
exports.setup = setup;
const load_balancer_1 = require("./load-balancer");
const connectivity_state_1 = require("./connectivity-state");
const picker_1 = require("./picker");
const logging = require("./logging");
const constants_1 = require("./constants");
const subchannel_address_1 = require("./subchannel-address");
const load_balancer_pick_first_1 = require("./load-balancer-pick-first");
const TRACER_NAME = 'round_robin';
function trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, text);
}
const TYPE_NAME = 'round_robin';
class RoundRobinLoadBalancingConfig {
getLoadBalancerName() {
return TYPE_NAME;
}
constructor() { }
toJsonObject() {
return {
[TYPE_NAME]: {},
};
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static createFromJson(obj) {
return new RoundRobinLoadBalancingConfig();
}
}
class RoundRobinPicker {
constructor(children, nextIndex = 0) {
this.children = children;
this.nextIndex = nextIndex;
}
pick(pickArgs) {
const childPicker = this.children[this.nextIndex].picker;
this.nextIndex = (this.nextIndex + 1) % this.children.length;
return childPicker.pick(pickArgs);
}
/**
* Check what the next subchannel returned would be. Used by the load
* balancer implementation to preserve this part of the picker state if
* possible when a subchannel connects or disconnects.
*/
peekNextEndpoint() {
return this.children[this.nextIndex].endpoint;
}
}
function rotateArray(list, startIndex) {
return [...list.slice(startIndex), ...list.slice(0, startIndex)];
}
class RoundRobinLoadBalancer {
constructor(channelControlHelper) {
this.channelControlHelper = channelControlHelper;
this.children = [];
this.currentState = connectivity_state_1.ConnectivityState.IDLE;
this.currentReadyPicker = null;
this.updatesPaused = false;
this.lastError = null;
this.childChannelControlHelper = (0, load_balancer_1.createChildChannelControlHelper)(channelControlHelper, {
updateState: (connectivityState, picker, errorMessage) => {
/* Ensure that name resolution is requested again after active
* connections are dropped. This is more aggressive than necessary to
* accomplish that, so we are counting on resolvers to have
* reasonable rate limits. */
if (this.currentState === connectivity_state_1.ConnectivityState.READY && connectivityState !== connectivity_state_1.ConnectivityState.READY) {
this.channelControlHelper.requestReresolution();
}
if (errorMessage) {
this.lastError = errorMessage;
}
this.calculateAndUpdateState();
},
});
}
countChildrenWithState(state) {
return this.children.filter(child => child.getConnectivityState() === state)
.length;
}
calculateAndUpdateState() {
if (this.updatesPaused) {
return;
}
if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.READY) > 0) {
const readyChildren = this.children.filter(child => child.getConnectivityState() === connectivity_state_1.ConnectivityState.READY);
let index = 0;
if (this.currentReadyPicker !== null) {
const nextPickedEndpoint = this.currentReadyPicker.peekNextEndpoint();
index = readyChildren.findIndex(child => (0, subchannel_address_1.endpointEqual)(child.getEndpoint(), nextPickedEndpoint));
if (index < 0) {
index = 0;
}
}
this.updateState(connectivity_state_1.ConnectivityState.READY, new RoundRobinPicker(readyChildren.map(child => ({
endpoint: child.getEndpoint(),
picker: child.getPicker(),
})), index), null);
}
else if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.CONNECTING) > 0) {
this.updateState(connectivity_state_1.ConnectivityState.CONNECTING, new picker_1.QueuePicker(this), null);
}
else if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) > 0) {
const errorMessage = `round_robin: No connection established. Last error: ${this.lastError}`;
this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
details: errorMessage,
}), errorMessage);
}
else {
this.updateState(connectivity_state_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this), null);
}
/* round_robin should keep all children connected, this is how we do that.
* We can't do this more efficiently in the individual child's updateState
* callback because that doesn't have a reference to which child the state
* change is associated with. */
for (const child of this.children) {
if (child.getConnectivityState() === connectivity_state_1.ConnectivityState.IDLE) {
child.exitIdle();
}
}
}
updateState(newState, picker, errorMessage) {
trace(connectivity_state_1.ConnectivityState[this.currentState] +
' -> ' +
connectivity_state_1.ConnectivityState[newState]);
if (newState === connectivity_state_1.ConnectivityState.READY) {
this.currentReadyPicker = picker;
}
else {
this.currentReadyPicker = null;
}
this.currentState = newState;
this.channelControlHelper.updateState(newState, picker, errorMessage);
}
resetSubchannelList() {
for (const child of this.children) {
child.destroy();
}
this.children = [];
}
updateAddressList(maybeEndpointList, lbConfig, options, resolutionNote) {
if (!(lbConfig instanceof RoundRobinLoadBalancingConfig)) {
return false;
}
if (!maybeEndpointList.ok) {
if (this.children.length === 0) {
this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker(maybeEndpointList.error), maybeEndpointList.error.details);
}
return true;
}
const startIndex = (Math.random() * maybeEndpointList.value.length) | 0;
const endpointList = rotateArray(maybeEndpointList.value, startIndex);
this.resetSubchannelList();
if (endpointList.length === 0) {
const errorMessage = `No addresses resolved. Resolution note: ${resolutionNote}`;
this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({ details: errorMessage }), errorMessage);
}
trace('Connect to endpoint list ' + endpointList.map(subchannel_address_1.endpointToString));
this.updatesPaused = true;
this.children = endpointList.map(endpoint => new load_balancer_pick_first_1.LeafLoadBalancer(endpoint, this.childChannelControlHelper, options, resolutionNote));
for (const child of this.children) {
child.startConnecting();
}
this.updatesPaused = false;
this.calculateAndUpdateState();
return true;
}
exitIdle() {
/* The round_robin LB policy is only in the IDLE state if it has no
* addresses to try to connect to and it has no picked subchannel.
* In that case, there is no meaningful action that can be taken here. */
}
resetBackoff() {
// This LB policy has no backoff to reset
}
destroy() {
this.resetSubchannelList();
}
getTypeName() {
return TYPE_NAME;
}
}
exports.RoundRobinLoadBalancer = RoundRobinLoadBalancer;
function setup() {
(0, load_balancer_1.registerLoadBalancerType)(TYPE_NAME, RoundRobinLoadBalancer, RoundRobinLoadBalancingConfig);
}
//# sourceMappingURL=load-balancer-round-robin.js.map

View File

@@ -0,0 +1,392 @@
"use strict";
/*
* Copyright 2025 gRPC authors.
*
* Licensed 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.WeightedRoundRobinLoadBalancingConfig = void 0;
exports.setup = setup;
const connectivity_state_1 = require("./connectivity-state");
const constants_1 = require("./constants");
const duration_1 = require("./duration");
const load_balancer_1 = require("./load-balancer");
const load_balancer_pick_first_1 = require("./load-balancer-pick-first");
const logging = require("./logging");
const orca_1 = require("./orca");
const picker_1 = require("./picker");
const priority_queue_1 = require("./priority-queue");
const subchannel_address_1 = require("./subchannel-address");
const TRACER_NAME = 'weighted_round_robin';
function trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, text);
}
const TYPE_NAME = 'weighted_round_robin';
const DEFAULT_OOB_REPORTING_PERIOD_MS = 10000;
const DEFAULT_BLACKOUT_PERIOD_MS = 10000;
const DEFAULT_WEIGHT_EXPIRATION_PERIOD_MS = 3 * 60000;
const DEFAULT_WEIGHT_UPDATE_PERIOD_MS = 1000;
const DEFAULT_ERROR_UTILIZATION_PENALTY = 1;
function validateFieldType(obj, fieldName, expectedType) {
if (fieldName in obj &&
obj[fieldName] !== undefined &&
typeof obj[fieldName] !== expectedType) {
throw new Error(`weighted round robin config ${fieldName} parse error: expected ${expectedType}, got ${typeof obj[fieldName]}`);
}
}
function parseDurationField(obj, fieldName) {
if (fieldName in obj && obj[fieldName] !== undefined && obj[fieldName] !== null) {
let durationObject;
if ((0, duration_1.isDuration)(obj[fieldName])) {
durationObject = obj[fieldName];
}
else if ((0, duration_1.isDurationMessage)(obj[fieldName])) {
durationObject = (0, duration_1.durationMessageToDuration)(obj[fieldName]);
}
else if (typeof obj[fieldName] === 'string') {
const parsedDuration = (0, duration_1.parseDuration)(obj[fieldName]);
if (!parsedDuration) {
throw new Error(`weighted round robin config ${fieldName}: failed to parse duration string ${obj[fieldName]}`);
}
durationObject = parsedDuration;
}
else {
throw new Error(`weighted round robin config ${fieldName}: expected duration, got ${typeof obj[fieldName]}`);
}
return (0, duration_1.durationToMs)(durationObject);
}
return null;
}
class WeightedRoundRobinLoadBalancingConfig {
constructor(enableOobLoadReport, oobLoadReportingPeriodMs, blackoutPeriodMs, weightExpirationPeriodMs, weightUpdatePeriodMs, errorUtilizationPenalty) {
this.enableOobLoadReport = enableOobLoadReport !== null && enableOobLoadReport !== void 0 ? enableOobLoadReport : false;
this.oobLoadReportingPeriodMs = oobLoadReportingPeriodMs !== null && oobLoadReportingPeriodMs !== void 0 ? oobLoadReportingPeriodMs : DEFAULT_OOB_REPORTING_PERIOD_MS;
this.blackoutPeriodMs = blackoutPeriodMs !== null && blackoutPeriodMs !== void 0 ? blackoutPeriodMs : DEFAULT_BLACKOUT_PERIOD_MS;
this.weightExpirationPeriodMs = weightExpirationPeriodMs !== null && weightExpirationPeriodMs !== void 0 ? weightExpirationPeriodMs : DEFAULT_WEIGHT_EXPIRATION_PERIOD_MS;
this.weightUpdatePeriodMs = Math.max(weightUpdatePeriodMs !== null && weightUpdatePeriodMs !== void 0 ? weightUpdatePeriodMs : DEFAULT_WEIGHT_UPDATE_PERIOD_MS, 100);
this.errorUtilizationPenalty = errorUtilizationPenalty !== null && errorUtilizationPenalty !== void 0 ? errorUtilizationPenalty : DEFAULT_ERROR_UTILIZATION_PENALTY;
}
getLoadBalancerName() {
return TYPE_NAME;
}
toJsonObject() {
return {
enable_oob_load_report: this.enableOobLoadReport,
oob_load_reporting_period: (0, duration_1.durationToString)((0, duration_1.msToDuration)(this.oobLoadReportingPeriodMs)),
blackout_period: (0, duration_1.durationToString)((0, duration_1.msToDuration)(this.blackoutPeriodMs)),
weight_expiration_period: (0, duration_1.durationToString)((0, duration_1.msToDuration)(this.weightExpirationPeriodMs)),
weight_update_period: (0, duration_1.durationToString)((0, duration_1.msToDuration)(this.weightUpdatePeriodMs)),
error_utilization_penalty: this.errorUtilizationPenalty
};
}
static createFromJson(obj) {
validateFieldType(obj, 'enable_oob_load_report', 'boolean');
validateFieldType(obj, 'error_utilization_penalty', 'number');
if (obj.error_utilization_penalty < 0) {
throw new Error('weighted round robin config error_utilization_penalty < 0');
}
return new WeightedRoundRobinLoadBalancingConfig(obj.enable_oob_load_report, parseDurationField(obj, 'oob_load_reporting_period'), parseDurationField(obj, 'blackout_period'), parseDurationField(obj, 'weight_expiration_period'), parseDurationField(obj, 'weight_update_period'), obj.error_utilization_penalty);
}
getEnableOobLoadReport() {
return this.enableOobLoadReport;
}
getOobLoadReportingPeriodMs() {
return this.oobLoadReportingPeriodMs;
}
getBlackoutPeriodMs() {
return this.blackoutPeriodMs;
}
getWeightExpirationPeriodMs() {
return this.weightExpirationPeriodMs;
}
getWeightUpdatePeriodMs() {
return this.weightUpdatePeriodMs;
}
getErrorUtilizationPenalty() {
return this.errorUtilizationPenalty;
}
}
exports.WeightedRoundRobinLoadBalancingConfig = WeightedRoundRobinLoadBalancingConfig;
class WeightedRoundRobinPicker {
constructor(children, metricsHandler) {
this.metricsHandler = metricsHandler;
this.queue = new priority_queue_1.PriorityQueue((a, b) => a.deadline < b.deadline);
const positiveWeight = children.filter(picker => picker.weight > 0);
let averageWeight;
if (positiveWeight.length < 2) {
averageWeight = 1;
}
else {
let weightSum = 0;
for (const { weight } of positiveWeight) {
weightSum += weight;
}
averageWeight = weightSum / positiveWeight.length;
}
for (const child of children) {
const period = child.weight > 0 ? 1 / child.weight : averageWeight;
this.queue.push({
endpointName: child.endpointName,
picker: child.picker,
period: period,
deadline: Math.random() * period
});
}
}
pick(pickArgs) {
const entry = this.queue.pop();
this.queue.push(Object.assign(Object.assign({}, entry), { deadline: entry.deadline + entry.period }));
const childPick = entry.picker.pick(pickArgs);
if (childPick.pickResultType === picker_1.PickResultType.COMPLETE) {
if (this.metricsHandler) {
return Object.assign(Object.assign({}, childPick), { onCallEnded: (0, orca_1.createMetricsReader)(loadReport => this.metricsHandler(loadReport, entry.endpointName), childPick.onCallEnded) });
}
else {
const subchannelWrapper = childPick.subchannel;
return Object.assign(Object.assign({}, childPick), { subchannel: subchannelWrapper.getWrappedSubchannel() });
}
}
else {
return childPick;
}
}
}
class WeightedRoundRobinLoadBalancer {
constructor(channelControlHelper) {
this.channelControlHelper = channelControlHelper;
this.latestConfig = null;
this.children = new Map();
this.currentState = connectivity_state_1.ConnectivityState.IDLE;
this.updatesPaused = false;
this.lastError = null;
this.weightUpdateTimer = null;
}
countChildrenWithState(state) {
let count = 0;
for (const entry of this.children.values()) {
if (entry.child.getConnectivityState() === state) {
count += 1;
}
}
return count;
}
updateWeight(entry, loadReport) {
var _a, _b;
const qps = loadReport.rps_fractional;
let utilization = loadReport.application_utilization;
if (utilization > 0 && qps > 0) {
utilization += (loadReport.eps / qps) * ((_b = (_a = this.latestConfig) === null || _a === void 0 ? void 0 : _a.getErrorUtilizationPenalty()) !== null && _b !== void 0 ? _b : 0);
}
const newWeight = utilization === 0 ? 0 : qps / utilization;
if (newWeight === 0) {
return;
}
const now = new Date();
if (entry.nonEmptySince === null) {
entry.nonEmptySince = now;
}
entry.lastUpdated = now;
entry.weight = newWeight;
}
getWeight(entry) {
if (!this.latestConfig) {
return 0;
}
const now = new Date().getTime();
if (now - entry.lastUpdated.getTime() >= this.latestConfig.getWeightExpirationPeriodMs()) {
entry.nonEmptySince = null;
return 0;
}
const blackoutPeriod = this.latestConfig.getBlackoutPeriodMs();
if (blackoutPeriod > 0 && (entry.nonEmptySince === null || now - entry.nonEmptySince.getTime() < blackoutPeriod)) {
return 0;
}
return entry.weight;
}
calculateAndUpdateState() {
if (this.updatesPaused || !this.latestConfig) {
return;
}
if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.READY) > 0) {
const weightedPickers = [];
for (const [endpoint, entry] of this.children) {
if (entry.child.getConnectivityState() !== connectivity_state_1.ConnectivityState.READY) {
continue;
}
weightedPickers.push({
endpointName: endpoint,
picker: entry.child.getPicker(),
weight: this.getWeight(entry)
});
}
trace('Created picker with weights: ' + weightedPickers.map(entry => entry.endpointName + ':' + entry.weight).join(','));
let metricsHandler;
if (!this.latestConfig.getEnableOobLoadReport()) {
metricsHandler = (loadReport, endpointName) => {
const childEntry = this.children.get(endpointName);
if (childEntry) {
this.updateWeight(childEntry, loadReport);
}
};
}
else {
metricsHandler = null;
}
this.updateState(connectivity_state_1.ConnectivityState.READY, new WeightedRoundRobinPicker(weightedPickers, metricsHandler), null);
}
else if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.CONNECTING) > 0) {
this.updateState(connectivity_state_1.ConnectivityState.CONNECTING, new picker_1.QueuePicker(this), null);
}
else if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) > 0) {
const errorMessage = `weighted_round_robin: No connection established. Last error: ${this.lastError}`;
this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
details: errorMessage,
}), errorMessage);
}
else {
this.updateState(connectivity_state_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this), null);
}
/* round_robin should keep all children connected, this is how we do that.
* We can't do this more efficiently in the individual child's updateState
* callback because that doesn't have a reference to which child the state
* change is associated with. */
for (const { child } of this.children.values()) {
if (child.getConnectivityState() === connectivity_state_1.ConnectivityState.IDLE) {
child.exitIdle();
}
}
}
updateState(newState, picker, errorMessage) {
trace(connectivity_state_1.ConnectivityState[this.currentState] +
' -> ' +
connectivity_state_1.ConnectivityState[newState]);
this.currentState = newState;
this.channelControlHelper.updateState(newState, picker, errorMessage);
}
updateAddressList(maybeEndpointList, lbConfig, options, resolutionNote) {
var _a, _b;
if (!(lbConfig instanceof WeightedRoundRobinLoadBalancingConfig)) {
return false;
}
if (!maybeEndpointList.ok) {
if (this.children.size === 0) {
this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker(maybeEndpointList.error), maybeEndpointList.error.details);
}
return true;
}
if (maybeEndpointList.value.length === 0) {
const errorMessage = `No addresses resolved. Resolution note: ${resolutionNote}`;
this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({ details: errorMessage }), errorMessage);
return false;
}
trace('Connect to endpoint list ' + maybeEndpointList.value.map(subchannel_address_1.endpointToString));
const now = new Date();
const seenEndpointNames = new Set();
this.updatesPaused = true;
this.latestConfig = lbConfig;
for (const endpoint of maybeEndpointList.value) {
const name = (0, subchannel_address_1.endpointToString)(endpoint);
seenEndpointNames.add(name);
let entry = this.children.get(name);
if (!entry) {
entry = {
child: new load_balancer_pick_first_1.LeafLoadBalancer(endpoint, (0, load_balancer_1.createChildChannelControlHelper)(this.channelControlHelper, {
updateState: (connectivityState, picker, errorMessage) => {
/* Ensure that name resolution is requested again after active
* connections are dropped. This is more aggressive than necessary to
* accomplish that, so we are counting on resolvers to have
* reasonable rate limits. */
if (this.currentState === connectivity_state_1.ConnectivityState.READY && connectivityState !== connectivity_state_1.ConnectivityState.READY) {
this.channelControlHelper.requestReresolution();
}
if (connectivityState === connectivity_state_1.ConnectivityState.READY) {
entry.nonEmptySince = null;
}
if (errorMessage) {
this.lastError = errorMessage;
}
this.calculateAndUpdateState();
},
createSubchannel: (subchannelAddress, subchannelArgs) => {
const subchannel = this.channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs);
if (entry === null || entry === void 0 ? void 0 : entry.oobMetricsListener) {
return new orca_1.OrcaOobMetricsSubchannelWrapper(subchannel, entry.oobMetricsListener, this.latestConfig.getOobLoadReportingPeriodMs());
}
else {
return subchannel;
}
}
}), options, resolutionNote),
lastUpdated: now,
nonEmptySince: null,
weight: 0,
oobMetricsListener: null
};
this.children.set(name, entry);
}
if (lbConfig.getEnableOobLoadReport()) {
entry.oobMetricsListener = loadReport => {
this.updateWeight(entry, loadReport);
};
}
else {
entry.oobMetricsListener = null;
}
}
for (const [endpointName, entry] of this.children) {
if (seenEndpointNames.has(endpointName)) {
entry.child.startConnecting();
}
else {
entry.child.destroy();
this.children.delete(endpointName);
}
}
this.updatesPaused = false;
this.calculateAndUpdateState();
if (this.weightUpdateTimer) {
clearInterval(this.weightUpdateTimer);
}
this.weightUpdateTimer = (_b = (_a = setInterval(() => {
if (this.currentState === connectivity_state_1.ConnectivityState.READY) {
this.calculateAndUpdateState();
}
}, lbConfig.getWeightUpdatePeriodMs())).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
return true;
}
exitIdle() {
/* The weighted_round_robin LB policy is only in the IDLE state if it has
* no addresses to try to connect to and it has no picked subchannel.
* In that case, there is no meaningful action that can be taken here. */
}
resetBackoff() {
// This LB policy has no backoff to reset
}
destroy() {
for (const entry of this.children.values()) {
entry.child.destroy();
}
this.children.clear();
if (this.weightUpdateTimer) {
clearInterval(this.weightUpdateTimer);
}
}
getTypeName() {
return TYPE_NAME;
}
}
function setup() {
(0, load_balancer_1.registerLoadBalancerType)(TYPE_NAME, WeightedRoundRobinLoadBalancer, WeightedRoundRobinLoadBalancingConfig);
}
//# sourceMappingURL=load-balancer-weighted-round-robin.js.map

View File

@@ -0,0 +1,116 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.createChildChannelControlHelper = createChildChannelControlHelper;
exports.registerLoadBalancerType = registerLoadBalancerType;
exports.registerDefaultLoadBalancerType = registerDefaultLoadBalancerType;
exports.createLoadBalancer = createLoadBalancer;
exports.isLoadBalancerNameRegistered = isLoadBalancerNameRegistered;
exports.parseLoadBalancingConfig = parseLoadBalancingConfig;
exports.getDefaultConfig = getDefaultConfig;
exports.selectLbConfigFromList = selectLbConfigFromList;
const logging_1 = require("./logging");
const constants_1 = require("./constants");
/**
* Create a child ChannelControlHelper that overrides some methods of the
* parent while letting others pass through to the parent unmodified. This
* allows other code to create these children without needing to know about
* all of the methods to be passed through.
* @param parent
* @param overrides
*/
function createChildChannelControlHelper(parent, overrides) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
return {
createSubchannel: (_b = (_a = overrides.createSubchannel) === null || _a === void 0 ? void 0 : _a.bind(overrides)) !== null && _b !== void 0 ? _b : parent.createSubchannel.bind(parent),
updateState: (_d = (_c = overrides.updateState) === null || _c === void 0 ? void 0 : _c.bind(overrides)) !== null && _d !== void 0 ? _d : parent.updateState.bind(parent),
requestReresolution: (_f = (_e = overrides.requestReresolution) === null || _e === void 0 ? void 0 : _e.bind(overrides)) !== null && _f !== void 0 ? _f : parent.requestReresolution.bind(parent),
addChannelzChild: (_h = (_g = overrides.addChannelzChild) === null || _g === void 0 ? void 0 : _g.bind(overrides)) !== null && _h !== void 0 ? _h : parent.addChannelzChild.bind(parent),
removeChannelzChild: (_k = (_j = overrides.removeChannelzChild) === null || _j === void 0 ? void 0 : _j.bind(overrides)) !== null && _k !== void 0 ? _k : parent.removeChannelzChild.bind(parent),
};
}
const registeredLoadBalancerTypes = {};
let defaultLoadBalancerType = null;
function registerLoadBalancerType(typeName, loadBalancerType, loadBalancingConfigType) {
registeredLoadBalancerTypes[typeName] = {
LoadBalancer: loadBalancerType,
LoadBalancingConfig: loadBalancingConfigType,
};
}
function registerDefaultLoadBalancerType(typeName) {
defaultLoadBalancerType = typeName;
}
function createLoadBalancer(config, channelControlHelper) {
const typeName = config.getLoadBalancerName();
if (typeName in registeredLoadBalancerTypes) {
return new registeredLoadBalancerTypes[typeName].LoadBalancer(channelControlHelper);
}
else {
return null;
}
}
function isLoadBalancerNameRegistered(typeName) {
return typeName in registeredLoadBalancerTypes;
}
function parseLoadBalancingConfig(rawConfig) {
const keys = Object.keys(rawConfig);
if (keys.length !== 1) {
throw new Error('Provided load balancing config has multiple conflicting entries');
}
const typeName = keys[0];
if (typeName in registeredLoadBalancerTypes) {
try {
return registeredLoadBalancerTypes[typeName].LoadBalancingConfig.createFromJson(rawConfig[typeName]);
}
catch (e) {
throw new Error(`${typeName}: ${e.message}`);
}
}
else {
throw new Error(`Unrecognized load balancing config name ${typeName}`);
}
}
function getDefaultConfig() {
if (!defaultLoadBalancerType) {
throw new Error('No default load balancer type registered');
}
return new registeredLoadBalancerTypes[defaultLoadBalancerType].LoadBalancingConfig();
}
function selectLbConfigFromList(configs, fallbackTodefault = false) {
for (const config of configs) {
try {
return parseLoadBalancingConfig(config);
}
catch (e) {
(0, logging_1.log)(constants_1.LogVerbosity.DEBUG, 'Config parsing failed with error', e.message);
continue;
}
}
if (fallbackTodefault) {
if (defaultLoadBalancerType) {
return new registeredLoadBalancerTypes[defaultLoadBalancerType].LoadBalancingConfig();
}
else {
return null;
}
}
else {
return null;
}
}
//# sourceMappingURL=load-balancer.js.map

View File

@@ -0,0 +1,302 @@
"use strict";
/*
* Copyright 2022 gRPC authors.
*
* Licensed 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.LoadBalancingCall = void 0;
const connectivity_state_1 = require("./connectivity-state");
const constants_1 = require("./constants");
const deadline_1 = require("./deadline");
const metadata_1 = require("./metadata");
const picker_1 = require("./picker");
const uri_parser_1 = require("./uri-parser");
const logging = require("./logging");
const control_plane_status_1 = require("./control-plane-status");
const http2 = require("http2");
const TRACER_NAME = 'load_balancing_call';
class LoadBalancingCall {
constructor(channel, callConfig, methodName, host, credentials, deadline, callNumber) {
var _a, _b;
this.channel = channel;
this.callConfig = callConfig;
this.methodName = methodName;
this.host = host;
this.credentials = credentials;
this.deadline = deadline;
this.callNumber = callNumber;
this.child = null;
this.readPending = false;
this.pendingMessage = null;
this.pendingHalfClose = false;
this.ended = false;
this.metadata = null;
this.listener = null;
this.onCallEnded = null;
this.childStartTime = null;
const splitPath = this.methodName.split('/');
let serviceName = '';
/* The standard path format is "/{serviceName}/{methodName}", so if we split
* by '/', the first item should be empty and the second should be the
* service name */
if (splitPath.length >= 2) {
serviceName = splitPath[1];
}
const hostname = (_b = (_a = (0, uri_parser_1.splitHostPort)(this.host)) === null || _a === void 0 ? void 0 : _a.host) !== null && _b !== void 0 ? _b : 'localhost';
/* Currently, call credentials are only allowed on HTTPS connections, so we
* can assume that the scheme is "https" */
this.serviceUrl = `https://${hostname}/${serviceName}`;
this.startTime = new Date();
}
getDeadlineInfo() {
var _a, _b;
const deadlineInfo = [];
if (this.childStartTime) {
if (this.childStartTime > this.startTime) {
if ((_a = this.metadata) === null || _a === void 0 ? void 0 : _a.getOptions().waitForReady) {
deadlineInfo.push('wait_for_ready');
}
deadlineInfo.push(`LB pick: ${(0, deadline_1.formatDateDifference)(this.startTime, this.childStartTime)}`);
}
deadlineInfo.push(...this.child.getDeadlineInfo());
return deadlineInfo;
}
else {
if ((_b = this.metadata) === null || _b === void 0 ? void 0 : _b.getOptions().waitForReady) {
deadlineInfo.push('wait_for_ready');
}
deadlineInfo.push('Waiting for LB pick');
}
return deadlineInfo;
}
trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, '[' + this.callNumber + '] ' + text);
}
outputStatus(status, progress) {
var _a, _b;
if (!this.ended) {
this.ended = true;
this.trace('ended with status: code=' +
status.code +
' details="' +
status.details +
'" start time=' +
this.startTime.toISOString());
const finalStatus = Object.assign(Object.assign({}, status), { progress });
(_a = this.listener) === null || _a === void 0 ? void 0 : _a.onReceiveStatus(finalStatus);
(_b = this.onCallEnded) === null || _b === void 0 ? void 0 : _b.call(this, finalStatus.code, finalStatus.details, finalStatus.metadata);
}
}
doPick() {
var _a, _b;
if (this.ended) {
return;
}
if (!this.metadata) {
throw new Error('doPick called before start');
}
this.trace('Pick called');
const finalMetadata = this.metadata.clone();
const pickResult = this.channel.doPick(finalMetadata, this.callConfig.pickInformation);
const subchannelString = pickResult.subchannel
? '(' +
pickResult.subchannel.getChannelzRef().id +
') ' +
pickResult.subchannel.getAddress()
: '' + pickResult.subchannel;
this.trace('Pick result: ' +
picker_1.PickResultType[pickResult.pickResultType] +
' subchannel: ' +
subchannelString +
' status: ' +
((_a = pickResult.status) === null || _a === void 0 ? void 0 : _a.code) +
' ' +
((_b = pickResult.status) === null || _b === void 0 ? void 0 : _b.details));
switch (pickResult.pickResultType) {
case picker_1.PickResultType.COMPLETE:
const combinedCallCredentials = this.credentials.compose(pickResult.subchannel.getCallCredentials());
combinedCallCredentials
.generateMetadata({ method_name: this.methodName, service_url: this.serviceUrl })
.then(credsMetadata => {
var _a;
/* If this call was cancelled (e.g. by the deadline) before
* metadata generation finished, we shouldn't do anything with
* it. */
if (this.ended) {
this.trace('Credentials metadata generation finished after call ended');
return;
}
finalMetadata.merge(credsMetadata);
if (finalMetadata.get('authorization').length > 1) {
this.outputStatus({
code: constants_1.Status.INTERNAL,
details: '"authorization" metadata cannot have multiple values',
metadata: new metadata_1.Metadata(),
}, 'PROCESSED');
}
if (pickResult.subchannel.getConnectivityState() !==
connectivity_state_1.ConnectivityState.READY) {
this.trace('Picked subchannel ' +
subchannelString +
' has state ' +
connectivity_state_1.ConnectivityState[pickResult.subchannel.getConnectivityState()] +
' after getting credentials metadata. Retrying pick');
this.doPick();
return;
}
if (this.deadline !== Infinity) {
finalMetadata.set('grpc-timeout', (0, deadline_1.getDeadlineTimeoutString)(this.deadline));
}
try {
this.child = pickResult
.subchannel.getRealSubchannel()
.createCall(finalMetadata, this.host, this.methodName, {
onReceiveMetadata: metadata => {
this.trace('Received metadata');
this.listener.onReceiveMetadata(metadata);
},
onReceiveMessage: message => {
this.trace('Received message');
this.listener.onReceiveMessage(message);
},
onReceiveStatus: status => {
this.trace('Received status');
if (status.rstCode ===
http2.constants.NGHTTP2_REFUSED_STREAM) {
this.outputStatus(status, 'REFUSED');
}
else {
this.outputStatus(status, 'PROCESSED');
}
},
});
this.childStartTime = new Date();
}
catch (error) {
this.trace('Failed to start call on picked subchannel ' +
subchannelString +
' with error ' +
error.message);
this.outputStatus({
code: constants_1.Status.INTERNAL,
details: 'Failed to start HTTP/2 stream with error ' +
error.message,
metadata: new metadata_1.Metadata(),
}, 'NOT_STARTED');
return;
}
(_a = pickResult.onCallStarted) === null || _a === void 0 ? void 0 : _a.call(pickResult);
this.onCallEnded = pickResult.onCallEnded;
this.trace('Created child call [' + this.child.getCallNumber() + ']');
if (this.readPending) {
this.child.startRead();
}
if (this.pendingMessage) {
this.child.sendMessageWithContext(this.pendingMessage.context, this.pendingMessage.message);
}
if (this.pendingHalfClose) {
this.child.halfClose();
}
}, (error) => {
// We assume the error code isn't 0 (Status.OK)
const { code, details } = (0, control_plane_status_1.restrictControlPlaneStatusCode)(typeof error.code === 'number' ? error.code : constants_1.Status.UNKNOWN, `Getting metadata from plugin failed with error: ${error.message}`);
this.outputStatus({
code: code,
details: details,
metadata: new metadata_1.Metadata(),
}, 'PROCESSED');
});
break;
case picker_1.PickResultType.DROP:
const { code, details } = (0, control_plane_status_1.restrictControlPlaneStatusCode)(pickResult.status.code, pickResult.status.details);
setImmediate(() => {
this.outputStatus({ code, details, metadata: pickResult.status.metadata }, 'DROP');
});
break;
case picker_1.PickResultType.TRANSIENT_FAILURE:
if (this.metadata.getOptions().waitForReady) {
this.channel.queueCallForPick(this);
}
else {
const { code, details } = (0, control_plane_status_1.restrictControlPlaneStatusCode)(pickResult.status.code, pickResult.status.details);
setImmediate(() => {
this.outputStatus({ code, details, metadata: pickResult.status.metadata }, 'PROCESSED');
});
}
break;
case picker_1.PickResultType.QUEUE:
this.channel.queueCallForPick(this);
}
}
cancelWithStatus(status, details) {
var _a;
this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"');
(_a = this.child) === null || _a === void 0 ? void 0 : _a.cancelWithStatus(status, details);
this.outputStatus({ code: status, details: details, metadata: new metadata_1.Metadata() }, 'PROCESSED');
}
getPeer() {
var _a, _b;
return (_b = (_a = this.child) === null || _a === void 0 ? void 0 : _a.getPeer()) !== null && _b !== void 0 ? _b : this.channel.getTarget();
}
start(metadata, listener) {
this.trace('start called');
this.listener = listener;
this.metadata = metadata;
this.doPick();
}
sendMessageWithContext(context, message) {
this.trace('write() called with message of length ' + message.length);
if (this.child) {
this.child.sendMessageWithContext(context, message);
}
else {
this.pendingMessage = { context, message };
}
}
startRead() {
this.trace('startRead called');
if (this.child) {
this.child.startRead();
}
else {
this.readPending = true;
}
}
halfClose() {
this.trace('halfClose called');
if (this.child) {
this.child.halfClose();
}
else {
this.pendingHalfClose = true;
}
}
setCredentials(credentials) {
throw new Error('Method not implemented.');
}
getCallNumber() {
return this.callNumber;
}
getAuthContext() {
if (this.child) {
return this.child.getAuthContext();
}
else {
return null;
}
}
}
exports.LoadBalancingCall = LoadBalancingCall;
//# sourceMappingURL=load-balancing-call.js.map

View File

@@ -0,0 +1,122 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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, _d;
Object.defineProperty(exports, "__esModule", { value: true });
exports.log = exports.setLoggerVerbosity = exports.setLogger = exports.getLogger = void 0;
exports.trace = trace;
exports.isTracerEnabled = isTracerEnabled;
const constants_1 = require("./constants");
const process_1 = require("process");
const clientVersion = require('../../package.json').version;
const DEFAULT_LOGGER = {
error: (message, ...optionalParams) => {
console.error('E ' + message, ...optionalParams);
},
info: (message, ...optionalParams) => {
console.error('I ' + message, ...optionalParams);
},
debug: (message, ...optionalParams) => {
console.error('D ' + message, ...optionalParams);
},
};
let _logger = DEFAULT_LOGGER;
let _logVerbosity = constants_1.LogVerbosity.ERROR;
const verbosityString = (_b = (_a = process.env.GRPC_NODE_VERBOSITY) !== null && _a !== void 0 ? _a : process.env.GRPC_VERBOSITY) !== null && _b !== void 0 ? _b : '';
switch (verbosityString.toUpperCase()) {
case 'DEBUG':
_logVerbosity = constants_1.LogVerbosity.DEBUG;
break;
case 'INFO':
_logVerbosity = constants_1.LogVerbosity.INFO;
break;
case 'ERROR':
_logVerbosity = constants_1.LogVerbosity.ERROR;
break;
case 'NONE':
_logVerbosity = constants_1.LogVerbosity.NONE;
break;
default:
// Ignore any other values
}
const getLogger = () => {
return _logger;
};
exports.getLogger = getLogger;
const setLogger = (logger) => {
_logger = logger;
};
exports.setLogger = setLogger;
const setLoggerVerbosity = (verbosity) => {
_logVerbosity = verbosity;
};
exports.setLoggerVerbosity = setLoggerVerbosity;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const log = (severity, ...args) => {
let logFunction;
if (severity >= _logVerbosity) {
switch (severity) {
case constants_1.LogVerbosity.DEBUG:
logFunction = _logger.debug;
break;
case constants_1.LogVerbosity.INFO:
logFunction = _logger.info;
break;
case constants_1.LogVerbosity.ERROR:
logFunction = _logger.error;
break;
}
/* Fall back to _logger.error when other methods are not available for
* compatiblity with older behavior that always logged to _logger.error */
if (!logFunction) {
logFunction = _logger.error;
}
if (logFunction) {
logFunction.bind(_logger)(...args);
}
}
};
exports.log = log;
const tracersString = (_d = (_c = process.env.GRPC_NODE_TRACE) !== null && _c !== void 0 ? _c : process.env.GRPC_TRACE) !== null && _d !== void 0 ? _d : '';
const enabledTracers = new Set();
const disabledTracers = new Set();
for (const tracerName of tracersString.split(',')) {
if (tracerName.startsWith('-')) {
disabledTracers.add(tracerName.substring(1));
}
else {
enabledTracers.add(tracerName);
}
}
const allEnabled = enabledTracers.has('all');
function trace(severity, tracer, text) {
if (isTracerEnabled(tracer)) {
(0, exports.log)(severity, new Date().toISOString() +
' | v' +
clientVersion +
' ' +
process_1.pid +
' | ' +
tracer +
' | ' +
text);
}
}
function isTracerEnabled(tracer) {
return (!disabledTracers.has(tracer) && (allEnabled || enabledTracers.has(tracer)));
}
//# sourceMappingURL=logging.js.map

View File

@@ -0,0 +1,143 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.makeClientConstructor = makeClientConstructor;
exports.loadPackageDefinition = loadPackageDefinition;
const client_1 = require("./client");
/**
* Map with short names for each of the requester maker functions. Used in
* makeClientConstructor
* @private
*/
const requesterFuncs = {
unary: client_1.Client.prototype.makeUnaryRequest,
server_stream: client_1.Client.prototype.makeServerStreamRequest,
client_stream: client_1.Client.prototype.makeClientStreamRequest,
bidi: client_1.Client.prototype.makeBidiStreamRequest,
};
/**
* Returns true, if given key is included in the blacklisted
* keys.
* @param key key for check, string.
*/
function isPrototypePolluted(key) {
return ['__proto__', 'prototype', 'constructor'].includes(key);
}
/**
* Creates a constructor for a client with the given methods, as specified in
* the methods argument. The resulting class will have an instance method for
* each method in the service, which is a partial application of one of the
* [Client]{@link grpc.Client} request methods, depending on `requestSerialize`
* and `responseSerialize`, with the `method`, `serialize`, and `deserialize`
* arguments predefined.
* @param methods An object mapping method names to
* method attributes
* @param serviceName The fully qualified name of the service
* @param classOptions An options object.
* @return New client constructor, which is a subclass of
* {@link grpc.Client}, and has the same arguments as that constructor.
*/
function makeClientConstructor(methods, serviceName, classOptions) {
if (!classOptions) {
classOptions = {};
}
class ServiceClientImpl extends client_1.Client {
}
Object.keys(methods).forEach(name => {
if (isPrototypePolluted(name)) {
return;
}
const attrs = methods[name];
let methodType;
// TODO(murgatroid99): Verify that we don't need this anymore
if (typeof name === 'string' && name.charAt(0) === '$') {
throw new Error('Method names cannot start with $');
}
if (attrs.requestStream) {
if (attrs.responseStream) {
methodType = 'bidi';
}
else {
methodType = 'client_stream';
}
}
else {
if (attrs.responseStream) {
methodType = 'server_stream';
}
else {
methodType = 'unary';
}
}
const serialize = attrs.requestSerialize;
const deserialize = attrs.responseDeserialize;
const methodFunc = partial(requesterFuncs[methodType], attrs.path, serialize, deserialize);
ServiceClientImpl.prototype[name] = methodFunc;
// Associate all provided attributes with the method
Object.assign(ServiceClientImpl.prototype[name], attrs);
if (attrs.originalName && !isPrototypePolluted(attrs.originalName)) {
ServiceClientImpl.prototype[attrs.originalName] =
ServiceClientImpl.prototype[name];
}
});
ServiceClientImpl.service = methods;
ServiceClientImpl.serviceName = serviceName;
return ServiceClientImpl;
}
function partial(fn, path, serialize, deserialize) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return function (...args) {
return fn.call(this, path, serialize, deserialize, ...args);
};
}
function isProtobufTypeDefinition(obj) {
return 'format' in obj;
}
/**
* Load a gRPC package definition as a gRPC object hierarchy.
* @param packageDef The package definition object.
* @return The resulting gRPC object.
*/
function loadPackageDefinition(packageDef) {
const result = {};
for (const serviceFqn in packageDef) {
if (Object.prototype.hasOwnProperty.call(packageDef, serviceFqn)) {
const service = packageDef[serviceFqn];
const nameComponents = serviceFqn.split('.');
if (nameComponents.some((comp) => isPrototypePolluted(comp))) {
continue;
}
const serviceName = nameComponents[nameComponents.length - 1];
let current = result;
for (const packageName of nameComponents.slice(0, -1)) {
if (!current[packageName]) {
current[packageName] = {};
}
current = current[packageName];
}
if (isProtobufTypeDefinition(service)) {
current[serviceName] = service;
}
else {
current[serviceName] = makeClientConstructor(service, serviceName, {});
}
}
}
return result;
}
//# sourceMappingURL=make-client.js.map

View File

@@ -0,0 +1,272 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.Metadata = void 0;
const logging_1 = require("./logging");
const constants_1 = require("./constants");
const error_1 = require("./error");
const LEGAL_KEY_REGEX = /^[:0-9a-z_.-]+$/;
const LEGAL_NON_BINARY_VALUE_REGEX = /^[ -~]*$/;
function isLegalKey(key) {
return LEGAL_KEY_REGEX.test(key);
}
function isLegalNonBinaryValue(value) {
return LEGAL_NON_BINARY_VALUE_REGEX.test(value);
}
function isBinaryKey(key) {
return key.endsWith('-bin');
}
function isCustomMetadata(key) {
return !key.startsWith('grpc-');
}
function normalizeKey(key) {
return key.toLowerCase();
}
function validate(key, value) {
if (!isLegalKey(key)) {
throw new Error('Metadata key "' + key + '" contains illegal characters');
}
if (value !== null && value !== undefined) {
if (isBinaryKey(key)) {
if (!Buffer.isBuffer(value)) {
throw new Error("keys that end with '-bin' must have Buffer values");
}
}
else {
if (Buffer.isBuffer(value)) {
throw new Error("keys that don't end with '-bin' must have String values");
}
if (!isLegalNonBinaryValue(value)) {
throw new Error('Metadata string value "' + value + '" contains illegal characters');
}
}
}
}
/**
* A class for storing metadata. Keys are normalized to lowercase ASCII.
*/
class Metadata {
constructor(options = {}) {
this.internalRepr = new Map();
this.opaqueData = new Map();
this.options = options;
}
/**
* Sets the given value for the given key by replacing any other values
* associated with that key. Normalizes the key.
* @param key The key to whose value should be set.
* @param value The value to set. Must be a buffer if and only
* if the normalized key ends with '-bin'.
*/
set(key, value) {
key = normalizeKey(key);
validate(key, value);
this.internalRepr.set(key, [value]);
}
/**
* Adds the given value for the given key by appending to a list of previous
* values associated with that key. Normalizes the key.
* @param key The key for which a new value should be appended.
* @param value The value to add. Must be a buffer if and only
* if the normalized key ends with '-bin'.
*/
add(key, value) {
key = normalizeKey(key);
validate(key, value);
const existingValue = this.internalRepr.get(key);
if (existingValue === undefined) {
this.internalRepr.set(key, [value]);
}
else {
existingValue.push(value);
}
}
/**
* Removes the given key and any associated values. Normalizes the key.
* @param key The key whose values should be removed.
*/
remove(key) {
key = normalizeKey(key);
// validate(key);
this.internalRepr.delete(key);
}
/**
* Gets a list of all values associated with the key. Normalizes the key.
* @param key The key whose value should be retrieved.
* @return A list of values associated with the given key.
*/
get(key) {
key = normalizeKey(key);
// validate(key);
return this.internalRepr.get(key) || [];
}
/**
* Gets a plain object mapping each key to the first value associated with it.
* This reflects the most common way that people will want to see metadata.
* @return A key/value mapping of the metadata.
*/
getMap() {
const result = {};
for (const [key, values] of this.internalRepr) {
if (values.length > 0) {
const v = values[0];
result[key] = Buffer.isBuffer(v) ? Buffer.from(v) : v;
}
}
return result;
}
/**
* Clones the metadata object.
* @return The newly cloned object.
*/
clone() {
const newMetadata = new Metadata(this.options);
const newInternalRepr = newMetadata.internalRepr;
for (const [key, value] of this.internalRepr) {
const clonedValue = value.map(v => {
if (Buffer.isBuffer(v)) {
return Buffer.from(v);
}
else {
return v;
}
});
newInternalRepr.set(key, clonedValue);
}
return newMetadata;
}
/**
* Merges all key-value pairs from a given Metadata object into this one.
* If both this object and the given object have values in the same key,
* values from the other Metadata object will be appended to this object's
* values.
* @param other A Metadata object.
*/
merge(other) {
for (const [key, values] of other.internalRepr) {
const mergedValue = (this.internalRepr.get(key) || []).concat(values);
this.internalRepr.set(key, mergedValue);
}
}
setOptions(options) {
this.options = options;
}
getOptions() {
return this.options;
}
/**
* Creates an OutgoingHttpHeaders object that can be used with the http2 API.
*/
toHttp2Headers() {
// NOTE: Node <8.9 formats http2 headers incorrectly.
const result = {};
for (const [key, values] of this.internalRepr) {
if (key.startsWith(':')) {
continue;
}
// We assume that the user's interaction with this object is limited to
// through its public API (i.e. keys and values are already validated).
result[key] = values.map(bufToString);
}
return result;
}
/**
* This modifies the behavior of JSON.stringify to show an object
* representation of the metadata map.
*/
toJSON() {
const result = {};
for (const [key, values] of this.internalRepr) {
result[key] = values;
}
return result;
}
/**
* Attach additional data of any type to the metadata object, which will not
* be included when sending headers. The data can later be retrieved with
* `getOpaque`. Keys with the prefix `grpc` are reserved for use by this
* library.
* @param key
* @param value
*/
setOpaque(key, value) {
this.opaqueData.set(key, value);
}
/**
* Retrieve data previously added with `setOpaque`.
* @param key
* @returns
*/
getOpaque(key) {
return this.opaqueData.get(key);
}
/**
* Returns a new Metadata object based fields in a given IncomingHttpHeaders
* object.
* @param headers An IncomingHttpHeaders object.
*/
static fromHttp2Headers(headers) {
const result = new Metadata();
for (const key of Object.keys(headers)) {
// Reserved headers (beginning with `:`) are not valid keys.
if (key.charAt(0) === ':') {
continue;
}
const values = headers[key];
try {
if (isBinaryKey(key)) {
if (Array.isArray(values)) {
values.forEach(value => {
result.add(key, Buffer.from(value, 'base64'));
});
}
else if (values !== undefined) {
if (isCustomMetadata(key)) {
values.split(',').forEach(v => {
result.add(key, Buffer.from(v.trim(), 'base64'));
});
}
else {
result.add(key, Buffer.from(values, 'base64'));
}
}
}
else {
if (Array.isArray(values)) {
values.forEach(value => {
result.add(key, value);
});
}
else if (values !== undefined) {
result.add(key, values);
}
}
}
catch (error) {
const message = `Failed to add metadata entry ${key}: ${values}. ${(0, error_1.getErrorMessage)(error)}. For more information see https://github.com/grpc/grpc-node/issues/1173`;
(0, logging_1.log)(constants_1.LogVerbosity.ERROR, message);
}
}
return result;
}
}
exports.Metadata = Metadata;
const bufToString = (val) => {
return Buffer.isBuffer(val) ? val.toString('base64') : val;
};
//# sourceMappingURL=metadata.js.map

View File

@@ -0,0 +1,323 @@
"use strict";
/*
* Copyright 2025 gRPC authors.
*
* Licensed 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.OrcaOobMetricsSubchannelWrapper = exports.GRPC_METRICS_HEADER = exports.ServerMetricRecorder = exports.PerRequestMetricRecorder = void 0;
exports.createOrcaClient = createOrcaClient;
exports.createMetricsReader = createMetricsReader;
const make_client_1 = require("./make-client");
const duration_1 = require("./duration");
const channel_credentials_1 = require("./channel-credentials");
const subchannel_interface_1 = require("./subchannel-interface");
const constants_1 = require("./constants");
const backoff_timeout_1 = require("./backoff-timeout");
const connectivity_state_1 = require("./connectivity-state");
const loadedOrcaProto = null;
function loadOrcaProto() {
if (loadedOrcaProto) {
return loadedOrcaProto;
}
/* The purpose of this complexity is to avoid loading @grpc/proto-loader at
* runtime for users who will not use/enable ORCA. */
const loaderLoadSync = require('@grpc/proto-loader')
.loadSync;
const loadedProto = loaderLoadSync('xds/service/orca/v3/orca.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: [
`${__dirname}/../../proto/xds`,
`${__dirname}/../../proto/protoc-gen-validate`
],
});
return (0, make_client_1.loadPackageDefinition)(loadedProto);
}
/**
* ORCA metrics recorder for a single request
*/
class PerRequestMetricRecorder {
constructor() {
this.message = {};
}
/**
* Records a request cost metric measurement for the call.
* @param name
* @param value
*/
recordRequestCostMetric(name, value) {
if (!this.message.request_cost) {
this.message.request_cost = {};
}
this.message.request_cost[name] = value;
}
/**
* Records a request cost metric measurement for the call.
* @param name
* @param value
*/
recordUtilizationMetric(name, value) {
if (!this.message.utilization) {
this.message.utilization = {};
}
this.message.utilization[name] = value;
}
/**
* Records an opaque named metric measurement for the call.
* @param name
* @param value
*/
recordNamedMetric(name, value) {
if (!this.message.named_metrics) {
this.message.named_metrics = {};
}
this.message.named_metrics[name] = value;
}
/**
* Records the CPU utilization metric measurement for the call.
* @param value
*/
recordCPUUtilizationMetric(value) {
this.message.cpu_utilization = value;
}
/**
* Records the memory utilization metric measurement for the call.
* @param value
*/
recordMemoryUtilizationMetric(value) {
this.message.mem_utilization = value;
}
/**
* Records the memory utilization metric measurement for the call.
* @param value
*/
recordApplicationUtilizationMetric(value) {
this.message.application_utilization = value;
}
/**
* Records the queries per second measurement.
* @param value
*/
recordQpsMetric(value) {
this.message.rps_fractional = value;
}
/**
* Records the errors per second measurement.
* @param value
*/
recordEpsMetric(value) {
this.message.eps = value;
}
serialize() {
const orcaProto = loadOrcaProto();
return orcaProto.xds.data.orca.v3.OrcaLoadReport.serialize(this.message);
}
}
exports.PerRequestMetricRecorder = PerRequestMetricRecorder;
const DEFAULT_REPORT_INTERVAL_MS = 30000;
class ServerMetricRecorder {
constructor() {
this.message = {};
this.serviceImplementation = {
StreamCoreMetrics: call => {
const reportInterval = call.request.report_interval ?
(0, duration_1.durationToMs)((0, duration_1.durationMessageToDuration)(call.request.report_interval)) :
DEFAULT_REPORT_INTERVAL_MS;
const reportTimer = setInterval(() => {
call.write(this.message);
}, reportInterval);
call.on('cancelled', () => {
clearInterval(reportTimer);
});
}
};
}
putUtilizationMetric(name, value) {
if (!this.message.utilization) {
this.message.utilization = {};
}
this.message.utilization[name] = value;
}
setAllUtilizationMetrics(metrics) {
this.message.utilization = Object.assign({}, metrics);
}
deleteUtilizationMetric(name) {
var _a;
(_a = this.message.utilization) === null || _a === void 0 ? true : delete _a[name];
}
setCpuUtilizationMetric(value) {
this.message.cpu_utilization = value;
}
deleteCpuUtilizationMetric() {
delete this.message.cpu_utilization;
}
setApplicationUtilizationMetric(value) {
this.message.application_utilization = value;
}
deleteApplicationUtilizationMetric() {
delete this.message.application_utilization;
}
setQpsMetric(value) {
this.message.rps_fractional = value;
}
deleteQpsMetric() {
delete this.message.rps_fractional;
}
setEpsMetric(value) {
this.message.eps = value;
}
deleteEpsMetric() {
delete this.message.eps;
}
addToServer(server) {
const serviceDefinition = loadOrcaProto().xds.service.orca.v3.OpenRcaService.service;
server.addService(serviceDefinition, this.serviceImplementation);
}
}
exports.ServerMetricRecorder = ServerMetricRecorder;
function createOrcaClient(channel) {
const ClientClass = loadOrcaProto().xds.service.orca.v3.OpenRcaService;
return new ClientClass('unused', channel_credentials_1.ChannelCredentials.createInsecure(), { channelOverride: channel });
}
exports.GRPC_METRICS_HEADER = 'endpoint-load-metrics-bin';
const PARSED_LOAD_REPORT_KEY = 'grpc_orca_load_report';
/**
* Create an onCallEnded callback for use in a picker.
* @param listener The listener to handle metrics, whenever they are provided.
* @param previousOnCallEnded The previous onCallEnded callback to propagate
* to, if applicable.
* @returns
*/
function createMetricsReader(listener, previousOnCallEnded) {
return (code, details, metadata) => {
let parsedLoadReport = metadata.getOpaque(PARSED_LOAD_REPORT_KEY);
if (parsedLoadReport) {
listener(parsedLoadReport);
}
else {
const serializedLoadReport = metadata.get(exports.GRPC_METRICS_HEADER);
if (serializedLoadReport.length > 0) {
const orcaProto = loadOrcaProto();
parsedLoadReport = orcaProto.xds.data.orca.v3.OrcaLoadReport.deserialize(serializedLoadReport[0]);
listener(parsedLoadReport);
metadata.setOpaque(PARSED_LOAD_REPORT_KEY, parsedLoadReport);
}
}
if (previousOnCallEnded) {
previousOnCallEnded(code, details, metadata);
}
};
}
const DATA_PRODUCER_KEY = 'orca_oob_metrics';
class OobMetricsDataWatcher {
constructor(metricsListener, intervalMs) {
this.metricsListener = metricsListener;
this.intervalMs = intervalMs;
this.dataProducer = null;
}
setSubchannel(subchannel) {
const producer = subchannel.getOrCreateDataProducer(DATA_PRODUCER_KEY, createOobMetricsDataProducer);
this.dataProducer = producer;
producer.addDataWatcher(this);
}
destroy() {
var _a;
(_a = this.dataProducer) === null || _a === void 0 ? void 0 : _a.removeDataWatcher(this);
}
getInterval() {
return this.intervalMs;
}
onMetricsUpdate(metrics) {
this.metricsListener(metrics);
}
}
class OobMetricsDataProducer {
constructor(subchannel) {
this.subchannel = subchannel;
this.dataWatchers = new Set();
this.orcaSupported = true;
this.metricsCall = null;
this.currentInterval = Infinity;
this.backoffTimer = new backoff_timeout_1.BackoffTimeout(() => this.updateMetricsSubscription());
this.subchannelStateListener = () => this.updateMetricsSubscription();
const channel = subchannel.getChannel();
this.client = createOrcaClient(channel);
subchannel.addConnectivityStateListener(this.subchannelStateListener);
}
addDataWatcher(dataWatcher) {
this.dataWatchers.add(dataWatcher);
this.updateMetricsSubscription();
}
removeDataWatcher(dataWatcher) {
var _a;
this.dataWatchers.delete(dataWatcher);
if (this.dataWatchers.size === 0) {
this.subchannel.removeDataProducer(DATA_PRODUCER_KEY);
(_a = this.metricsCall) === null || _a === void 0 ? void 0 : _a.cancel();
this.metricsCall = null;
this.client.close();
this.subchannel.removeConnectivityStateListener(this.subchannelStateListener);
}
else {
this.updateMetricsSubscription();
}
}
updateMetricsSubscription() {
var _a;
if (this.dataWatchers.size === 0 || !this.orcaSupported || this.subchannel.getConnectivityState() !== connectivity_state_1.ConnectivityState.READY) {
return;
}
const newInterval = Math.min(...Array.from(this.dataWatchers).map(watcher => watcher.getInterval()));
if (!this.metricsCall || newInterval !== this.currentInterval) {
(_a = this.metricsCall) === null || _a === void 0 ? void 0 : _a.cancel();
this.currentInterval = newInterval;
const metricsCall = this.client.streamCoreMetrics({ report_interval: (0, duration_1.msToDuration)(newInterval) });
this.metricsCall = metricsCall;
metricsCall.on('data', (report) => {
this.dataWatchers.forEach(watcher => {
watcher.onMetricsUpdate(report);
});
});
metricsCall.on('error', (error) => {
this.metricsCall = null;
if (error.code === constants_1.Status.UNIMPLEMENTED) {
this.orcaSupported = false;
return;
}
if (error.code === constants_1.Status.CANCELLED) {
return;
}
this.backoffTimer.runOnce();
});
}
}
}
class OrcaOobMetricsSubchannelWrapper extends subchannel_interface_1.BaseSubchannelWrapper {
constructor(child, metricsListener, intervalMs) {
super(child);
this.addDataWatcher(new OobMetricsDataWatcher(metricsListener, intervalMs));
}
getWrappedSubchannel() {
return this.child;
}
}
exports.OrcaOobMetricsSubchannelWrapper = OrcaOobMetricsSubchannelWrapper;
function createOobMetricsDataProducer(subchannel) {
return new OobMetricsDataProducer(subchannel);
}
//# sourceMappingURL=orca.js.map

View File

@@ -0,0 +1,86 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.QueuePicker = exports.UnavailablePicker = exports.PickResultType = void 0;
const metadata_1 = require("./metadata");
const constants_1 = require("./constants");
var PickResultType;
(function (PickResultType) {
PickResultType[PickResultType["COMPLETE"] = 0] = "COMPLETE";
PickResultType[PickResultType["QUEUE"] = 1] = "QUEUE";
PickResultType[PickResultType["TRANSIENT_FAILURE"] = 2] = "TRANSIENT_FAILURE";
PickResultType[PickResultType["DROP"] = 3] = "DROP";
})(PickResultType || (exports.PickResultType = PickResultType = {}));
/**
* A standard picker representing a load balancer in the TRANSIENT_FAILURE
* state. Always responds to every pick request with an UNAVAILABLE status.
*/
class UnavailablePicker {
constructor(status) {
this.status = Object.assign({ code: constants_1.Status.UNAVAILABLE, details: 'No connection established', metadata: new metadata_1.Metadata() }, status);
}
pick(pickArgs) {
return {
pickResultType: PickResultType.TRANSIENT_FAILURE,
subchannel: null,
status: this.status,
onCallStarted: null,
onCallEnded: null,
};
}
}
exports.UnavailablePicker = UnavailablePicker;
/**
* A standard picker representing a load balancer in the IDLE or CONNECTING
* state. Always responds to every pick request with a QUEUE pick result
* indicating that the pick should be tried again with the next `Picker`. Also
* reports back to the load balancer that a connection should be established
* once any pick is attempted.
* If the childPicker is provided, delegate to it instead of returning the
* hardcoded QUEUE pick result, but still calls exitIdle.
*/
class QueuePicker {
// Constructed with a load balancer. Calls exitIdle on it the first time pick is called
constructor(loadBalancer, childPicker) {
this.loadBalancer = loadBalancer;
this.childPicker = childPicker;
this.calledExitIdle = false;
}
pick(pickArgs) {
if (!this.calledExitIdle) {
process.nextTick(() => {
this.loadBalancer.exitIdle();
});
this.calledExitIdle = true;
}
if (this.childPicker) {
return this.childPicker.pick(pickArgs);
}
else {
return {
pickResultType: PickResultType.QUEUE,
subchannel: null,
status: null,
onCallStarted: null,
onCallEnded: null,
};
}
}
}
exports.QueuePicker = QueuePicker;
//# sourceMappingURL=picker.js.map

View File

@@ -0,0 +1,120 @@
"use strict";
/*
* Copyright 2025 gRPC authors.
*
* Licensed 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.PriorityQueue = void 0;
const top = 0;
const parent = (i) => Math.floor(i / 2);
const left = (i) => i * 2 + 1;
const right = (i) => i * 2 + 2;
/**
* A generic priority queue implemented as an array-based binary heap.
* Adapted from https://stackoverflow.com/a/42919752/159388
*/
class PriorityQueue {
/**
*
* @param comparator Returns true if the first argument should precede the
* second in the queue. Defaults to `(a, b) => a > b`
*/
constructor(comparator = (a, b) => a > b) {
this.comparator = comparator;
this.heap = [];
}
/**
* @returns The number of items currently in the queue
*/
size() {
return this.heap.length;
}
/**
* @returns True if there are no items in the queue, false otherwise
*/
isEmpty() {
return this.size() == 0;
}
/**
* Look at the front item that would be popped, without modifying the contents
* of the queue
* @returns The front item in the queue, or undefined if the queue is empty
*/
peek() {
return this.heap[top];
}
/**
* Add the items to the queue
* @param values The items to add
* @returns The new size of the queue after adding the items
*/
push(...values) {
values.forEach(value => {
this.heap.push(value);
this.siftUp();
});
return this.size();
}
/**
* Remove the front item in the queue and return it
* @returns The front item in the queue, or undefined if the queue is empty
*/
pop() {
const poppedValue = this.peek();
const bottom = this.size() - 1;
if (bottom > top) {
this.swap(top, bottom);
}
this.heap.pop();
this.siftDown();
return poppedValue;
}
/**
* Simultaneously remove the front item in the queue and add the provided
* item.
* @param value The item to add
* @returns The front item in the queue, or undefined if the queue is empty
*/
replace(value) {
const replacedValue = this.peek();
this.heap[top] = value;
this.siftDown();
return replacedValue;
}
greater(i, j) {
return this.comparator(this.heap[i], this.heap[j]);
}
swap(i, j) {
[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
}
siftUp() {
let node = this.size() - 1;
while (node > top && this.greater(node, parent(node))) {
this.swap(node, parent(node));
node = parent(node);
}
}
siftDown() {
let node = top;
while ((left(node) < this.size() && this.greater(left(node), node)) ||
(right(node) < this.size() && this.greater(right(node), node))) {
let maxChild = (right(node) < this.size() && this.greater(right(node), left(node))) ? right(node) : left(node);
this.swap(node, maxChild);
node = maxChild;
}
}
}
exports.PriorityQueue = PriorityQueue;
//# sourceMappingURL=priority-queue.js.map

View File

@@ -0,0 +1,363 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.DEFAULT_PORT = void 0;
exports.setup = setup;
const resolver_1 = require("./resolver");
const dns_1 = require("dns");
const service_config_1 = require("./service-config");
const constants_1 = require("./constants");
const call_interface_1 = require("./call-interface");
const metadata_1 = require("./metadata");
const logging = require("./logging");
const constants_2 = require("./constants");
const uri_parser_1 = require("./uri-parser");
const net_1 = require("net");
const backoff_timeout_1 = require("./backoff-timeout");
const environment_1 = require("./environment");
const TRACER_NAME = 'dns_resolver';
function trace(text) {
logging.trace(constants_2.LogVerbosity.DEBUG, TRACER_NAME, text);
}
/**
* The default TCP port to connect to if not explicitly specified in the target.
*/
exports.DEFAULT_PORT = 443;
const DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS = 30000;
/**
* Resolver implementation that handles DNS names and IP addresses.
*/
class DnsResolver {
constructor(target, listener, channelOptions) {
var _a, _b, _c;
this.target = target;
this.listener = listener;
this.pendingLookupPromise = null;
this.pendingTxtPromise = null;
this.latestLookupResult = null;
this.latestServiceConfigResult = null;
this.continueResolving = false;
this.isNextResolutionTimerRunning = false;
this.isServiceConfigEnabled = true;
this.returnedIpResult = false;
this.alternativeResolver = new dns_1.promises.Resolver();
trace('Resolver constructed for target ' + (0, uri_parser_1.uriToString)(target));
if (target.authority) {
this.alternativeResolver.setServers([target.authority]);
}
const hostPort = (0, uri_parser_1.splitHostPort)(target.path);
if (hostPort === null) {
this.ipResult = null;
this.dnsHostname = null;
this.port = null;
}
else {
if ((0, net_1.isIPv4)(hostPort.host) || (0, net_1.isIPv6)(hostPort.host)) {
this.ipResult = [
{
addresses: [
{
host: hostPort.host,
port: (_a = hostPort.port) !== null && _a !== void 0 ? _a : exports.DEFAULT_PORT,
},
],
},
];
this.dnsHostname = null;
this.port = null;
}
else {
this.ipResult = null;
this.dnsHostname = hostPort.host;
this.port = (_b = hostPort.port) !== null && _b !== void 0 ? _b : exports.DEFAULT_PORT;
}
}
this.percentage = Math.random() * 100;
if (channelOptions['grpc.service_config_disable_resolution'] === 1) {
this.isServiceConfigEnabled = false;
}
this.defaultResolutionError = {
code: constants_1.Status.UNAVAILABLE,
details: `Name resolution failed for target ${(0, uri_parser_1.uriToString)(this.target)}`,
metadata: new metadata_1.Metadata(),
};
const backoffOptions = {
initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'],
maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'],
};
this.backoff = new backoff_timeout_1.BackoffTimeout(() => {
if (this.continueResolving) {
this.startResolutionWithBackoff();
}
}, backoffOptions);
this.backoff.unref();
this.minTimeBetweenResolutionsMs =
(_c = channelOptions['grpc.dns_min_time_between_resolutions_ms']) !== null && _c !== void 0 ? _c : DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS;
this.nextResolutionTimer = setTimeout(() => { }, 0);
clearTimeout(this.nextResolutionTimer);
}
/**
* If the target is an IP address, just provide that address as a result.
* Otherwise, initiate A, AAAA, and TXT lookups
*/
startResolution() {
if (this.ipResult !== null) {
if (!this.returnedIpResult) {
trace('Returning IP address for target ' + (0, uri_parser_1.uriToString)(this.target));
setImmediate(() => {
this.listener((0, call_interface_1.statusOrFromValue)(this.ipResult), {}, null, '');
});
this.returnedIpResult = true;
}
this.backoff.stop();
this.backoff.reset();
this.stopNextResolutionTimer();
return;
}
if (this.dnsHostname === null) {
trace('Failed to parse DNS address ' + (0, uri_parser_1.uriToString)(this.target));
setImmediate(() => {
this.listener((0, call_interface_1.statusOrFromError)({
code: constants_1.Status.UNAVAILABLE,
details: `Failed to parse DNS address ${(0, uri_parser_1.uriToString)(this.target)}`
}), {}, null, '');
});
this.stopNextResolutionTimer();
}
else {
if (this.pendingLookupPromise !== null) {
return;
}
trace('Looking up DNS hostname ' + this.dnsHostname);
/* We clear out latestLookupResult here to ensure that it contains the
* latest result since the last time we started resolving. That way, the
* TXT resolution handler can use it, but only if it finishes second. We
* don't clear out any previous service config results because it's
* better to use a service config that's slightly out of date than to
* revert to an effectively blank one. */
this.latestLookupResult = null;
const hostname = this.dnsHostname;
this.pendingLookupPromise = this.lookup(hostname);
this.pendingLookupPromise.then(addressList => {
if (this.pendingLookupPromise === null) {
return;
}
this.pendingLookupPromise = null;
this.latestLookupResult = (0, call_interface_1.statusOrFromValue)(addressList.map(address => ({
addresses: [address],
})));
const allAddressesString = '[' +
addressList.map(addr => addr.host + ':' + addr.port).join(',') +
']';
trace('Resolved addresses for target ' +
(0, uri_parser_1.uriToString)(this.target) +
': ' +
allAddressesString);
/* If the TXT lookup has not yet finished, both of the last two
* arguments will be null, which is the equivalent of getting an
* empty TXT response. When the TXT lookup does finish, its handler
* can update the service config by using the same address list */
const healthStatus = this.listener(this.latestLookupResult, {}, this.latestServiceConfigResult, '');
this.handleHealthStatus(healthStatus);
}, err => {
if (this.pendingLookupPromise === null) {
return;
}
trace('Resolution error for target ' +
(0, uri_parser_1.uriToString)(this.target) +
': ' +
err.message);
this.pendingLookupPromise = null;
this.stopNextResolutionTimer();
this.listener((0, call_interface_1.statusOrFromError)(this.defaultResolutionError), {}, this.latestServiceConfigResult, '');
});
/* If there already is a still-pending TXT resolution, we can just use
* that result when it comes in */
if (this.isServiceConfigEnabled && this.pendingTxtPromise === null) {
/* We handle the TXT query promise differently than the others because
* the name resolution attempt as a whole is a success even if the TXT
* lookup fails */
this.pendingTxtPromise = this.resolveTxt(hostname);
this.pendingTxtPromise.then(txtRecord => {
if (this.pendingTxtPromise === null) {
return;
}
this.pendingTxtPromise = null;
let serviceConfig;
try {
serviceConfig = (0, service_config_1.extractAndSelectServiceConfig)(txtRecord, this.percentage);
if (serviceConfig) {
this.latestServiceConfigResult = (0, call_interface_1.statusOrFromValue)(serviceConfig);
}
else {
this.latestServiceConfigResult = null;
}
}
catch (err) {
this.latestServiceConfigResult = (0, call_interface_1.statusOrFromError)({
code: constants_1.Status.UNAVAILABLE,
details: `Parsing service config failed with error ${err.message}`
});
}
if (this.latestLookupResult !== null) {
/* We rely here on the assumption that calling this function with
* identical parameters will be essentialy idempotent, and calling
* it with the same address list and a different service config
* should result in a fast and seamless switchover. */
this.listener(this.latestLookupResult, {}, this.latestServiceConfigResult, '');
}
}, err => {
/* If TXT lookup fails we should do nothing, which means that we
* continue to use the result of the most recent successful lookup,
* or the default null config object if there has never been a
* successful lookup. We do not set the latestServiceConfigError
* here because that is specifically used for response validation
* errors. We still need to handle this error so that it does not
* bubble up as an unhandled promise rejection. */
});
}
}
}
/**
* The ResolverListener returns a boolean indicating whether the LB policy
* accepted the resolution result. A false result on an otherwise successful
* resolution should be treated as a resolution failure.
* @param healthStatus
*/
handleHealthStatus(healthStatus) {
if (healthStatus) {
this.backoff.stop();
this.backoff.reset();
}
else {
this.continueResolving = true;
}
}
async lookup(hostname) {
if (environment_1.GRPC_NODE_USE_ALTERNATIVE_RESOLVER) {
trace('Using alternative DNS resolver.');
const records = await Promise.allSettled([
this.alternativeResolver.resolve4(hostname),
this.alternativeResolver.resolve6(hostname),
]);
if (records.every(result => result.status === 'rejected')) {
throw new Error(records[0].reason);
}
return records
.reduce((acc, result) => {
return result.status === 'fulfilled'
? [...acc, ...result.value]
: acc;
}, [])
.map(addr => ({
host: addr,
port: +this.port,
}));
}
/* We lookup both address families here and then split them up later
* because when looking up a single family, dns.lookup outputs an error
* if the name exists but there are no records for that family, and that
* error is indistinguishable from other kinds of errors */
const addressList = await dns_1.promises.lookup(hostname, { all: true });
return addressList.map(addr => ({ host: addr.address, port: +this.port }));
}
async resolveTxt(hostname) {
if (environment_1.GRPC_NODE_USE_ALTERNATIVE_RESOLVER) {
trace('Using alternative DNS resolver.');
return this.alternativeResolver.resolveTxt(hostname);
}
return dns_1.promises.resolveTxt(hostname);
}
startNextResolutionTimer() {
var _a, _b;
clearTimeout(this.nextResolutionTimer);
this.nextResolutionTimer = setTimeout(() => {
this.stopNextResolutionTimer();
if (this.continueResolving) {
this.startResolutionWithBackoff();
}
}, this.minTimeBetweenResolutionsMs);
(_b = (_a = this.nextResolutionTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
this.isNextResolutionTimerRunning = true;
}
stopNextResolutionTimer() {
clearTimeout(this.nextResolutionTimer);
this.isNextResolutionTimerRunning = false;
}
startResolutionWithBackoff() {
if (this.pendingLookupPromise === null) {
this.continueResolving = false;
this.backoff.runOnce();
this.startNextResolutionTimer();
this.startResolution();
}
}
updateResolution() {
/* If there is a pending lookup, just let it finish. Otherwise, if the
* nextResolutionTimer or backoff timer is running, set the
* continueResolving flag to resolve when whichever of those timers
* fires. Otherwise, start resolving immediately. */
if (this.pendingLookupPromise === null) {
if (this.isNextResolutionTimerRunning || this.backoff.isRunning()) {
if (this.isNextResolutionTimerRunning) {
trace('resolution update delayed by "min time between resolutions" rate limit');
}
else {
trace('resolution update delayed by backoff timer until ' +
this.backoff.getEndTime().toISOString());
}
this.continueResolving = true;
}
else {
this.startResolutionWithBackoff();
}
}
}
/**
* Reset the resolver to the same state it had when it was created. In-flight
* DNS requests cannot be cancelled, but they are discarded and their results
* will be ignored.
*/
destroy() {
this.continueResolving = false;
this.backoff.reset();
this.backoff.stop();
this.stopNextResolutionTimer();
this.pendingLookupPromise = null;
this.pendingTxtPromise = null;
this.latestLookupResult = null;
this.latestServiceConfigResult = null;
this.returnedIpResult = false;
}
/**
* Get the default authority for the given target. For IP targets, that is
* the IP address. For DNS targets, it is the hostname.
* @param target
*/
static getDefaultAuthority(target) {
return target.path;
}
}
/**
* Set up the DNS resolver class by registering it as the handler for the
* "dns:" prefix and as the default resolver.
*/
function setup() {
(0, resolver_1.registerResolver)('dns', DnsResolver);
(0, resolver_1.registerDefaultScheme)('dns');
}
//# sourceMappingURL=resolver-dns.js.map

View File

@@ -0,0 +1,106 @@
"use strict";
/*
* Copyright 2021 gRPC authors.
*
* Licensed 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.setup = setup;
const net_1 = require("net");
const call_interface_1 = require("./call-interface");
const constants_1 = require("./constants");
const metadata_1 = require("./metadata");
const resolver_1 = require("./resolver");
const subchannel_address_1 = require("./subchannel-address");
const uri_parser_1 = require("./uri-parser");
const logging = require("./logging");
const TRACER_NAME = 'ip_resolver';
function trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, text);
}
const IPV4_SCHEME = 'ipv4';
const IPV6_SCHEME = 'ipv6';
/**
* The default TCP port to connect to if not explicitly specified in the target.
*/
const DEFAULT_PORT = 443;
class IpResolver {
constructor(target, listener, channelOptions) {
var _a;
this.listener = listener;
this.endpoints = [];
this.error = null;
this.hasReturnedResult = false;
trace('Resolver constructed for target ' + (0, uri_parser_1.uriToString)(target));
const addresses = [];
if (!(target.scheme === IPV4_SCHEME || target.scheme === IPV6_SCHEME)) {
this.error = {
code: constants_1.Status.UNAVAILABLE,
details: `Unrecognized scheme ${target.scheme} in IP resolver`,
metadata: new metadata_1.Metadata(),
};
return;
}
const pathList = target.path.split(',');
for (const path of pathList) {
const hostPort = (0, uri_parser_1.splitHostPort)(path);
if (hostPort === null) {
this.error = {
code: constants_1.Status.UNAVAILABLE,
details: `Failed to parse ${target.scheme} address ${path}`,
metadata: new metadata_1.Metadata(),
};
return;
}
if ((target.scheme === IPV4_SCHEME && !(0, net_1.isIPv4)(hostPort.host)) ||
(target.scheme === IPV6_SCHEME && !(0, net_1.isIPv6)(hostPort.host))) {
this.error = {
code: constants_1.Status.UNAVAILABLE,
details: `Failed to parse ${target.scheme} address ${path}`,
metadata: new metadata_1.Metadata(),
};
return;
}
addresses.push({
host: hostPort.host,
port: (_a = hostPort.port) !== null && _a !== void 0 ? _a : DEFAULT_PORT,
});
}
this.endpoints = addresses.map(address => ({ addresses: [address] }));
trace('Parsed ' + target.scheme + ' address list ' + addresses.map(subchannel_address_1.subchannelAddressToString));
}
updateResolution() {
if (!this.hasReturnedResult) {
this.hasReturnedResult = true;
process.nextTick(() => {
if (this.error) {
this.listener((0, call_interface_1.statusOrFromError)(this.error), {}, null, '');
}
else {
this.listener((0, call_interface_1.statusOrFromValue)(this.endpoints), {}, null, '');
}
});
}
}
destroy() {
this.hasReturnedResult = false;
}
static getDefaultAuthority(target) {
return target.path.split(',')[0];
}
}
function setup() {
(0, resolver_1.registerResolver)(IPV4_SCHEME, IpResolver);
(0, resolver_1.registerResolver)(IPV6_SCHEME, IpResolver);
}
//# sourceMappingURL=resolver-ip.js.map

View File

@@ -0,0 +1,51 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.setup = setup;
const resolver_1 = require("./resolver");
const call_interface_1 = require("./call-interface");
class UdsResolver {
constructor(target, listener, channelOptions) {
this.listener = listener;
this.hasReturnedResult = false;
this.endpoints = [];
let path;
if (target.authority === '') {
path = '/' + target.path;
}
else {
path = target.path;
}
this.endpoints = [{ addresses: [{ path }] }];
}
updateResolution() {
if (!this.hasReturnedResult) {
this.hasReturnedResult = true;
process.nextTick(this.listener, (0, call_interface_1.statusOrFromValue)(this.endpoints), {}, null, '');
}
}
destroy() {
this.hasReturnedResult = false;
}
static getDefaultAuthority(target) {
return 'localhost';
}
}
function setup() {
(0, resolver_1.registerResolver)('unix', UdsResolver);
}
//# sourceMappingURL=resolver-uds.js.map

View File

@@ -0,0 +1,89 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.CHANNEL_ARGS_CONFIG_SELECTOR_KEY = void 0;
exports.registerResolver = registerResolver;
exports.registerDefaultScheme = registerDefaultScheme;
exports.createResolver = createResolver;
exports.getDefaultAuthority = getDefaultAuthority;
exports.mapUriDefaultScheme = mapUriDefaultScheme;
const uri_parser_1 = require("./uri-parser");
exports.CHANNEL_ARGS_CONFIG_SELECTOR_KEY = 'grpc.internal.config_selector';
const registeredResolvers = {};
let defaultScheme = null;
/**
* Register a resolver class to handle target names prefixed with the `prefix`
* string. This prefix should correspond to a URI scheme name listed in the
* [gRPC Name Resolution document](https://github.com/grpc/grpc/blob/master/doc/naming.md)
* @param prefix
* @param resolverClass
*/
function registerResolver(scheme, resolverClass) {
registeredResolvers[scheme] = resolverClass;
}
/**
* Register a default resolver to handle target names that do not start with
* any registered prefix.
* @param resolverClass
*/
function registerDefaultScheme(scheme) {
defaultScheme = scheme;
}
/**
* Create a name resolver for the specified target, if possible. Throws an
* error if no such name resolver can be created.
* @param target
* @param listener
*/
function createResolver(target, listener, options) {
if (target.scheme !== undefined && target.scheme in registeredResolvers) {
return new registeredResolvers[target.scheme](target, listener, options);
}
else {
throw new Error(`No resolver could be created for target ${(0, uri_parser_1.uriToString)(target)}`);
}
}
/**
* Get the default authority for the specified target, if possible. Throws an
* error if no registered name resolver can parse that target string.
* @param target
*/
function getDefaultAuthority(target) {
if (target.scheme !== undefined && target.scheme in registeredResolvers) {
return registeredResolvers[target.scheme].getDefaultAuthority(target);
}
else {
throw new Error(`Invalid target ${(0, uri_parser_1.uriToString)(target)}`);
}
}
function mapUriDefaultScheme(target) {
if (target.scheme === undefined || !(target.scheme in registeredResolvers)) {
if (defaultScheme !== null) {
return {
scheme: defaultScheme,
authority: undefined,
path: (0, uri_parser_1.uriToString)(target),
};
}
else {
return null;
}
}
return target;
}
//# sourceMappingURL=resolver.js.map

View File

@@ -0,0 +1,319 @@
"use strict";
/*
* Copyright 2022 gRPC authors.
*
* Licensed 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.ResolvingCall = void 0;
const call_credentials_1 = require("./call-credentials");
const constants_1 = require("./constants");
const deadline_1 = require("./deadline");
const metadata_1 = require("./metadata");
const logging = require("./logging");
const control_plane_status_1 = require("./control-plane-status");
const TRACER_NAME = 'resolving_call';
class ResolvingCall {
constructor(channel, method, options, filterStackFactory, callNumber) {
this.channel = channel;
this.method = method;
this.filterStackFactory = filterStackFactory;
this.callNumber = callNumber;
this.child = null;
this.readPending = false;
this.pendingMessage = null;
this.pendingHalfClose = false;
this.ended = false;
this.readFilterPending = false;
this.writeFilterPending = false;
this.pendingChildStatus = null;
this.metadata = null;
this.listener = null;
this.statusWatchers = [];
this.deadlineTimer = setTimeout(() => { }, 0);
this.filterStack = null;
this.deadlineStartTime = null;
this.configReceivedTime = null;
this.childStartTime = null;
/**
* Credentials configured for this specific call. Does not include
* call credentials associated with the channel credentials used to create
* the channel.
*/
this.credentials = call_credentials_1.CallCredentials.createEmpty();
this.deadline = options.deadline;
this.host = options.host;
if (options.parentCall) {
if (options.flags & constants_1.Propagate.CANCELLATION) {
options.parentCall.on('cancelled', () => {
this.cancelWithStatus(constants_1.Status.CANCELLED, 'Cancelled by parent call');
});
}
if (options.flags & constants_1.Propagate.DEADLINE) {
this.trace('Propagating deadline from parent: ' +
options.parentCall.getDeadline());
this.deadline = (0, deadline_1.minDeadline)(this.deadline, options.parentCall.getDeadline());
}
}
this.trace('Created');
this.runDeadlineTimer();
}
trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, '[' + this.callNumber + '] ' + text);
}
runDeadlineTimer() {
clearTimeout(this.deadlineTimer);
this.deadlineStartTime = new Date();
this.trace('Deadline: ' + (0, deadline_1.deadlineToString)(this.deadline));
const timeout = (0, deadline_1.getRelativeTimeout)(this.deadline);
if (timeout !== Infinity) {
this.trace('Deadline will be reached in ' + timeout + 'ms');
const handleDeadline = () => {
if (!this.deadlineStartTime) {
this.cancelWithStatus(constants_1.Status.DEADLINE_EXCEEDED, 'Deadline exceeded');
return;
}
const deadlineInfo = [];
const deadlineEndTime = new Date();
deadlineInfo.push(`Deadline exceeded after ${(0, deadline_1.formatDateDifference)(this.deadlineStartTime, deadlineEndTime)}`);
if (this.configReceivedTime) {
if (this.configReceivedTime > this.deadlineStartTime) {
deadlineInfo.push(`name resolution: ${(0, deadline_1.formatDateDifference)(this.deadlineStartTime, this.configReceivedTime)}`);
}
if (this.childStartTime) {
if (this.childStartTime > this.configReceivedTime) {
deadlineInfo.push(`metadata filters: ${(0, deadline_1.formatDateDifference)(this.configReceivedTime, this.childStartTime)}`);
}
}
else {
deadlineInfo.push('waiting for metadata filters');
}
}
else {
deadlineInfo.push('waiting for name resolution');
}
if (this.child) {
deadlineInfo.push(...this.child.getDeadlineInfo());
}
this.cancelWithStatus(constants_1.Status.DEADLINE_EXCEEDED, deadlineInfo.join(','));
};
if (timeout <= 0) {
process.nextTick(handleDeadline);
}
else {
this.deadlineTimer = setTimeout(handleDeadline, timeout);
}
}
}
outputStatus(status) {
if (!this.ended) {
this.ended = true;
if (!this.filterStack) {
this.filterStack = this.filterStackFactory.createFilter();
}
clearTimeout(this.deadlineTimer);
const filteredStatus = this.filterStack.receiveTrailers(status);
this.trace('ended with status: code=' +
filteredStatus.code +
' details="' +
filteredStatus.details +
'"');
this.statusWatchers.forEach(watcher => watcher(filteredStatus));
process.nextTick(() => {
var _a;
(_a = this.listener) === null || _a === void 0 ? void 0 : _a.onReceiveStatus(filteredStatus);
});
}
}
sendMessageOnChild(context, message) {
if (!this.child) {
throw new Error('sendMessageonChild called with child not populated');
}
const child = this.child;
this.writeFilterPending = true;
this.filterStack.sendMessage(Promise.resolve({ message: message, flags: context.flags })).then(filteredMessage => {
this.writeFilterPending = false;
child.sendMessageWithContext(context, filteredMessage.message);
if (this.pendingHalfClose) {
child.halfClose();
}
}, (status) => {
this.cancelWithStatus(status.code, status.details);
});
}
getConfig() {
if (this.ended) {
return;
}
if (!this.metadata || !this.listener) {
throw new Error('getConfig called before start');
}
const configResult = this.channel.getConfig(this.method, this.metadata);
if (configResult.type === 'NONE') {
this.channel.queueCallForConfig(this);
return;
}
else if (configResult.type === 'ERROR') {
if (this.metadata.getOptions().waitForReady) {
this.channel.queueCallForConfig(this);
}
else {
this.outputStatus(configResult.error);
}
return;
}
// configResult.type === 'SUCCESS'
this.configReceivedTime = new Date();
const config = configResult.config;
if (config.status !== constants_1.Status.OK) {
const { code, details } = (0, control_plane_status_1.restrictControlPlaneStatusCode)(config.status, 'Failed to route call to method ' + this.method);
this.outputStatus({
code: code,
details: details,
metadata: new metadata_1.Metadata(),
});
return;
}
if (config.methodConfig.timeout) {
const configDeadline = new Date();
configDeadline.setSeconds(configDeadline.getSeconds() + config.methodConfig.timeout.seconds);
configDeadline.setMilliseconds(configDeadline.getMilliseconds() +
config.methodConfig.timeout.nanos / 1000000);
this.deadline = (0, deadline_1.minDeadline)(this.deadline, configDeadline);
this.runDeadlineTimer();
}
this.filterStackFactory.push(config.dynamicFilterFactories);
this.filterStack = this.filterStackFactory.createFilter();
this.filterStack.sendMetadata(Promise.resolve(this.metadata)).then(filteredMetadata => {
this.child = this.channel.createRetryingCall(config, this.method, this.host, this.credentials, this.deadline);
this.trace('Created child [' + this.child.getCallNumber() + ']');
this.childStartTime = new Date();
this.child.start(filteredMetadata, {
onReceiveMetadata: metadata => {
this.trace('Received metadata');
this.listener.onReceiveMetadata(this.filterStack.receiveMetadata(metadata));
},
onReceiveMessage: message => {
this.trace('Received message');
this.readFilterPending = true;
this.filterStack.receiveMessage(message).then(filteredMesssage => {
this.trace('Finished filtering received message');
this.readFilterPending = false;
this.listener.onReceiveMessage(filteredMesssage);
if (this.pendingChildStatus) {
this.outputStatus(this.pendingChildStatus);
}
}, (status) => {
this.cancelWithStatus(status.code, status.details);
});
},
onReceiveStatus: status => {
this.trace('Received status');
if (this.readFilterPending) {
this.pendingChildStatus = status;
}
else {
this.outputStatus(status);
}
},
});
if (this.readPending) {
this.child.startRead();
}
if (this.pendingMessage) {
this.sendMessageOnChild(this.pendingMessage.context, this.pendingMessage.message);
}
else if (this.pendingHalfClose) {
this.child.halfClose();
}
}, (status) => {
this.outputStatus(status);
});
}
reportResolverError(status) {
var _a;
if ((_a = this.metadata) === null || _a === void 0 ? void 0 : _a.getOptions().waitForReady) {
this.channel.queueCallForConfig(this);
}
else {
this.outputStatus(status);
}
}
cancelWithStatus(status, details) {
var _a;
this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"');
(_a = this.child) === null || _a === void 0 ? void 0 : _a.cancelWithStatus(status, details);
this.outputStatus({
code: status,
details: details,
metadata: new metadata_1.Metadata(),
});
}
getPeer() {
var _a, _b;
return (_b = (_a = this.child) === null || _a === void 0 ? void 0 : _a.getPeer()) !== null && _b !== void 0 ? _b : this.channel.getTarget();
}
start(metadata, listener) {
this.trace('start called');
this.metadata = metadata.clone();
this.listener = listener;
this.getConfig();
}
sendMessageWithContext(context, message) {
this.trace('write() called with message of length ' + message.length);
if (this.child) {
this.sendMessageOnChild(context, message);
}
else {
this.pendingMessage = { context, message };
}
}
startRead() {
this.trace('startRead called');
if (this.child) {
this.child.startRead();
}
else {
this.readPending = true;
}
}
halfClose() {
this.trace('halfClose called');
if (this.child && !this.writeFilterPending) {
this.child.halfClose();
}
else {
this.pendingHalfClose = true;
}
}
setCredentials(credentials) {
this.credentials = credentials;
}
addStatusWatcher(watcher) {
this.statusWatchers.push(watcher);
}
getCallNumber() {
return this.callNumber;
}
getAuthContext() {
if (this.child) {
return this.child.getAuthContext();
}
else {
return null;
}
}
}
exports.ResolvingCall = ResolvingCall;
//# sourceMappingURL=resolving-call.js.map

View File

@@ -0,0 +1,304 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.ResolvingLoadBalancer = void 0;
const load_balancer_1 = require("./load-balancer");
const service_config_1 = require("./service-config");
const connectivity_state_1 = require("./connectivity-state");
const resolver_1 = require("./resolver");
const picker_1 = require("./picker");
const backoff_timeout_1 = require("./backoff-timeout");
const constants_1 = require("./constants");
const metadata_1 = require("./metadata");
const logging = require("./logging");
const constants_2 = require("./constants");
const uri_parser_1 = require("./uri-parser");
const load_balancer_child_handler_1 = require("./load-balancer-child-handler");
const TRACER_NAME = 'resolving_load_balancer';
function trace(text) {
logging.trace(constants_2.LogVerbosity.DEBUG, TRACER_NAME, text);
}
/**
* Name match levels in order from most to least specific. This is the order in
* which searches will be performed.
*/
const NAME_MATCH_LEVEL_ORDER = [
'SERVICE_AND_METHOD',
'SERVICE',
'EMPTY',
];
function hasMatchingName(service, method, methodConfig, matchLevel) {
for (const name of methodConfig.name) {
switch (matchLevel) {
case 'EMPTY':
if (!name.service && !name.method) {
return true;
}
break;
case 'SERVICE':
if (name.service === service && !name.method) {
return true;
}
break;
case 'SERVICE_AND_METHOD':
if (name.service === service && name.method === method) {
return true;
}
}
}
return false;
}
function findMatchingConfig(service, method, methodConfigs, matchLevel) {
for (const config of methodConfigs) {
if (hasMatchingName(service, method, config, matchLevel)) {
return config;
}
}
return null;
}
function getDefaultConfigSelector(serviceConfig) {
return {
invoke(methodName, metadata) {
var _a, _b;
const splitName = methodName.split('/').filter(x => x.length > 0);
const service = (_a = splitName[0]) !== null && _a !== void 0 ? _a : '';
const method = (_b = splitName[1]) !== null && _b !== void 0 ? _b : '';
if (serviceConfig && serviceConfig.methodConfig) {
/* Check for the following in order, and return the first method
* config that matches:
* 1. A name that exactly matches the service and method
* 2. A name with no method set that matches the service
* 3. An empty name
*/
for (const matchLevel of NAME_MATCH_LEVEL_ORDER) {
const matchingConfig = findMatchingConfig(service, method, serviceConfig.methodConfig, matchLevel);
if (matchingConfig) {
return {
methodConfig: matchingConfig,
pickInformation: {},
status: constants_1.Status.OK,
dynamicFilterFactories: [],
};
}
}
}
return {
methodConfig: { name: [] },
pickInformation: {},
status: constants_1.Status.OK,
dynamicFilterFactories: [],
};
},
unref() { }
};
}
class ResolvingLoadBalancer {
/**
* Wrapper class that behaves like a `LoadBalancer` and also handles name
* resolution internally.
* @param target The address of the backend to connect to.
* @param channelControlHelper `ChannelControlHelper` instance provided by
* this load balancer's owner.
* @param defaultServiceConfig The default service configuration to be used
* if none is provided by the name resolver. A `null` value indicates
* that the default behavior should be the default unconfigured behavior.
* In practice, that means using the "pick first" load balancer
* implmentation
*/
constructor(target, channelControlHelper, channelOptions, onSuccessfulResolution, onFailedResolution) {
this.target = target;
this.channelControlHelper = channelControlHelper;
this.channelOptions = channelOptions;
this.onSuccessfulResolution = onSuccessfulResolution;
this.onFailedResolution = onFailedResolution;
this.latestChildState = connectivity_state_1.ConnectivityState.IDLE;
this.latestChildPicker = new picker_1.QueuePicker(this);
this.latestChildErrorMessage = null;
/**
* This resolving load balancer's current connectivity state.
*/
this.currentState = connectivity_state_1.ConnectivityState.IDLE;
/**
* The service config object from the last successful resolution, if
* available. A value of null indicates that we have not yet received a valid
* service config from the resolver.
*/
this.previousServiceConfig = null;
/**
* Indicates whether we should attempt to resolve again after the backoff
* timer runs out.
*/
this.continueResolving = false;
if (channelOptions['grpc.service_config']) {
this.defaultServiceConfig = (0, service_config_1.validateServiceConfig)(JSON.parse(channelOptions['grpc.service_config']));
}
else {
this.defaultServiceConfig = {
loadBalancingConfig: [],
methodConfig: [],
};
}
this.updateState(connectivity_state_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this), null);
this.childLoadBalancer = new load_balancer_child_handler_1.ChildLoadBalancerHandler({
createSubchannel: channelControlHelper.createSubchannel.bind(channelControlHelper),
requestReresolution: () => {
/* If the backoffTimeout is running, we're still backing off from
* making resolve requests, so we shouldn't make another one here.
* In that case, the backoff timer callback will call
* updateResolution */
if (this.backoffTimeout.isRunning()) {
trace('requestReresolution delayed by backoff timer until ' +
this.backoffTimeout.getEndTime().toISOString());
this.continueResolving = true;
}
else {
this.updateResolution();
}
},
updateState: (newState, picker, errorMessage) => {
this.latestChildState = newState;
this.latestChildPicker = picker;
this.latestChildErrorMessage = errorMessage;
this.updateState(newState, picker, errorMessage);
},
addChannelzChild: channelControlHelper.addChannelzChild.bind(channelControlHelper),
removeChannelzChild: channelControlHelper.removeChannelzChild.bind(channelControlHelper),
});
this.innerResolver = (0, resolver_1.createResolver)(target, this.handleResolverResult.bind(this), channelOptions);
const backoffOptions = {
initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'],
maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'],
};
this.backoffTimeout = new backoff_timeout_1.BackoffTimeout(() => {
if (this.continueResolving) {
this.updateResolution();
this.continueResolving = false;
}
else {
this.updateState(this.latestChildState, this.latestChildPicker, this.latestChildErrorMessage);
}
}, backoffOptions);
this.backoffTimeout.unref();
}
handleResolverResult(endpointList, attributes, serviceConfig, resolutionNote) {
var _a, _b;
this.backoffTimeout.stop();
this.backoffTimeout.reset();
let resultAccepted = true;
let workingServiceConfig = null;
if (serviceConfig === null) {
workingServiceConfig = this.defaultServiceConfig;
}
else if (serviceConfig.ok) {
workingServiceConfig = serviceConfig.value;
}
else {
if (this.previousServiceConfig !== null) {
workingServiceConfig = this.previousServiceConfig;
}
else {
resultAccepted = false;
this.handleResolutionFailure(serviceConfig.error);
}
}
if (workingServiceConfig !== null) {
const workingConfigList = (_a = workingServiceConfig === null || workingServiceConfig === void 0 ? void 0 : workingServiceConfig.loadBalancingConfig) !== null && _a !== void 0 ? _a : [];
const loadBalancingConfig = (0, load_balancer_1.selectLbConfigFromList)(workingConfigList, true);
if (loadBalancingConfig === null) {
resultAccepted = false;
this.handleResolutionFailure({
code: constants_1.Status.UNAVAILABLE,
details: 'All load balancer options in service config are not compatible',
metadata: new metadata_1.Metadata(),
});
}
else {
resultAccepted = this.childLoadBalancer.updateAddressList(endpointList, loadBalancingConfig, Object.assign(Object.assign({}, this.channelOptions), attributes), resolutionNote);
}
}
if (resultAccepted) {
this.onSuccessfulResolution(workingServiceConfig, (_b = attributes[resolver_1.CHANNEL_ARGS_CONFIG_SELECTOR_KEY]) !== null && _b !== void 0 ? _b : getDefaultConfigSelector(workingServiceConfig));
}
return resultAccepted;
}
updateResolution() {
this.innerResolver.updateResolution();
if (this.currentState === connectivity_state_1.ConnectivityState.IDLE) {
/* this.latestChildPicker is initialized as new QueuePicker(this), which
* is an appropriate value here if the child LB policy is unset.
* Otherwise, we want to delegate to the child here, in case that
* triggers something. */
this.updateState(connectivity_state_1.ConnectivityState.CONNECTING, this.latestChildPicker, this.latestChildErrorMessage);
}
this.backoffTimeout.runOnce();
}
updateState(connectivityState, picker, errorMessage) {
trace((0, uri_parser_1.uriToString)(this.target) +
' ' +
connectivity_state_1.ConnectivityState[this.currentState] +
' -> ' +
connectivity_state_1.ConnectivityState[connectivityState]);
// Ensure that this.exitIdle() is called by the picker
if (connectivityState === connectivity_state_1.ConnectivityState.IDLE) {
picker = new picker_1.QueuePicker(this, picker);
}
this.currentState = connectivityState;
this.channelControlHelper.updateState(connectivityState, picker, errorMessage);
}
handleResolutionFailure(error) {
if (this.latestChildState === connectivity_state_1.ConnectivityState.IDLE) {
this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker(error), error.details);
this.onFailedResolution(error);
}
}
exitIdle() {
if (this.currentState === connectivity_state_1.ConnectivityState.IDLE ||
this.currentState === connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) {
if (this.backoffTimeout.isRunning()) {
this.continueResolving = true;
}
else {
this.updateResolution();
}
}
this.childLoadBalancer.exitIdle();
}
updateAddressList(endpointList, lbConfig) {
throw new Error('updateAddressList not supported on ResolvingLoadBalancer');
}
resetBackoff() {
this.backoffTimeout.reset();
this.childLoadBalancer.resetBackoff();
}
destroy() {
this.childLoadBalancer.destroy();
this.innerResolver.destroy();
this.backoffTimeout.reset();
this.backoffTimeout.stop();
this.latestChildState = connectivity_state_1.ConnectivityState.IDLE;
this.latestChildPicker = new picker_1.QueuePicker(this);
this.currentState = connectivity_state_1.ConnectivityState.IDLE;
this.previousServiceConfig = null;
this.continueResolving = false;
}
getTypeName() {
return 'resolving_load_balancer';
}
}
exports.ResolvingLoadBalancer = ResolvingLoadBalancer;
//# sourceMappingURL=resolving-load-balancer.js.map

View File

@@ -0,0 +1,700 @@
"use strict";
/*
* Copyright 2022 gRPC authors.
*
* Licensed 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.RetryingCall = exports.MessageBufferTracker = exports.RetryThrottler = void 0;
const constants_1 = require("./constants");
const deadline_1 = require("./deadline");
const metadata_1 = require("./metadata");
const logging = require("./logging");
const TRACER_NAME = 'retrying_call';
class RetryThrottler {
constructor(maxTokens, tokenRatio, previousRetryThrottler) {
this.maxTokens = maxTokens;
this.tokenRatio = tokenRatio;
if (previousRetryThrottler) {
/* When carrying over tokens from a previous config, rescale them to the
* new max value */
this.tokens =
previousRetryThrottler.tokens *
(maxTokens / previousRetryThrottler.maxTokens);
}
else {
this.tokens = maxTokens;
}
}
addCallSucceeded() {
this.tokens = Math.min(this.tokens + this.tokenRatio, this.maxTokens);
}
addCallFailed() {
this.tokens = Math.max(this.tokens - 1, 0);
}
canRetryCall() {
return this.tokens > (this.maxTokens / 2);
}
}
exports.RetryThrottler = RetryThrottler;
class MessageBufferTracker {
constructor(totalLimit, limitPerCall) {
this.totalLimit = totalLimit;
this.limitPerCall = limitPerCall;
this.totalAllocated = 0;
this.allocatedPerCall = new Map();
}
allocate(size, callId) {
var _a;
const currentPerCall = (_a = this.allocatedPerCall.get(callId)) !== null && _a !== void 0 ? _a : 0;
if (this.limitPerCall - currentPerCall < size ||
this.totalLimit - this.totalAllocated < size) {
return false;
}
this.allocatedPerCall.set(callId, currentPerCall + size);
this.totalAllocated += size;
return true;
}
free(size, callId) {
var _a;
if (this.totalAllocated < size) {
throw new Error(`Invalid buffer allocation state: call ${callId} freed ${size} > total allocated ${this.totalAllocated}`);
}
this.totalAllocated -= size;
const currentPerCall = (_a = this.allocatedPerCall.get(callId)) !== null && _a !== void 0 ? _a : 0;
if (currentPerCall < size) {
throw new Error(`Invalid buffer allocation state: call ${callId} freed ${size} > allocated for call ${currentPerCall}`);
}
this.allocatedPerCall.set(callId, currentPerCall - size);
}
freeAll(callId) {
var _a;
const currentPerCall = (_a = this.allocatedPerCall.get(callId)) !== null && _a !== void 0 ? _a : 0;
if (this.totalAllocated < currentPerCall) {
throw new Error(`Invalid buffer allocation state: call ${callId} allocated ${currentPerCall} > total allocated ${this.totalAllocated}`);
}
this.totalAllocated -= currentPerCall;
this.allocatedPerCall.delete(callId);
}
}
exports.MessageBufferTracker = MessageBufferTracker;
const PREVIONS_RPC_ATTEMPTS_METADATA_KEY = 'grpc-previous-rpc-attempts';
const DEFAULT_MAX_ATTEMPTS_LIMIT = 5;
class RetryingCall {
constructor(channel, callConfig, methodName, host, credentials, deadline, callNumber, bufferTracker, retryThrottler) {
var _a;
this.channel = channel;
this.callConfig = callConfig;
this.methodName = methodName;
this.host = host;
this.credentials = credentials;
this.deadline = deadline;
this.callNumber = callNumber;
this.bufferTracker = bufferTracker;
this.retryThrottler = retryThrottler;
this.listener = null;
this.initialMetadata = null;
this.underlyingCalls = [];
this.writeBuffer = [];
/**
* The offset of message indices in the writeBuffer. For example, if
* writeBufferOffset is 10, message 10 is in writeBuffer[0] and message 15
* is in writeBuffer[5].
*/
this.writeBufferOffset = 0;
/**
* Tracks whether a read has been started, so that we know whether to start
* reads on new child calls. This only matters for the first read, because
* once a message comes in the child call becomes committed and there will
* be no new child calls.
*/
this.readStarted = false;
this.transparentRetryUsed = false;
/**
* Number of attempts so far
*/
this.attempts = 0;
this.hedgingTimer = null;
this.committedCallIndex = null;
this.initialRetryBackoffSec = 0;
this.nextRetryBackoffSec = 0;
const maxAttemptsLimit = (_a = channel.getOptions()['grpc-node.retry_max_attempts_limit']) !== null && _a !== void 0 ? _a : DEFAULT_MAX_ATTEMPTS_LIMIT;
if (channel.getOptions()['grpc.enable_retries'] === 0) {
this.state = 'NO_RETRY';
this.maxAttempts = 1;
}
else if (callConfig.methodConfig.retryPolicy) {
this.state = 'RETRY';
const retryPolicy = callConfig.methodConfig.retryPolicy;
this.nextRetryBackoffSec = this.initialRetryBackoffSec = Number(retryPolicy.initialBackoff.substring(0, retryPolicy.initialBackoff.length - 1));
this.maxAttempts = Math.min(retryPolicy.maxAttempts, maxAttemptsLimit);
}
else if (callConfig.methodConfig.hedgingPolicy) {
this.state = 'HEDGING';
this.maxAttempts = Math.min(callConfig.methodConfig.hedgingPolicy.maxAttempts, maxAttemptsLimit);
}
else {
this.state = 'TRANSPARENT_ONLY';
this.maxAttempts = 1;
}
this.startTime = new Date();
}
getDeadlineInfo() {
if (this.underlyingCalls.length === 0) {
return [];
}
const deadlineInfo = [];
const latestCall = this.underlyingCalls[this.underlyingCalls.length - 1];
if (this.underlyingCalls.length > 1) {
deadlineInfo.push(`previous attempts: ${this.underlyingCalls.length - 1}`);
}
if (latestCall.startTime > this.startTime) {
deadlineInfo.push(`time to current attempt start: ${(0, deadline_1.formatDateDifference)(this.startTime, latestCall.startTime)}`);
}
deadlineInfo.push(...latestCall.call.getDeadlineInfo());
return deadlineInfo;
}
getCallNumber() {
return this.callNumber;
}
trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, '[' + this.callNumber + '] ' + text);
}
reportStatus(statusObject) {
this.trace('ended with status: code=' +
statusObject.code +
' details="' +
statusObject.details +
'" start time=' +
this.startTime.toISOString());
this.bufferTracker.freeAll(this.callNumber);
this.writeBufferOffset = this.writeBufferOffset + this.writeBuffer.length;
this.writeBuffer = [];
process.nextTick(() => {
var _a;
// Explicitly construct status object to remove progress field
(_a = this.listener) === null || _a === void 0 ? void 0 : _a.onReceiveStatus({
code: statusObject.code,
details: statusObject.details,
metadata: statusObject.metadata,
});
});
}
cancelWithStatus(status, details) {
this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"');
this.reportStatus({ code: status, details, metadata: new metadata_1.Metadata() });
for (const { call } of this.underlyingCalls) {
call.cancelWithStatus(status, details);
}
}
getPeer() {
if (this.committedCallIndex !== null) {
return this.underlyingCalls[this.committedCallIndex].call.getPeer();
}
else {
return 'unknown';
}
}
getBufferEntry(messageIndex) {
var _a;
return ((_a = this.writeBuffer[messageIndex - this.writeBufferOffset]) !== null && _a !== void 0 ? _a : {
entryType: 'FREED',
allocated: false,
});
}
getNextBufferIndex() {
return this.writeBufferOffset + this.writeBuffer.length;
}
clearSentMessages() {
if (this.state !== 'COMMITTED') {
return;
}
let earliestNeededMessageIndex;
if (this.underlyingCalls[this.committedCallIndex].state === 'COMPLETED') {
/* If the committed call is completed, clear all messages, even if some
* have not been sent. */
earliestNeededMessageIndex = this.getNextBufferIndex();
}
else {
earliestNeededMessageIndex =
this.underlyingCalls[this.committedCallIndex].nextMessageToSend;
}
for (let messageIndex = this.writeBufferOffset; messageIndex < earliestNeededMessageIndex; messageIndex++) {
const bufferEntry = this.getBufferEntry(messageIndex);
if (bufferEntry.allocated) {
this.bufferTracker.free(bufferEntry.message.message.length, this.callNumber);
}
}
this.writeBuffer = this.writeBuffer.slice(earliestNeededMessageIndex - this.writeBufferOffset);
this.writeBufferOffset = earliestNeededMessageIndex;
}
commitCall(index) {
var _a, _b;
if (this.state === 'COMMITTED') {
return;
}
this.trace('Committing call [' +
this.underlyingCalls[index].call.getCallNumber() +
'] at index ' +
index);
this.state = 'COMMITTED';
(_b = (_a = this.callConfig).onCommitted) === null || _b === void 0 ? void 0 : _b.call(_a);
this.committedCallIndex = index;
for (let i = 0; i < this.underlyingCalls.length; i++) {
if (i === index) {
continue;
}
if (this.underlyingCalls[i].state === 'COMPLETED') {
continue;
}
this.underlyingCalls[i].state = 'COMPLETED';
this.underlyingCalls[i].call.cancelWithStatus(constants_1.Status.CANCELLED, 'Discarded in favor of other hedged attempt');
}
this.clearSentMessages();
}
commitCallWithMostMessages() {
if (this.state === 'COMMITTED') {
return;
}
let mostMessages = -1;
let callWithMostMessages = -1;
for (const [index, childCall] of this.underlyingCalls.entries()) {
if (childCall.state === 'ACTIVE' &&
childCall.nextMessageToSend > mostMessages) {
mostMessages = childCall.nextMessageToSend;
callWithMostMessages = index;
}
}
if (callWithMostMessages === -1) {
/* There are no active calls, disable retries to force the next call that
* is started to be committed. */
this.state = 'TRANSPARENT_ONLY';
}
else {
this.commitCall(callWithMostMessages);
}
}
isStatusCodeInList(list, code) {
return list.some(value => {
var _a;
return value === code ||
value.toString().toLowerCase() === ((_a = constants_1.Status[code]) === null || _a === void 0 ? void 0 : _a.toLowerCase());
});
}
getNextRetryJitter() {
/* Jitter of +-20% is applied: https://github.com/grpc/proposal/blob/master/A6-client-retries.md#exponential-backoff */
return Math.random() * (1.2 - 0.8) + 0.8;
}
getNextRetryBackoffMs() {
var _a;
const retryPolicy = (_a = this.callConfig) === null || _a === void 0 ? void 0 : _a.methodConfig.retryPolicy;
if (!retryPolicy) {
return 0;
}
const jitter = this.getNextRetryJitter();
const nextBackoffMs = jitter * this.nextRetryBackoffSec * 1000;
const maxBackoffSec = Number(retryPolicy.maxBackoff.substring(0, retryPolicy.maxBackoff.length - 1));
this.nextRetryBackoffSec = Math.min(this.nextRetryBackoffSec * retryPolicy.backoffMultiplier, maxBackoffSec);
return nextBackoffMs;
}
maybeRetryCall(pushback, callback) {
if (this.state !== 'RETRY') {
callback(false);
return;
}
if (this.attempts >= this.maxAttempts) {
callback(false);
return;
}
let retryDelayMs;
if (pushback === null) {
retryDelayMs = this.getNextRetryBackoffMs();
}
else if (pushback < 0) {
this.state = 'TRANSPARENT_ONLY';
callback(false);
return;
}
else {
retryDelayMs = pushback;
this.nextRetryBackoffSec = this.initialRetryBackoffSec;
}
setTimeout(() => {
var _a, _b;
if (this.state !== 'RETRY') {
callback(false);
return;
}
if ((_b = (_a = this.retryThrottler) === null || _a === void 0 ? void 0 : _a.canRetryCall()) !== null && _b !== void 0 ? _b : true) {
callback(true);
this.attempts += 1;
this.startNewAttempt();
}
else {
this.trace('Retry attempt denied by throttling policy');
callback(false);
}
}, retryDelayMs);
}
countActiveCalls() {
let count = 0;
for (const call of this.underlyingCalls) {
if ((call === null || call === void 0 ? void 0 : call.state) === 'ACTIVE') {
count += 1;
}
}
return count;
}
handleProcessedStatus(status, callIndex, pushback) {
var _a, _b, _c;
switch (this.state) {
case 'COMMITTED':
case 'NO_RETRY':
case 'TRANSPARENT_ONLY':
this.commitCall(callIndex);
this.reportStatus(status);
break;
case 'HEDGING':
if (this.isStatusCodeInList((_a = this.callConfig.methodConfig.hedgingPolicy.nonFatalStatusCodes) !== null && _a !== void 0 ? _a : [], status.code)) {
(_b = this.retryThrottler) === null || _b === void 0 ? void 0 : _b.addCallFailed();
let delayMs;
if (pushback === null) {
delayMs = 0;
}
else if (pushback < 0) {
this.state = 'TRANSPARENT_ONLY';
this.commitCall(callIndex);
this.reportStatus(status);
return;
}
else {
delayMs = pushback;
}
setTimeout(() => {
this.maybeStartHedgingAttempt();
// If after trying to start a call there are no active calls, this was the last one
if (this.countActiveCalls() === 0) {
this.commitCall(callIndex);
this.reportStatus(status);
}
}, delayMs);
}
else {
this.commitCall(callIndex);
this.reportStatus(status);
}
break;
case 'RETRY':
if (this.isStatusCodeInList(this.callConfig.methodConfig.retryPolicy.retryableStatusCodes, status.code)) {
(_c = this.retryThrottler) === null || _c === void 0 ? void 0 : _c.addCallFailed();
this.maybeRetryCall(pushback, retried => {
if (!retried) {
this.commitCall(callIndex);
this.reportStatus(status);
}
});
}
else {
this.commitCall(callIndex);
this.reportStatus(status);
}
break;
}
}
getPushback(metadata) {
const mdValue = metadata.get('grpc-retry-pushback-ms');
if (mdValue.length === 0) {
return null;
}
try {
return parseInt(mdValue[0]);
}
catch (e) {
return -1;
}
}
handleChildStatus(status, callIndex) {
var _a;
if (this.underlyingCalls[callIndex].state === 'COMPLETED') {
return;
}
this.trace('state=' +
this.state +
' handling status with progress ' +
status.progress +
' from child [' +
this.underlyingCalls[callIndex].call.getCallNumber() +
'] in state ' +
this.underlyingCalls[callIndex].state);
this.underlyingCalls[callIndex].state = 'COMPLETED';
if (status.code === constants_1.Status.OK) {
(_a = this.retryThrottler) === null || _a === void 0 ? void 0 : _a.addCallSucceeded();
this.commitCall(callIndex);
this.reportStatus(status);
return;
}
if (this.state === 'NO_RETRY') {
this.commitCall(callIndex);
this.reportStatus(status);
return;
}
if (this.state === 'COMMITTED') {
this.reportStatus(status);
return;
}
const pushback = this.getPushback(status.metadata);
switch (status.progress) {
case 'NOT_STARTED':
// RPC never leaves the client, always safe to retry
this.startNewAttempt();
break;
case 'REFUSED':
// RPC reaches the server library, but not the server application logic
if (this.transparentRetryUsed) {
this.handleProcessedStatus(status, callIndex, pushback);
}
else {
this.transparentRetryUsed = true;
this.startNewAttempt();
}
break;
case 'DROP':
this.commitCall(callIndex);
this.reportStatus(status);
break;
case 'PROCESSED':
this.handleProcessedStatus(status, callIndex, pushback);
break;
}
}
maybeStartHedgingAttempt() {
if (this.state !== 'HEDGING') {
return;
}
if (!this.callConfig.methodConfig.hedgingPolicy) {
return;
}
if (this.attempts >= this.maxAttempts) {
return;
}
this.attempts += 1;
this.startNewAttempt();
this.maybeStartHedgingTimer();
}
maybeStartHedgingTimer() {
var _a, _b, _c;
if (this.hedgingTimer) {
clearTimeout(this.hedgingTimer);
}
if (this.state !== 'HEDGING') {
return;
}
if (!this.callConfig.methodConfig.hedgingPolicy) {
return;
}
const hedgingPolicy = this.callConfig.methodConfig.hedgingPolicy;
if (this.attempts >= this.maxAttempts) {
return;
}
const hedgingDelayString = (_a = hedgingPolicy.hedgingDelay) !== null && _a !== void 0 ? _a : '0s';
const hedgingDelaySec = Number(hedgingDelayString.substring(0, hedgingDelayString.length - 1));
this.hedgingTimer = setTimeout(() => {
this.maybeStartHedgingAttempt();
}, hedgingDelaySec * 1000);
(_c = (_b = this.hedgingTimer).unref) === null || _c === void 0 ? void 0 : _c.call(_b);
}
startNewAttempt() {
const child = this.channel.createLoadBalancingCall(this.callConfig, this.methodName, this.host, this.credentials, this.deadline);
this.trace('Created child call [' +
child.getCallNumber() +
'] for attempt ' +
this.attempts);
const index = this.underlyingCalls.length;
this.underlyingCalls.push({
state: 'ACTIVE',
call: child,
nextMessageToSend: 0,
startTime: new Date(),
});
const previousAttempts = this.attempts - 1;
const initialMetadata = this.initialMetadata.clone();
if (previousAttempts > 0) {
initialMetadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`);
}
let receivedMetadata = false;
child.start(initialMetadata, {
onReceiveMetadata: metadata => {
this.trace('Received metadata from child [' + child.getCallNumber() + ']');
this.commitCall(index);
receivedMetadata = true;
if (previousAttempts > 0) {
metadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`);
}
if (this.underlyingCalls[index].state === 'ACTIVE') {
this.listener.onReceiveMetadata(metadata);
}
},
onReceiveMessage: message => {
this.trace('Received message from child [' + child.getCallNumber() + ']');
this.commitCall(index);
if (this.underlyingCalls[index].state === 'ACTIVE') {
this.listener.onReceiveMessage(message);
}
},
onReceiveStatus: status => {
this.trace('Received status from child [' + child.getCallNumber() + ']');
if (!receivedMetadata && previousAttempts > 0) {
status.metadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`);
}
this.handleChildStatus(status, index);
},
});
this.sendNextChildMessage(index);
if (this.readStarted) {
child.startRead();
}
}
start(metadata, listener) {
this.trace('start called');
this.listener = listener;
this.initialMetadata = metadata;
this.attempts += 1;
this.startNewAttempt();
this.maybeStartHedgingTimer();
}
handleChildWriteCompleted(childIndex) {
var _a, _b;
const childCall = this.underlyingCalls[childIndex];
const messageIndex = childCall.nextMessageToSend;
(_b = (_a = this.getBufferEntry(messageIndex)).callback) === null || _b === void 0 ? void 0 : _b.call(_a);
this.clearSentMessages();
childCall.nextMessageToSend += 1;
this.sendNextChildMessage(childIndex);
}
sendNextChildMessage(childIndex) {
const childCall = this.underlyingCalls[childIndex];
if (childCall.state === 'COMPLETED') {
return;
}
if (this.getBufferEntry(childCall.nextMessageToSend)) {
const bufferEntry = this.getBufferEntry(childCall.nextMessageToSend);
switch (bufferEntry.entryType) {
case 'MESSAGE':
childCall.call.sendMessageWithContext({
callback: error => {
// Ignore error
this.handleChildWriteCompleted(childIndex);
},
}, bufferEntry.message.message);
break;
case 'HALF_CLOSE':
childCall.nextMessageToSend += 1;
childCall.call.halfClose();
break;
case 'FREED':
// Should not be possible
break;
}
}
}
sendMessageWithContext(context, message) {
var _a;
this.trace('write() called with message of length ' + message.length);
const writeObj = {
message,
flags: context.flags,
};
const messageIndex = this.getNextBufferIndex();
const bufferEntry = {
entryType: 'MESSAGE',
message: writeObj,
allocated: this.bufferTracker.allocate(message.length, this.callNumber),
};
this.writeBuffer.push(bufferEntry);
if (bufferEntry.allocated) {
(_a = context.callback) === null || _a === void 0 ? void 0 : _a.call(context);
for (const [callIndex, call] of this.underlyingCalls.entries()) {
if (call.state === 'ACTIVE' &&
call.nextMessageToSend === messageIndex) {
call.call.sendMessageWithContext({
callback: error => {
// Ignore error
this.handleChildWriteCompleted(callIndex);
},
}, message);
}
}
}
else {
this.commitCallWithMostMessages();
// commitCallWithMostMessages can fail if we are between ping attempts
if (this.committedCallIndex === null) {
return;
}
const call = this.underlyingCalls[this.committedCallIndex];
bufferEntry.callback = context.callback;
if (call.state === 'ACTIVE' && call.nextMessageToSend === messageIndex) {
call.call.sendMessageWithContext({
callback: error => {
// Ignore error
this.handleChildWriteCompleted(this.committedCallIndex);
},
}, message);
}
}
}
startRead() {
this.trace('startRead called');
this.readStarted = true;
for (const underlyingCall of this.underlyingCalls) {
if ((underlyingCall === null || underlyingCall === void 0 ? void 0 : underlyingCall.state) === 'ACTIVE') {
underlyingCall.call.startRead();
}
}
}
halfClose() {
this.trace('halfClose called');
const halfCloseIndex = this.getNextBufferIndex();
this.writeBuffer.push({
entryType: 'HALF_CLOSE',
allocated: false,
});
for (const call of this.underlyingCalls) {
if ((call === null || call === void 0 ? void 0 : call.state) === 'ACTIVE' &&
call.nextMessageToSend === halfCloseIndex) {
call.nextMessageToSend += 1;
call.call.halfClose();
}
}
}
setCredentials(newCredentials) {
throw new Error('Method not implemented.');
}
getMethod() {
return this.methodName;
}
getHost() {
return this.host;
}
getAuthContext() {
if (this.committedCallIndex !== null) {
return this.underlyingCalls[this.committedCallIndex].call.getAuthContext();
}
else {
return null;
}
}
}
exports.RetryingCall = RetryingCall;
//# sourceMappingURL=retrying-call.js.map

View File

@@ -0,0 +1,226 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.ServerDuplexStreamImpl = exports.ServerWritableStreamImpl = exports.ServerReadableStreamImpl = exports.ServerUnaryCallImpl = void 0;
exports.serverErrorToStatus = serverErrorToStatus;
const events_1 = require("events");
const stream_1 = require("stream");
const constants_1 = require("./constants");
const metadata_1 = require("./metadata");
function serverErrorToStatus(error, overrideTrailers) {
var _a;
const status = {
code: constants_1.Status.UNKNOWN,
details: 'message' in error ? error.message : 'Unknown Error',
metadata: (_a = overrideTrailers !== null && overrideTrailers !== void 0 ? overrideTrailers : error.metadata) !== null && _a !== void 0 ? _a : null,
};
if ('code' in error &&
typeof error.code === 'number' &&
Number.isInteger(error.code)) {
status.code = error.code;
if ('details' in error && typeof error.details === 'string') {
status.details = error.details;
}
}
return status;
}
class ServerUnaryCallImpl extends events_1.EventEmitter {
constructor(path, call, metadata, request) {
super();
this.path = path;
this.call = call;
this.metadata = metadata;
this.request = request;
this.cancelled = false;
}
getPeer() {
return this.call.getPeer();
}
sendMetadata(responseMetadata) {
this.call.sendMetadata(responseMetadata);
}
getDeadline() {
return this.call.getDeadline();
}
getPath() {
return this.path;
}
getHost() {
return this.call.getHost();
}
getAuthContext() {
return this.call.getAuthContext();
}
getMetricsRecorder() {
return this.call.getMetricsRecorder();
}
}
exports.ServerUnaryCallImpl = ServerUnaryCallImpl;
class ServerReadableStreamImpl extends stream_1.Readable {
constructor(path, call, metadata) {
super({ objectMode: true });
this.path = path;
this.call = call;
this.metadata = metadata;
this.cancelled = false;
}
_read(size) {
this.call.startRead();
}
getPeer() {
return this.call.getPeer();
}
sendMetadata(responseMetadata) {
this.call.sendMetadata(responseMetadata);
}
getDeadline() {
return this.call.getDeadline();
}
getPath() {
return this.path;
}
getHost() {
return this.call.getHost();
}
getAuthContext() {
return this.call.getAuthContext();
}
getMetricsRecorder() {
return this.call.getMetricsRecorder();
}
}
exports.ServerReadableStreamImpl = ServerReadableStreamImpl;
class ServerWritableStreamImpl extends stream_1.Writable {
constructor(path, call, metadata, request) {
super({ objectMode: true });
this.path = path;
this.call = call;
this.metadata = metadata;
this.request = request;
this.pendingStatus = {
code: constants_1.Status.OK,
details: 'OK',
};
this.cancelled = false;
this.trailingMetadata = new metadata_1.Metadata();
this.on('error', err => {
this.pendingStatus = serverErrorToStatus(err);
this.end();
});
}
getPeer() {
return this.call.getPeer();
}
sendMetadata(responseMetadata) {
this.call.sendMetadata(responseMetadata);
}
getDeadline() {
return this.call.getDeadline();
}
getPath() {
return this.path;
}
getHost() {
return this.call.getHost();
}
getAuthContext() {
return this.call.getAuthContext();
}
getMetricsRecorder() {
return this.call.getMetricsRecorder();
}
_write(chunk, encoding,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callback) {
this.call.sendMessage(chunk, callback);
}
_final(callback) {
var _a;
callback(null);
this.call.sendStatus(Object.assign(Object.assign({}, this.pendingStatus), { metadata: (_a = this.pendingStatus.metadata) !== null && _a !== void 0 ? _a : this.trailingMetadata }));
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
end(metadata) {
if (metadata) {
this.trailingMetadata = metadata;
}
return super.end();
}
}
exports.ServerWritableStreamImpl = ServerWritableStreamImpl;
class ServerDuplexStreamImpl extends stream_1.Duplex {
constructor(path, call, metadata) {
super({ objectMode: true });
this.path = path;
this.call = call;
this.metadata = metadata;
this.pendingStatus = {
code: constants_1.Status.OK,
details: 'OK',
};
this.cancelled = false;
this.trailingMetadata = new metadata_1.Metadata();
this.on('error', err => {
this.pendingStatus = serverErrorToStatus(err);
this.end();
});
}
getPeer() {
return this.call.getPeer();
}
sendMetadata(responseMetadata) {
this.call.sendMetadata(responseMetadata);
}
getDeadline() {
return this.call.getDeadline();
}
getPath() {
return this.path;
}
getHost() {
return this.call.getHost();
}
getAuthContext() {
return this.call.getAuthContext();
}
getMetricsRecorder() {
return this.call.getMetricsRecorder();
}
_read(size) {
this.call.startRead();
}
_write(chunk, encoding,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callback) {
this.call.sendMessage(chunk, callback);
}
_final(callback) {
var _a;
callback(null);
this.call.sendStatus(Object.assign(Object.assign({}, this.pendingStatus), { metadata: (_a = this.pendingStatus.metadata) !== null && _a !== void 0 ? _a : this.trailingMetadata }));
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
end(metadata) {
if (metadata) {
this.trailingMetadata = metadata;
}
return super.end();
}
}
exports.ServerDuplexStreamImpl = ServerDuplexStreamImpl;
//# sourceMappingURL=server-call.js.map

View File

@@ -0,0 +1,314 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.ServerCredentials = void 0;
exports.createCertificateProviderServerCredentials = createCertificateProviderServerCredentials;
exports.createServerCredentialsWithInterceptors = createServerCredentialsWithInterceptors;
const tls_helpers_1 = require("./tls-helpers");
class ServerCredentials {
constructor(serverConstructorOptions, contextOptions) {
this.serverConstructorOptions = serverConstructorOptions;
this.watchers = new Set();
this.latestContextOptions = null;
this.latestContextOptions = contextOptions !== null && contextOptions !== void 0 ? contextOptions : null;
}
_addWatcher(watcher) {
this.watchers.add(watcher);
}
_removeWatcher(watcher) {
this.watchers.delete(watcher);
}
getWatcherCount() {
return this.watchers.size;
}
updateSecureContextOptions(options) {
this.latestContextOptions = options;
for (const watcher of this.watchers) {
watcher(this.latestContextOptions);
}
}
_isSecure() {
return this.serverConstructorOptions !== null;
}
_getSecureContextOptions() {
return this.latestContextOptions;
}
_getConstructorOptions() {
return this.serverConstructorOptions;
}
_getInterceptors() {
return [];
}
static createInsecure() {
return new InsecureServerCredentials();
}
static createSsl(rootCerts, keyCertPairs, checkClientCertificate = false) {
var _a;
if (rootCerts !== null && !Buffer.isBuffer(rootCerts)) {
throw new TypeError('rootCerts must be null or a Buffer');
}
if (!Array.isArray(keyCertPairs)) {
throw new TypeError('keyCertPairs must be an array');
}
if (typeof checkClientCertificate !== 'boolean') {
throw new TypeError('checkClientCertificate must be a boolean');
}
const cert = [];
const key = [];
for (let i = 0; i < keyCertPairs.length; i++) {
const pair = keyCertPairs[i];
if (pair === null || typeof pair !== 'object') {
throw new TypeError(`keyCertPair[${i}] must be an object`);
}
if (!Buffer.isBuffer(pair.private_key)) {
throw new TypeError(`keyCertPair[${i}].private_key must be a Buffer`);
}
if (!Buffer.isBuffer(pair.cert_chain)) {
throw new TypeError(`keyCertPair[${i}].cert_chain must be a Buffer`);
}
cert.push(pair.cert_chain);
key.push(pair.private_key);
}
return new SecureServerCredentials({
requestCert: checkClientCertificate,
ciphers: tls_helpers_1.CIPHER_SUITES,
}, {
ca: (_a = rootCerts !== null && rootCerts !== void 0 ? rootCerts : (0, tls_helpers_1.getDefaultRootsData)()) !== null && _a !== void 0 ? _a : undefined,
cert,
key,
});
}
}
exports.ServerCredentials = ServerCredentials;
class InsecureServerCredentials extends ServerCredentials {
constructor() {
super(null);
}
_getSettings() {
return null;
}
_equals(other) {
return other instanceof InsecureServerCredentials;
}
}
class SecureServerCredentials extends ServerCredentials {
constructor(constructorOptions, contextOptions) {
super(constructorOptions, contextOptions);
this.options = Object.assign(Object.assign({}, constructorOptions), contextOptions);
}
/**
* Checks equality by checking the options that are actually set by
* createSsl.
* @param other
* @returns
*/
_equals(other) {
if (this === other) {
return true;
}
if (!(other instanceof SecureServerCredentials)) {
return false;
}
// options.ca equality check
if (Buffer.isBuffer(this.options.ca) && Buffer.isBuffer(other.options.ca)) {
if (!this.options.ca.equals(other.options.ca)) {
return false;
}
}
else {
if (this.options.ca !== other.options.ca) {
return false;
}
}
// options.cert equality check
if (Array.isArray(this.options.cert) && Array.isArray(other.options.cert)) {
if (this.options.cert.length !== other.options.cert.length) {
return false;
}
for (let i = 0; i < this.options.cert.length; i++) {
const thisCert = this.options.cert[i];
const otherCert = other.options.cert[i];
if (Buffer.isBuffer(thisCert) && Buffer.isBuffer(otherCert)) {
if (!thisCert.equals(otherCert)) {
return false;
}
}
else {
if (thisCert !== otherCert) {
return false;
}
}
}
}
else {
if (this.options.cert !== other.options.cert) {
return false;
}
}
// options.key equality check
if (Array.isArray(this.options.key) && Array.isArray(other.options.key)) {
if (this.options.key.length !== other.options.key.length) {
return false;
}
for (let i = 0; i < this.options.key.length; i++) {
const thisKey = this.options.key[i];
const otherKey = other.options.key[i];
if (Buffer.isBuffer(thisKey) && Buffer.isBuffer(otherKey)) {
if (!thisKey.equals(otherKey)) {
return false;
}
}
else {
if (thisKey !== otherKey) {
return false;
}
}
}
}
else {
if (this.options.key !== other.options.key) {
return false;
}
}
// options.requestCert equality check
if (this.options.requestCert !== other.options.requestCert) {
return false;
}
/* ciphers is derived from a value that is constant for the process, so no
* equality check is needed. */
return true;
}
}
class CertificateProviderServerCredentials extends ServerCredentials {
constructor(identityCertificateProvider, caCertificateProvider, requireClientCertificate) {
super({
requestCert: caCertificateProvider !== null,
rejectUnauthorized: requireClientCertificate,
ciphers: tls_helpers_1.CIPHER_SUITES
});
this.identityCertificateProvider = identityCertificateProvider;
this.caCertificateProvider = caCertificateProvider;
this.requireClientCertificate = requireClientCertificate;
this.latestCaUpdate = null;
this.latestIdentityUpdate = null;
this.caCertificateUpdateListener = this.handleCaCertificateUpdate.bind(this);
this.identityCertificateUpdateListener = this.handleIdentityCertitificateUpdate.bind(this);
}
_addWatcher(watcher) {
var _a;
if (this.getWatcherCount() === 0) {
(_a = this.caCertificateProvider) === null || _a === void 0 ? void 0 : _a.addCaCertificateListener(this.caCertificateUpdateListener);
this.identityCertificateProvider.addIdentityCertificateListener(this.identityCertificateUpdateListener);
}
super._addWatcher(watcher);
}
_removeWatcher(watcher) {
var _a;
super._removeWatcher(watcher);
if (this.getWatcherCount() === 0) {
(_a = this.caCertificateProvider) === null || _a === void 0 ? void 0 : _a.removeCaCertificateListener(this.caCertificateUpdateListener);
this.identityCertificateProvider.removeIdentityCertificateListener(this.identityCertificateUpdateListener);
}
}
_equals(other) {
if (this === other) {
return true;
}
if (!(other instanceof CertificateProviderServerCredentials)) {
return false;
}
return (this.caCertificateProvider === other.caCertificateProvider &&
this.identityCertificateProvider === other.identityCertificateProvider &&
this.requireClientCertificate === other.requireClientCertificate);
}
calculateSecureContextOptions() {
var _a;
if (this.latestIdentityUpdate === null) {
return null;
}
if (this.caCertificateProvider !== null && this.latestCaUpdate === null) {
return null;
}
return {
ca: (_a = this.latestCaUpdate) === null || _a === void 0 ? void 0 : _a.caCertificate,
cert: [this.latestIdentityUpdate.certificate],
key: [this.latestIdentityUpdate.privateKey],
};
}
finalizeUpdate() {
const secureContextOptions = this.calculateSecureContextOptions();
this.updateSecureContextOptions(secureContextOptions);
}
handleCaCertificateUpdate(update) {
this.latestCaUpdate = update;
this.finalizeUpdate();
}
handleIdentityCertitificateUpdate(update) {
this.latestIdentityUpdate = update;
this.finalizeUpdate();
}
}
function createCertificateProviderServerCredentials(caCertificateProvider, identityCertificateProvider, requireClientCertificate) {
return new CertificateProviderServerCredentials(caCertificateProvider, identityCertificateProvider, requireClientCertificate);
}
class InterceptorServerCredentials extends ServerCredentials {
constructor(childCredentials, interceptors) {
super({});
this.childCredentials = childCredentials;
this.interceptors = interceptors;
}
_isSecure() {
return this.childCredentials._isSecure();
}
_equals(other) {
if (!(other instanceof InterceptorServerCredentials)) {
return false;
}
if (!(this.childCredentials._equals(other.childCredentials))) {
return false;
}
if (this.interceptors.length !== other.interceptors.length) {
return false;
}
for (let i = 0; i < this.interceptors.length; i++) {
if (this.interceptors[i] !== other.interceptors[i]) {
return false;
}
}
return true;
}
_getInterceptors() {
return this.interceptors;
}
_addWatcher(watcher) {
this.childCredentials._addWatcher(watcher);
}
_removeWatcher(watcher) {
this.childCredentials._removeWatcher(watcher);
}
_getConstructorOptions() {
return this.childCredentials._getConstructorOptions();
}
_getSecureContextOptions() {
return this.childCredentials._getSecureContextOptions();
}
}
function createServerCredentialsWithInterceptors(credentials, interceptors) {
return new InterceptorServerCredentials(credentials, interceptors);
}
//# sourceMappingURL=server-credentials.js.map

View File

@@ -0,0 +1,817 @@
"use strict";
/*
* Copyright 2024 gRPC authors.
*
* Licensed 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.BaseServerInterceptingCall = exports.ServerInterceptingCall = exports.ResponderBuilder = exports.ServerListenerBuilder = void 0;
exports.isInterceptingServerListener = isInterceptingServerListener;
exports.getServerInterceptingCall = getServerInterceptingCall;
const metadata_1 = require("./metadata");
const constants_1 = require("./constants");
const http2 = require("http2");
const error_1 = require("./error");
const zlib = require("zlib");
const stream_decoder_1 = require("./stream-decoder");
const logging = require("./logging");
const tls_1 = require("tls");
const orca_1 = require("./orca");
const TRACER_NAME = 'server_call';
function trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, text);
}
class ServerListenerBuilder {
constructor() {
this.metadata = undefined;
this.message = undefined;
this.halfClose = undefined;
this.cancel = undefined;
}
withOnReceiveMetadata(onReceiveMetadata) {
this.metadata = onReceiveMetadata;
return this;
}
withOnReceiveMessage(onReceiveMessage) {
this.message = onReceiveMessage;
return this;
}
withOnReceiveHalfClose(onReceiveHalfClose) {
this.halfClose = onReceiveHalfClose;
return this;
}
withOnCancel(onCancel) {
this.cancel = onCancel;
return this;
}
build() {
return {
onReceiveMetadata: this.metadata,
onReceiveMessage: this.message,
onReceiveHalfClose: this.halfClose,
onCancel: this.cancel,
};
}
}
exports.ServerListenerBuilder = ServerListenerBuilder;
function isInterceptingServerListener(listener) {
return (listener.onReceiveMetadata !== undefined &&
listener.onReceiveMetadata.length === 1);
}
class InterceptingServerListenerImpl {
constructor(listener, nextListener) {
this.listener = listener;
this.nextListener = nextListener;
/**
* Once the call is cancelled, ignore all other events.
*/
this.cancelled = false;
this.processingMetadata = false;
this.hasPendingMessage = false;
this.pendingMessage = null;
this.processingMessage = false;
this.hasPendingHalfClose = false;
}
processPendingMessage() {
if (this.hasPendingMessage) {
this.nextListener.onReceiveMessage(this.pendingMessage);
this.pendingMessage = null;
this.hasPendingMessage = false;
}
}
processPendingHalfClose() {
if (this.hasPendingHalfClose) {
this.nextListener.onReceiveHalfClose();
this.hasPendingHalfClose = false;
}
}
onReceiveMetadata(metadata) {
if (this.cancelled) {
return;
}
this.processingMetadata = true;
this.listener.onReceiveMetadata(metadata, interceptedMetadata => {
this.processingMetadata = false;
if (this.cancelled) {
return;
}
this.nextListener.onReceiveMetadata(interceptedMetadata);
this.processPendingMessage();
this.processPendingHalfClose();
});
}
onReceiveMessage(message) {
if (this.cancelled) {
return;
}
this.processingMessage = true;
this.listener.onReceiveMessage(message, msg => {
this.processingMessage = false;
if (this.cancelled) {
return;
}
if (this.processingMetadata) {
this.pendingMessage = msg;
this.hasPendingMessage = true;
}
else {
this.nextListener.onReceiveMessage(msg);
this.processPendingHalfClose();
}
});
}
onReceiveHalfClose() {
if (this.cancelled) {
return;
}
this.listener.onReceiveHalfClose(() => {
if (this.cancelled) {
return;
}
if (this.processingMetadata || this.processingMessage) {
this.hasPendingHalfClose = true;
}
else {
this.nextListener.onReceiveHalfClose();
}
});
}
onCancel() {
this.cancelled = true;
this.listener.onCancel();
this.nextListener.onCancel();
}
}
class ResponderBuilder {
constructor() {
this.start = undefined;
this.metadata = undefined;
this.message = undefined;
this.status = undefined;
}
withStart(start) {
this.start = start;
return this;
}
withSendMetadata(sendMetadata) {
this.metadata = sendMetadata;
return this;
}
withSendMessage(sendMessage) {
this.message = sendMessage;
return this;
}
withSendStatus(sendStatus) {
this.status = sendStatus;
return this;
}
build() {
return {
start: this.start,
sendMetadata: this.metadata,
sendMessage: this.message,
sendStatus: this.status,
};
}
}
exports.ResponderBuilder = ResponderBuilder;
const defaultServerListener = {
onReceiveMetadata: (metadata, next) => {
next(metadata);
},
onReceiveMessage: (message, next) => {
next(message);
},
onReceiveHalfClose: next => {
next();
},
onCancel: () => { },
};
const defaultResponder = {
start: next => {
next();
},
sendMetadata: (metadata, next) => {
next(metadata);
},
sendMessage: (message, next) => {
next(message);
},
sendStatus: (status, next) => {
next(status);
},
};
class ServerInterceptingCall {
constructor(nextCall, responder) {
var _a, _b, _c, _d;
this.nextCall = nextCall;
this.processingMetadata = false;
this.sentMetadata = false;
this.processingMessage = false;
this.pendingMessage = null;
this.pendingMessageCallback = null;
this.pendingStatus = null;
this.responder = {
start: (_a = responder === null || responder === void 0 ? void 0 : responder.start) !== null && _a !== void 0 ? _a : defaultResponder.start,
sendMetadata: (_b = responder === null || responder === void 0 ? void 0 : responder.sendMetadata) !== null && _b !== void 0 ? _b : defaultResponder.sendMetadata,
sendMessage: (_c = responder === null || responder === void 0 ? void 0 : responder.sendMessage) !== null && _c !== void 0 ? _c : defaultResponder.sendMessage,
sendStatus: (_d = responder === null || responder === void 0 ? void 0 : responder.sendStatus) !== null && _d !== void 0 ? _d : defaultResponder.sendStatus,
};
}
processPendingMessage() {
if (this.pendingMessageCallback) {
this.nextCall.sendMessage(this.pendingMessage, this.pendingMessageCallback);
this.pendingMessage = null;
this.pendingMessageCallback = null;
}
}
processPendingStatus() {
if (this.pendingStatus) {
this.nextCall.sendStatus(this.pendingStatus);
this.pendingStatus = null;
}
}
start(listener) {
this.responder.start(interceptedListener => {
var _a, _b, _c, _d;
const fullInterceptedListener = {
onReceiveMetadata: (_a = interceptedListener === null || interceptedListener === void 0 ? void 0 : interceptedListener.onReceiveMetadata) !== null && _a !== void 0 ? _a : defaultServerListener.onReceiveMetadata,
onReceiveMessage: (_b = interceptedListener === null || interceptedListener === void 0 ? void 0 : interceptedListener.onReceiveMessage) !== null && _b !== void 0 ? _b : defaultServerListener.onReceiveMessage,
onReceiveHalfClose: (_c = interceptedListener === null || interceptedListener === void 0 ? void 0 : interceptedListener.onReceiveHalfClose) !== null && _c !== void 0 ? _c : defaultServerListener.onReceiveHalfClose,
onCancel: (_d = interceptedListener === null || interceptedListener === void 0 ? void 0 : interceptedListener.onCancel) !== null && _d !== void 0 ? _d : defaultServerListener.onCancel,
};
const finalInterceptingListener = new InterceptingServerListenerImpl(fullInterceptedListener, listener);
this.nextCall.start(finalInterceptingListener);
});
}
sendMetadata(metadata) {
this.processingMetadata = true;
this.sentMetadata = true;
this.responder.sendMetadata(metadata, interceptedMetadata => {
this.processingMetadata = false;
this.nextCall.sendMetadata(interceptedMetadata);
this.processPendingMessage();
this.processPendingStatus();
});
}
sendMessage(message, callback) {
this.processingMessage = true;
if (!this.sentMetadata) {
this.sendMetadata(new metadata_1.Metadata());
}
this.responder.sendMessage(message, interceptedMessage => {
this.processingMessage = false;
if (this.processingMetadata) {
this.pendingMessage = interceptedMessage;
this.pendingMessageCallback = callback;
}
else {
this.nextCall.sendMessage(interceptedMessage, callback);
}
});
}
sendStatus(status) {
this.responder.sendStatus(status, interceptedStatus => {
if (this.processingMetadata || this.processingMessage) {
this.pendingStatus = interceptedStatus;
}
else {
this.nextCall.sendStatus(interceptedStatus);
}
});
}
startRead() {
this.nextCall.startRead();
}
getPeer() {
return this.nextCall.getPeer();
}
getDeadline() {
return this.nextCall.getDeadline();
}
getHost() {
return this.nextCall.getHost();
}
getAuthContext() {
return this.nextCall.getAuthContext();
}
getConnectionInfo() {
return this.nextCall.getConnectionInfo();
}
getMetricsRecorder() {
return this.nextCall.getMetricsRecorder();
}
}
exports.ServerInterceptingCall = ServerInterceptingCall;
const GRPC_ACCEPT_ENCODING_HEADER = 'grpc-accept-encoding';
const GRPC_ENCODING_HEADER = 'grpc-encoding';
const GRPC_MESSAGE_HEADER = 'grpc-message';
const GRPC_STATUS_HEADER = 'grpc-status';
const GRPC_TIMEOUT_HEADER = 'grpc-timeout';
const DEADLINE_REGEX = /(\d{1,8})\s*([HMSmun])/;
const deadlineUnitsToMs = {
H: 3600000,
M: 60000,
S: 1000,
m: 1,
u: 0.001,
n: 0.000001,
};
const defaultCompressionHeaders = {
// TODO(cjihrig): Remove these encoding headers from the default response
// once compression is integrated.
[GRPC_ACCEPT_ENCODING_HEADER]: 'identity,deflate,gzip',
[GRPC_ENCODING_HEADER]: 'identity',
};
const defaultResponseHeaders = {
[http2.constants.HTTP2_HEADER_STATUS]: http2.constants.HTTP_STATUS_OK,
[http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'application/grpc+proto',
};
const defaultResponseOptions = {
waitForTrailers: true,
};
class BaseServerInterceptingCall {
constructor(stream, headers, callEventTracker, handler, options) {
var _a, _b;
this.stream = stream;
this.callEventTracker = callEventTracker;
this.handler = handler;
this.listener = null;
this.deadlineTimer = null;
this.deadline = Infinity;
this.maxSendMessageSize = constants_1.DEFAULT_MAX_SEND_MESSAGE_LENGTH;
this.maxReceiveMessageSize = constants_1.DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH;
this.cancelled = false;
this.metadataSent = false;
this.wantTrailers = false;
this.cancelNotified = false;
this.incomingEncoding = 'identity';
this.readQueue = [];
this.isReadPending = false;
this.receivedHalfClose = false;
this.streamEnded = false;
this.metricsRecorder = new orca_1.PerRequestMetricRecorder();
this.stream.once('error', (err) => {
/* We need an error handler to avoid uncaught error event exceptions, but
* there is nothing we can reasonably do here. Any error event should
* have a corresponding close event, which handles emitting the cancelled
* event. And the stream is now in a bad state, so we can't reasonably
* expect to be able to send an error over it. */
});
this.stream.once('close', () => {
var _a;
trace('Request to method ' +
((_a = this.handler) === null || _a === void 0 ? void 0 : _a.path) +
' stream closed with rstCode ' +
this.stream.rstCode);
if (this.callEventTracker && !this.streamEnded) {
this.streamEnded = true;
this.callEventTracker.onStreamEnd(false);
this.callEventTracker.onCallEnd({
code: constants_1.Status.CANCELLED,
details: 'Stream closed before sending status',
metadata: null,
});
}
this.notifyOnCancel();
});
this.stream.on('data', (data) => {
this.handleDataFrame(data);
});
this.stream.pause();
this.stream.on('end', () => {
this.handleEndEvent();
});
if ('grpc.max_send_message_length' in options) {
this.maxSendMessageSize = options['grpc.max_send_message_length'];
}
if ('grpc.max_receive_message_length' in options) {
this.maxReceiveMessageSize = options['grpc.max_receive_message_length'];
}
this.host = (_a = headers[':authority']) !== null && _a !== void 0 ? _a : headers.host;
this.decoder = new stream_decoder_1.StreamDecoder(this.maxReceiveMessageSize);
const metadata = metadata_1.Metadata.fromHttp2Headers(headers);
if (logging.isTracerEnabled(TRACER_NAME)) {
trace('Request to ' +
this.handler.path +
' received headers ' +
JSON.stringify(metadata.toJSON()));
}
const timeoutHeader = metadata.get(GRPC_TIMEOUT_HEADER);
if (timeoutHeader.length > 0) {
this.handleTimeoutHeader(timeoutHeader[0]);
}
const encodingHeader = metadata.get(GRPC_ENCODING_HEADER);
if (encodingHeader.length > 0) {
this.incomingEncoding = encodingHeader[0];
}
// Remove several headers that should not be propagated to the application
metadata.remove(GRPC_TIMEOUT_HEADER);
metadata.remove(GRPC_ENCODING_HEADER);
metadata.remove(GRPC_ACCEPT_ENCODING_HEADER);
metadata.remove(http2.constants.HTTP2_HEADER_ACCEPT_ENCODING);
metadata.remove(http2.constants.HTTP2_HEADER_TE);
metadata.remove(http2.constants.HTTP2_HEADER_CONTENT_TYPE);
this.metadata = metadata;
const socket = (_b = stream.session) === null || _b === void 0 ? void 0 : _b.socket;
this.connectionInfo = {
localAddress: socket === null || socket === void 0 ? void 0 : socket.localAddress,
localPort: socket === null || socket === void 0 ? void 0 : socket.localPort,
remoteAddress: socket === null || socket === void 0 ? void 0 : socket.remoteAddress,
remotePort: socket === null || socket === void 0 ? void 0 : socket.remotePort
};
this.shouldSendMetrics = !!options['grpc.server_call_metric_recording'];
}
handleTimeoutHeader(timeoutHeader) {
const match = timeoutHeader.toString().match(DEADLINE_REGEX);
if (match === null) {
const status = {
code: constants_1.Status.INTERNAL,
details: `Invalid ${GRPC_TIMEOUT_HEADER} value "${timeoutHeader}"`,
metadata: null,
};
// Wait for the constructor to complete before sending the error.
process.nextTick(() => {
this.sendStatus(status);
});
return;
}
const timeout = (+match[1] * deadlineUnitsToMs[match[2]]) | 0;
const now = new Date();
this.deadline = now.setMilliseconds(now.getMilliseconds() + timeout);
this.deadlineTimer = setTimeout(() => {
const status = {
code: constants_1.Status.DEADLINE_EXCEEDED,
details: 'Deadline exceeded',
metadata: null,
};
this.sendStatus(status);
}, timeout);
}
checkCancelled() {
/* In some cases the stream can become destroyed before the close event
* fires. That creates a race condition that this check works around */
if (!this.cancelled && (this.stream.destroyed || this.stream.closed)) {
this.notifyOnCancel();
this.cancelled = true;
}
return this.cancelled;
}
notifyOnCancel() {
if (this.cancelNotified) {
return;
}
this.cancelNotified = true;
this.cancelled = true;
process.nextTick(() => {
var _a;
(_a = this.listener) === null || _a === void 0 ? void 0 : _a.onCancel();
});
if (this.deadlineTimer) {
clearTimeout(this.deadlineTimer);
}
// Flush incoming data frames
this.stream.resume();
}
/**
* A server handler can start sending messages without explicitly sending
* metadata. In that case, we need to send headers before sending any
* messages. This function does that if necessary.
*/
maybeSendMetadata() {
if (!this.metadataSent) {
this.sendMetadata(new metadata_1.Metadata());
}
}
/**
* Serialize a message to a length-delimited byte string.
* @param value
* @returns
*/
serializeMessage(value) {
const messageBuffer = this.handler.serialize(value);
const byteLength = messageBuffer.byteLength;
const output = Buffer.allocUnsafe(byteLength + 5);
/* Note: response compression is currently not supported, so this
* compressed bit is always 0. */
output.writeUInt8(0, 0);
output.writeUInt32BE(byteLength, 1);
messageBuffer.copy(output, 5);
return output;
}
decompressMessage(message, encoding) {
const messageContents = message.subarray(5);
if (encoding === 'identity') {
return messageContents;
}
else if (encoding === 'deflate' || encoding === 'gzip') {
let decompresser;
if (encoding === 'deflate') {
decompresser = zlib.createInflate();
}
else {
decompresser = zlib.createGunzip();
}
return new Promise((resolve, reject) => {
let totalLength = 0;
const messageParts = [];
decompresser.on('data', (chunk) => {
messageParts.push(chunk);
totalLength += chunk.byteLength;
if (this.maxReceiveMessageSize !== -1 && totalLength > this.maxReceiveMessageSize) {
decompresser.destroy();
reject({
code: constants_1.Status.RESOURCE_EXHAUSTED,
details: `Received message that decompresses to a size larger than ${this.maxReceiveMessageSize}`
});
}
});
decompresser.on('end', () => {
resolve(Buffer.concat(messageParts));
});
decompresser.write(messageContents);
decompresser.end();
});
}
else {
return Promise.reject({
code: constants_1.Status.UNIMPLEMENTED,
details: `Received message compressed with unsupported encoding "${encoding}"`,
});
}
}
async decompressAndMaybePush(queueEntry) {
if (queueEntry.type !== 'COMPRESSED') {
throw new Error(`Invalid queue entry type: ${queueEntry.type}`);
}
const compressed = queueEntry.compressedMessage.readUInt8(0) === 1;
const compressedMessageEncoding = compressed
? this.incomingEncoding
: 'identity';
let decompressedMessage;
try {
decompressedMessage = await this.decompressMessage(queueEntry.compressedMessage, compressedMessageEncoding);
}
catch (err) {
this.sendStatus(err);
return;
}
try {
queueEntry.parsedMessage = this.handler.deserialize(decompressedMessage);
}
catch (err) {
this.sendStatus({
code: constants_1.Status.INTERNAL,
details: `Error deserializing request: ${err.message}`,
});
return;
}
queueEntry.type = 'READABLE';
this.maybePushNextMessage();
}
maybePushNextMessage() {
if (this.listener &&
this.isReadPending &&
this.readQueue.length > 0 &&
this.readQueue[0].type !== 'COMPRESSED') {
this.isReadPending = false;
const nextQueueEntry = this.readQueue.shift();
if (nextQueueEntry.type === 'READABLE') {
this.listener.onReceiveMessage(nextQueueEntry.parsedMessage);
}
else {
// nextQueueEntry.type === 'HALF_CLOSE'
this.listener.onReceiveHalfClose();
}
}
}
handleDataFrame(data) {
var _a;
if (this.checkCancelled()) {
return;
}
trace('Request to ' +
this.handler.path +
' received data frame of size ' +
data.length);
let rawMessages;
try {
rawMessages = this.decoder.write(data);
}
catch (e) {
this.sendStatus({ code: constants_1.Status.RESOURCE_EXHAUSTED, details: e.message });
return;
}
for (const messageBytes of rawMessages) {
this.stream.pause();
const queueEntry = {
type: 'COMPRESSED',
compressedMessage: messageBytes,
parsedMessage: null,
};
this.readQueue.push(queueEntry);
this.decompressAndMaybePush(queueEntry);
(_a = this.callEventTracker) === null || _a === void 0 ? void 0 : _a.addMessageReceived();
}
}
handleEndEvent() {
this.readQueue.push({
type: 'HALF_CLOSE',
compressedMessage: null,
parsedMessage: null,
});
this.receivedHalfClose = true;
this.maybePushNextMessage();
}
start(listener) {
trace('Request to ' + this.handler.path + ' start called');
if (this.checkCancelled()) {
return;
}
this.listener = listener;
listener.onReceiveMetadata(this.metadata);
}
sendMetadata(metadata) {
if (this.checkCancelled()) {
return;
}
if (this.metadataSent) {
return;
}
this.metadataSent = true;
const custom = metadata ? metadata.toHttp2Headers() : null;
const headers = Object.assign(Object.assign(Object.assign({}, defaultResponseHeaders), defaultCompressionHeaders), custom);
this.stream.respond(headers, defaultResponseOptions);
}
sendMessage(message, callback) {
if (this.checkCancelled()) {
return;
}
let response;
try {
response = this.serializeMessage(message);
}
catch (e) {
this.sendStatus({
code: constants_1.Status.INTERNAL,
details: `Error serializing response: ${(0, error_1.getErrorMessage)(e)}`,
metadata: null,
});
return;
}
if (this.maxSendMessageSize !== -1 &&
response.length - 5 > this.maxSendMessageSize) {
this.sendStatus({
code: constants_1.Status.RESOURCE_EXHAUSTED,
details: `Sent message larger than max (${response.length} vs. ${this.maxSendMessageSize})`,
metadata: null,
});
return;
}
this.maybeSendMetadata();
trace('Request to ' +
this.handler.path +
' sent data frame of size ' +
response.length);
this.stream.write(response, error => {
var _a;
if (error) {
this.sendStatus({
code: constants_1.Status.INTERNAL,
details: `Error writing message: ${(0, error_1.getErrorMessage)(error)}`,
metadata: null,
});
return;
}
(_a = this.callEventTracker) === null || _a === void 0 ? void 0 : _a.addMessageSent();
callback();
});
}
sendStatus(status) {
var _a, _b, _c;
if (this.checkCancelled()) {
return;
}
trace('Request to method ' +
((_a = this.handler) === null || _a === void 0 ? void 0 : _a.path) +
' ended with status code: ' +
constants_1.Status[status.code] +
' details: ' +
status.details);
const statusMetadata = (_c = (_b = status.metadata) === null || _b === void 0 ? void 0 : _b.clone()) !== null && _c !== void 0 ? _c : new metadata_1.Metadata();
if (this.shouldSendMetrics) {
statusMetadata.set(orca_1.GRPC_METRICS_HEADER, this.metricsRecorder.serialize());
}
if (this.metadataSent) {
if (!this.wantTrailers) {
this.wantTrailers = true;
this.stream.once('wantTrailers', () => {
if (this.callEventTracker && !this.streamEnded) {
this.streamEnded = true;
this.callEventTracker.onStreamEnd(true);
this.callEventTracker.onCallEnd(status);
}
const trailersToSend = Object.assign({ [GRPC_STATUS_HEADER]: status.code, [GRPC_MESSAGE_HEADER]: encodeURI(status.details) }, statusMetadata.toHttp2Headers());
this.stream.sendTrailers(trailersToSend);
this.notifyOnCancel();
});
this.stream.end();
}
else {
this.notifyOnCancel();
}
}
else {
if (this.callEventTracker && !this.streamEnded) {
this.streamEnded = true;
this.callEventTracker.onStreamEnd(true);
this.callEventTracker.onCallEnd(status);
}
// Trailers-only response
const trailersToSend = Object.assign(Object.assign({ [GRPC_STATUS_HEADER]: status.code, [GRPC_MESSAGE_HEADER]: encodeURI(status.details) }, defaultResponseHeaders), statusMetadata.toHttp2Headers());
this.stream.respond(trailersToSend, { endStream: true });
this.notifyOnCancel();
}
}
startRead() {
trace('Request to ' + this.handler.path + ' startRead called');
if (this.checkCancelled()) {
return;
}
this.isReadPending = true;
if (this.readQueue.length === 0) {
if (!this.receivedHalfClose) {
this.stream.resume();
}
}
else {
this.maybePushNextMessage();
}
}
getPeer() {
var _a;
const socket = (_a = this.stream.session) === null || _a === void 0 ? void 0 : _a.socket;
if (socket === null || socket === void 0 ? void 0 : socket.remoteAddress) {
if (socket.remotePort) {
return `${socket.remoteAddress}:${socket.remotePort}`;
}
else {
return socket.remoteAddress;
}
}
else {
return 'unknown';
}
}
getDeadline() {
return this.deadline;
}
getHost() {
return this.host;
}
getAuthContext() {
var _a;
if (((_a = this.stream.session) === null || _a === void 0 ? void 0 : _a.socket) instanceof tls_1.TLSSocket) {
const peerCertificate = this.stream.session.socket.getPeerCertificate();
return {
transportSecurityType: 'ssl',
sslPeerCertificate: peerCertificate.raw ? peerCertificate : undefined
};
}
else {
return {};
}
}
getConnectionInfo() {
return this.connectionInfo;
}
getMetricsRecorder() {
return this.metricsRecorder;
}
}
exports.BaseServerInterceptingCall = BaseServerInterceptingCall;
function getServerInterceptingCall(interceptors, stream, headers, callEventTracker, handler, options) {
const methodDefinition = {
path: handler.path,
requestStream: handler.type === 'clientStream' || handler.type === 'bidi',
responseStream: handler.type === 'serverStream' || handler.type === 'bidi',
requestDeserialize: handler.deserialize,
responseSerialize: handler.serialize,
};
const baseCall = new BaseServerInterceptingCall(stream, headers, callEventTracker, handler, options);
return interceptors.reduce((call, interceptor) => {
return interceptor(methodDefinition, call);
}, baseCall);
}
//# sourceMappingURL=server-interceptors.js.map

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,430 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.validateRetryThrottling = validateRetryThrottling;
exports.validateServiceConfig = validateServiceConfig;
exports.extractAndSelectServiceConfig = extractAndSelectServiceConfig;
/* This file implements gRFC A2 and the service config spec:
* https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md
* https://github.com/grpc/grpc/blob/master/doc/service_config.md. Each
* function here takes an object with unknown structure and returns its
* specific object type if the input has the right structure, and throws an
* error otherwise. */
/* The any type is purposely used here. All functions validate their input at
* runtime */
/* eslint-disable @typescript-eslint/no-explicit-any */
const os = require("os");
const constants_1 = require("./constants");
/**
* Recognizes a number with up to 9 digits after the decimal point, followed by
* an "s", representing a number of seconds.
*/
const DURATION_REGEX = /^\d+(\.\d{1,9})?s$/;
/**
* Client language name used for determining whether this client matches a
* `ServiceConfigCanaryConfig`'s `clientLanguage` list.
*/
const CLIENT_LANGUAGE_STRING = 'node';
function validateName(obj) {
// In this context, and unset field and '' are considered the same
if ('service' in obj && obj.service !== '') {
if (typeof obj.service !== 'string') {
throw new Error(`Invalid method config name: invalid service: expected type string, got ${typeof obj.service}`);
}
if ('method' in obj && obj.method !== '') {
if (typeof obj.method !== 'string') {
throw new Error(`Invalid method config name: invalid method: expected type string, got ${typeof obj.service}`);
}
return {
service: obj.service,
method: obj.method,
};
}
else {
return {
service: obj.service,
};
}
}
else {
if ('method' in obj && obj.method !== undefined) {
throw new Error(`Invalid method config name: method set with empty or unset service`);
}
return {};
}
}
function validateRetryPolicy(obj) {
if (!('maxAttempts' in obj) ||
!Number.isInteger(obj.maxAttempts) ||
obj.maxAttempts < 2) {
throw new Error('Invalid method config retry policy: maxAttempts must be an integer at least 2');
}
if (!('initialBackoff' in obj) ||
typeof obj.initialBackoff !== 'string' ||
!DURATION_REGEX.test(obj.initialBackoff)) {
throw new Error('Invalid method config retry policy: initialBackoff must be a string consisting of a positive integer or decimal followed by s');
}
if (!('maxBackoff' in obj) ||
typeof obj.maxBackoff !== 'string' ||
!DURATION_REGEX.test(obj.maxBackoff)) {
throw new Error('Invalid method config retry policy: maxBackoff must be a string consisting of a positive integer or decimal followed by s');
}
if (!('backoffMultiplier' in obj) ||
typeof obj.backoffMultiplier !== 'number' ||
obj.backoffMultiplier <= 0) {
throw new Error('Invalid method config retry policy: backoffMultiplier must be a number greater than 0');
}
if (!('retryableStatusCodes' in obj && Array.isArray(obj.retryableStatusCodes))) {
throw new Error('Invalid method config retry policy: retryableStatusCodes is required');
}
if (obj.retryableStatusCodes.length === 0) {
throw new Error('Invalid method config retry policy: retryableStatusCodes must be non-empty');
}
for (const value of obj.retryableStatusCodes) {
if (typeof value === 'number') {
if (!Object.values(constants_1.Status).includes(value)) {
throw new Error('Invalid method config retry policy: retryableStatusCodes value not in status code range');
}
}
else if (typeof value === 'string') {
if (!Object.values(constants_1.Status).includes(value.toUpperCase())) {
throw new Error('Invalid method config retry policy: retryableStatusCodes value not a status code name');
}
}
else {
throw new Error('Invalid method config retry policy: retryableStatusCodes value must be a string or number');
}
}
return {
maxAttempts: obj.maxAttempts,
initialBackoff: obj.initialBackoff,
maxBackoff: obj.maxBackoff,
backoffMultiplier: obj.backoffMultiplier,
retryableStatusCodes: obj.retryableStatusCodes,
};
}
function validateHedgingPolicy(obj) {
if (!('maxAttempts' in obj) ||
!Number.isInteger(obj.maxAttempts) ||
obj.maxAttempts < 2) {
throw new Error('Invalid method config hedging policy: maxAttempts must be an integer at least 2');
}
if ('hedgingDelay' in obj &&
(typeof obj.hedgingDelay !== 'string' ||
!DURATION_REGEX.test(obj.hedgingDelay))) {
throw new Error('Invalid method config hedging policy: hedgingDelay must be a string consisting of a positive integer followed by s');
}
if ('nonFatalStatusCodes' in obj && Array.isArray(obj.nonFatalStatusCodes)) {
for (const value of obj.nonFatalStatusCodes) {
if (typeof value === 'number') {
if (!Object.values(constants_1.Status).includes(value)) {
throw new Error('Invalid method config hedging policy: nonFatalStatusCodes value not in status code range');
}
}
else if (typeof value === 'string') {
if (!Object.values(constants_1.Status).includes(value.toUpperCase())) {
throw new Error('Invalid method config hedging policy: nonFatalStatusCodes value not a status code name');
}
}
else {
throw new Error('Invalid method config hedging policy: nonFatalStatusCodes value must be a string or number');
}
}
}
const result = {
maxAttempts: obj.maxAttempts,
};
if (obj.hedgingDelay) {
result.hedgingDelay = obj.hedgingDelay;
}
if (obj.nonFatalStatusCodes) {
result.nonFatalStatusCodes = obj.nonFatalStatusCodes;
}
return result;
}
function validateMethodConfig(obj) {
var _a;
const result = {
name: [],
};
if (!('name' in obj) || !Array.isArray(obj.name)) {
throw new Error('Invalid method config: invalid name array');
}
for (const name of obj.name) {
result.name.push(validateName(name));
}
if ('waitForReady' in obj) {
if (typeof obj.waitForReady !== 'boolean') {
throw new Error('Invalid method config: invalid waitForReady');
}
result.waitForReady = obj.waitForReady;
}
if ('timeout' in obj) {
if (typeof obj.timeout === 'object') {
if (!('seconds' in obj.timeout) ||
!(typeof obj.timeout.seconds === 'number')) {
throw new Error('Invalid method config: invalid timeout.seconds');
}
if (!('nanos' in obj.timeout) ||
!(typeof obj.timeout.nanos === 'number')) {
throw new Error('Invalid method config: invalid timeout.nanos');
}
result.timeout = obj.timeout;
}
else if (typeof obj.timeout === 'string' &&
DURATION_REGEX.test(obj.timeout)) {
const timeoutParts = obj.timeout
.substring(0, obj.timeout.length - 1)
.split('.');
result.timeout = {
seconds: timeoutParts[0] | 0,
nanos: ((_a = timeoutParts[1]) !== null && _a !== void 0 ? _a : 0) | 0,
};
}
else {
throw new Error('Invalid method config: invalid timeout');
}
}
if ('maxRequestBytes' in obj) {
if (typeof obj.maxRequestBytes !== 'number') {
throw new Error('Invalid method config: invalid maxRequestBytes');
}
result.maxRequestBytes = obj.maxRequestBytes;
}
if ('maxResponseBytes' in obj) {
if (typeof obj.maxResponseBytes !== 'number') {
throw new Error('Invalid method config: invalid maxRequestBytes');
}
result.maxResponseBytes = obj.maxResponseBytes;
}
if ('retryPolicy' in obj) {
if ('hedgingPolicy' in obj) {
throw new Error('Invalid method config: retryPolicy and hedgingPolicy cannot both be specified');
}
else {
result.retryPolicy = validateRetryPolicy(obj.retryPolicy);
}
}
else if ('hedgingPolicy' in obj) {
result.hedgingPolicy = validateHedgingPolicy(obj.hedgingPolicy);
}
return result;
}
function validateRetryThrottling(obj) {
if (!('maxTokens' in obj) ||
typeof obj.maxTokens !== 'number' ||
obj.maxTokens <= 0 ||
obj.maxTokens > 1000) {
throw new Error('Invalid retryThrottling: maxTokens must be a number in (0, 1000]');
}
if (!('tokenRatio' in obj) ||
typeof obj.tokenRatio !== 'number' ||
obj.tokenRatio <= 0) {
throw new Error('Invalid retryThrottling: tokenRatio must be a number greater than 0');
}
return {
maxTokens: +obj.maxTokens.toFixed(3),
tokenRatio: +obj.tokenRatio.toFixed(3),
};
}
function validateLoadBalancingConfig(obj) {
if (!(typeof obj === 'object' && obj !== null)) {
throw new Error(`Invalid loadBalancingConfig: unexpected type ${typeof obj}`);
}
const keys = Object.keys(obj);
if (keys.length > 1) {
throw new Error(`Invalid loadBalancingConfig: unexpected multiple keys ${keys}`);
}
if (keys.length === 0) {
throw new Error('Invalid loadBalancingConfig: load balancing policy name required');
}
return {
[keys[0]]: obj[keys[0]],
};
}
function validateServiceConfig(obj) {
const result = {
loadBalancingConfig: [],
methodConfig: [],
};
if ('loadBalancingPolicy' in obj) {
if (typeof obj.loadBalancingPolicy === 'string') {
result.loadBalancingPolicy = obj.loadBalancingPolicy;
}
else {
throw new Error('Invalid service config: invalid loadBalancingPolicy');
}
}
if ('loadBalancingConfig' in obj) {
if (Array.isArray(obj.loadBalancingConfig)) {
for (const config of obj.loadBalancingConfig) {
result.loadBalancingConfig.push(validateLoadBalancingConfig(config));
}
}
else {
throw new Error('Invalid service config: invalid loadBalancingConfig');
}
}
if ('methodConfig' in obj) {
if (Array.isArray(obj.methodConfig)) {
for (const methodConfig of obj.methodConfig) {
result.methodConfig.push(validateMethodConfig(methodConfig));
}
}
}
if ('retryThrottling' in obj) {
result.retryThrottling = validateRetryThrottling(obj.retryThrottling);
}
// Validate method name uniqueness
const seenMethodNames = [];
for (const methodConfig of result.methodConfig) {
for (const name of methodConfig.name) {
for (const seenName of seenMethodNames) {
if (name.service === seenName.service &&
name.method === seenName.method) {
throw new Error(`Invalid service config: duplicate name ${name.service}/${name.method}`);
}
}
seenMethodNames.push(name);
}
}
return result;
}
function validateCanaryConfig(obj) {
if (!('serviceConfig' in obj)) {
throw new Error('Invalid service config choice: missing service config');
}
const result = {
serviceConfig: validateServiceConfig(obj.serviceConfig),
};
if ('clientLanguage' in obj) {
if (Array.isArray(obj.clientLanguage)) {
result.clientLanguage = [];
for (const lang of obj.clientLanguage) {
if (typeof lang === 'string') {
result.clientLanguage.push(lang);
}
else {
throw new Error('Invalid service config choice: invalid clientLanguage');
}
}
}
else {
throw new Error('Invalid service config choice: invalid clientLanguage');
}
}
if ('clientHostname' in obj) {
if (Array.isArray(obj.clientHostname)) {
result.clientHostname = [];
for (const lang of obj.clientHostname) {
if (typeof lang === 'string') {
result.clientHostname.push(lang);
}
else {
throw new Error('Invalid service config choice: invalid clientHostname');
}
}
}
else {
throw new Error('Invalid service config choice: invalid clientHostname');
}
}
if ('percentage' in obj) {
if (typeof obj.percentage === 'number' &&
0 <= obj.percentage &&
obj.percentage <= 100) {
result.percentage = obj.percentage;
}
else {
throw new Error('Invalid service config choice: invalid percentage');
}
}
// Validate that no unexpected fields are present
const allowedFields = [
'clientLanguage',
'percentage',
'clientHostname',
'serviceConfig',
];
for (const field in obj) {
if (!allowedFields.includes(field)) {
throw new Error(`Invalid service config choice: unexpected field ${field}`);
}
}
return result;
}
function validateAndSelectCanaryConfig(obj, percentage) {
if (!Array.isArray(obj)) {
throw new Error('Invalid service config list');
}
for (const config of obj) {
const validatedConfig = validateCanaryConfig(config);
/* For each field, we check if it is present, then only discard the
* config if the field value does not match the current client */
if (typeof validatedConfig.percentage === 'number' &&
percentage > validatedConfig.percentage) {
continue;
}
if (Array.isArray(validatedConfig.clientHostname)) {
let hostnameMatched = false;
for (const hostname of validatedConfig.clientHostname) {
if (hostname === os.hostname()) {
hostnameMatched = true;
}
}
if (!hostnameMatched) {
continue;
}
}
if (Array.isArray(validatedConfig.clientLanguage)) {
let languageMatched = false;
for (const language of validatedConfig.clientLanguage) {
if (language === CLIENT_LANGUAGE_STRING) {
languageMatched = true;
}
}
if (!languageMatched) {
continue;
}
}
return validatedConfig.serviceConfig;
}
throw new Error('No matching service config found');
}
/**
* Find the "grpc_config" record among the TXT records, parse its value as JSON, validate its contents,
* and select a service config with selection fields that all match this client. Most of these steps
* can fail with an error; the caller must handle any errors thrown this way.
* @param txtRecord The TXT record array that is output from a successful call to dns.resolveTxt
* @param percentage A number chosen from the range [0, 100) that is used to select which config to use
* @return The service configuration to use, given the percentage value, or null if the service config
* data has a valid format but none of the options match the current client.
*/
function extractAndSelectServiceConfig(txtRecord, percentage) {
for (const record of txtRecord) {
if (record.length > 0 && record[0].startsWith('grpc_config=')) {
/* Treat the list of strings in this record as a single string and remove
* "grpc_config=" from the beginning. The rest should be a JSON string */
const recordString = record.join('').substring('grpc_config='.length);
const recordJson = JSON.parse(recordString);
return validateAndSelectCanaryConfig(recordJson, percentage);
}
}
return null;
}
//# sourceMappingURL=service-config.js.map

View File

@@ -0,0 +1,245 @@
"use strict";
/*
* Copyright 2025 gRPC authors.
*
* Licensed 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.SingleSubchannelChannel = void 0;
const call_number_1 = require("./call-number");
const channelz_1 = require("./channelz");
const compression_filter_1 = require("./compression-filter");
const connectivity_state_1 = require("./connectivity-state");
const constants_1 = require("./constants");
const control_plane_status_1 = require("./control-plane-status");
const deadline_1 = require("./deadline");
const filter_stack_1 = require("./filter-stack");
const metadata_1 = require("./metadata");
const resolver_1 = require("./resolver");
const uri_parser_1 = require("./uri-parser");
class SubchannelCallWrapper {
constructor(subchannel, method, filterStackFactory, options, callNumber) {
var _a, _b;
this.subchannel = subchannel;
this.method = method;
this.options = options;
this.callNumber = callNumber;
this.childCall = null;
this.pendingMessage = null;
this.readPending = false;
this.halfClosePending = false;
this.pendingStatus = null;
this.readFilterPending = false;
this.writeFilterPending = false;
const splitPath = this.method.split('/');
let serviceName = '';
/* The standard path format is "/{serviceName}/{methodName}", so if we split
* by '/', the first item should be empty and the second should be the
* service name */
if (splitPath.length >= 2) {
serviceName = splitPath[1];
}
const hostname = (_b = (_a = (0, uri_parser_1.splitHostPort)(this.options.host)) === null || _a === void 0 ? void 0 : _a.host) !== null && _b !== void 0 ? _b : 'localhost';
/* Currently, call credentials are only allowed on HTTPS connections, so we
* can assume that the scheme is "https" */
this.serviceUrl = `https://${hostname}/${serviceName}`;
const timeout = (0, deadline_1.getRelativeTimeout)(options.deadline);
if (timeout !== Infinity) {
if (timeout <= 0) {
this.cancelWithStatus(constants_1.Status.DEADLINE_EXCEEDED, 'Deadline exceeded');
}
else {
setTimeout(() => {
this.cancelWithStatus(constants_1.Status.DEADLINE_EXCEEDED, 'Deadline exceeded');
}, timeout);
}
}
this.filterStack = filterStackFactory.createFilter();
}
cancelWithStatus(status, details) {
if (this.childCall) {
this.childCall.cancelWithStatus(status, details);
}
else {
this.pendingStatus = {
code: status,
details: details,
metadata: new metadata_1.Metadata()
};
}
}
getPeer() {
var _a, _b;
return (_b = (_a = this.childCall) === null || _a === void 0 ? void 0 : _a.getPeer()) !== null && _b !== void 0 ? _b : this.subchannel.getAddress();
}
async start(metadata, listener) {
if (this.pendingStatus) {
listener.onReceiveStatus(this.pendingStatus);
return;
}
if (this.subchannel.getConnectivityState() !== connectivity_state_1.ConnectivityState.READY) {
listener.onReceiveStatus({
code: constants_1.Status.UNAVAILABLE,
details: 'Subchannel not ready',
metadata: new metadata_1.Metadata()
});
return;
}
const filteredMetadata = await this.filterStack.sendMetadata(Promise.resolve(metadata));
let credsMetadata;
try {
credsMetadata = await this.subchannel.getCallCredentials()
.generateMetadata({ method_name: this.method, service_url: this.serviceUrl });
}
catch (e) {
const error = e;
const { code, details } = (0, control_plane_status_1.restrictControlPlaneStatusCode)(typeof error.code === 'number' ? error.code : constants_1.Status.UNKNOWN, `Getting metadata from plugin failed with error: ${error.message}`);
listener.onReceiveStatus({
code: code,
details: details,
metadata: new metadata_1.Metadata(),
});
return;
}
credsMetadata.merge(filteredMetadata);
const childListener = {
onReceiveMetadata: async (metadata) => {
listener.onReceiveMetadata(await this.filterStack.receiveMetadata(metadata));
},
onReceiveMessage: async (message) => {
this.readFilterPending = true;
const filteredMessage = await this.filterStack.receiveMessage(message);
this.readFilterPending = false;
listener.onReceiveMessage(filteredMessage);
if (this.pendingStatus) {
listener.onReceiveStatus(this.pendingStatus);
}
},
onReceiveStatus: async (status) => {
const filteredStatus = await this.filterStack.receiveTrailers(status);
if (this.readFilterPending) {
this.pendingStatus = filteredStatus;
}
else {
listener.onReceiveStatus(filteredStatus);
}
}
};
this.childCall = this.subchannel.createCall(credsMetadata, this.options.host, this.method, childListener);
if (this.readPending) {
this.childCall.startRead();
}
if (this.pendingMessage) {
this.childCall.sendMessageWithContext(this.pendingMessage.context, this.pendingMessage.message);
}
if (this.halfClosePending && !this.writeFilterPending) {
this.childCall.halfClose();
}
}
async sendMessageWithContext(context, message) {
this.writeFilterPending = true;
const filteredMessage = await this.filterStack.sendMessage(Promise.resolve({ message: message, flags: context.flags }));
this.writeFilterPending = false;
if (this.childCall) {
this.childCall.sendMessageWithContext(context, filteredMessage.message);
if (this.halfClosePending) {
this.childCall.halfClose();
}
}
else {
this.pendingMessage = { context, message: filteredMessage.message };
}
}
startRead() {
if (this.childCall) {
this.childCall.startRead();
}
else {
this.readPending = true;
}
}
halfClose() {
if (this.childCall && !this.writeFilterPending) {
this.childCall.halfClose();
}
else {
this.halfClosePending = true;
}
}
getCallNumber() {
return this.callNumber;
}
setCredentials(credentials) {
throw new Error("Method not implemented.");
}
getAuthContext() {
if (this.childCall) {
return this.childCall.getAuthContext();
}
else {
return null;
}
}
}
class SingleSubchannelChannel {
constructor(subchannel, target, options) {
this.subchannel = subchannel;
this.target = target;
this.channelzEnabled = false;
this.channelzTrace = new channelz_1.ChannelzTrace();
this.callTracker = new channelz_1.ChannelzCallTracker();
this.childrenTracker = new channelz_1.ChannelzChildrenTracker();
this.channelzEnabled = options['grpc.enable_channelz'] !== 0;
this.channelzRef = (0, channelz_1.registerChannelzChannel)((0, uri_parser_1.uriToString)(target), () => ({
target: `${(0, uri_parser_1.uriToString)(target)} (${subchannel.getAddress()})`,
state: this.subchannel.getConnectivityState(),
trace: this.channelzTrace,
callTracker: this.callTracker,
children: this.childrenTracker.getChildLists()
}), this.channelzEnabled);
if (this.channelzEnabled) {
this.childrenTracker.refChild(subchannel.getChannelzRef());
}
this.filterStackFactory = new filter_stack_1.FilterStackFactory([new compression_filter_1.CompressionFilterFactory(this, options)]);
}
close() {
if (this.channelzEnabled) {
this.childrenTracker.unrefChild(this.subchannel.getChannelzRef());
}
(0, channelz_1.unregisterChannelzRef)(this.channelzRef);
}
getTarget() {
return (0, uri_parser_1.uriToString)(this.target);
}
getConnectivityState(tryToConnect) {
throw new Error("Method not implemented.");
}
watchConnectivityState(currentState, deadline, callback) {
throw new Error("Method not implemented.");
}
getChannelzRef() {
return this.channelzRef;
}
createCall(method, deadline) {
const callOptions = {
deadline: deadline,
host: (0, resolver_1.getDefaultAuthority)(this.target),
flags: constants_1.Propagate.DEFAULTS,
parentCall: null
};
return new SubchannelCallWrapper(this.subchannel, method, this.filterStackFactory, callOptions, (0, call_number_1.getNextCallNumber)());
}
}
exports.SingleSubchannelChannel = SingleSubchannelChannel;
//# sourceMappingURL=single-subchannel-channel.js.map

View File

@@ -0,0 +1,68 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.StatusBuilder = void 0;
/**
* A builder for gRPC status objects.
*/
class StatusBuilder {
constructor() {
this.code = null;
this.details = null;
this.metadata = null;
}
/**
* Adds a status code to the builder.
*/
withCode(code) {
this.code = code;
return this;
}
/**
* Adds details to the builder.
*/
withDetails(details) {
this.details = details;
return this;
}
/**
* Adds metadata to the builder.
*/
withMetadata(metadata) {
this.metadata = metadata;
return this;
}
/**
* Builds the status object.
*/
build() {
const status = {};
if (this.code !== null) {
status.code = this.code;
}
if (this.details !== null) {
status.details = this.details;
}
if (this.metadata !== null) {
status.metadata = this.metadata;
}
return status;
}
}
exports.StatusBuilder = StatusBuilder;
//# sourceMappingURL=status-builder.js.map

View File

@@ -0,0 +1,100 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.StreamDecoder = void 0;
var ReadState;
(function (ReadState) {
ReadState[ReadState["NO_DATA"] = 0] = "NO_DATA";
ReadState[ReadState["READING_SIZE"] = 1] = "READING_SIZE";
ReadState[ReadState["READING_MESSAGE"] = 2] = "READING_MESSAGE";
})(ReadState || (ReadState = {}));
class StreamDecoder {
constructor(maxReadMessageLength) {
this.maxReadMessageLength = maxReadMessageLength;
this.readState = ReadState.NO_DATA;
this.readCompressFlag = Buffer.alloc(1);
this.readPartialSize = Buffer.alloc(4);
this.readSizeRemaining = 4;
this.readMessageSize = 0;
this.readPartialMessage = [];
this.readMessageRemaining = 0;
}
write(data) {
let readHead = 0;
let toRead;
const result = [];
while (readHead < data.length) {
switch (this.readState) {
case ReadState.NO_DATA:
this.readCompressFlag = data.slice(readHead, readHead + 1);
readHead += 1;
this.readState = ReadState.READING_SIZE;
this.readPartialSize.fill(0);
this.readSizeRemaining = 4;
this.readMessageSize = 0;
this.readMessageRemaining = 0;
this.readPartialMessage = [];
break;
case ReadState.READING_SIZE:
toRead = Math.min(data.length - readHead, this.readSizeRemaining);
data.copy(this.readPartialSize, 4 - this.readSizeRemaining, readHead, readHead + toRead);
this.readSizeRemaining -= toRead;
readHead += toRead;
// readSizeRemaining >=0 here
if (this.readSizeRemaining === 0) {
this.readMessageSize = this.readPartialSize.readUInt32BE(0);
if (this.maxReadMessageLength !== -1 && this.readMessageSize > this.maxReadMessageLength) {
throw new Error(`Received message larger than max (${this.readMessageSize} vs ${this.maxReadMessageLength})`);
}
this.readMessageRemaining = this.readMessageSize;
if (this.readMessageRemaining > 0) {
this.readState = ReadState.READING_MESSAGE;
}
else {
const message = Buffer.concat([this.readCompressFlag, this.readPartialSize], 5);
this.readState = ReadState.NO_DATA;
result.push(message);
}
}
break;
case ReadState.READING_MESSAGE:
toRead = Math.min(data.length - readHead, this.readMessageRemaining);
this.readPartialMessage.push(data.slice(readHead, readHead + toRead));
this.readMessageRemaining -= toRead;
readHead += toRead;
// readMessageRemaining >=0 here
if (this.readMessageRemaining === 0) {
// At this point, we have read a full message
const framedMessageBuffers = [
this.readCompressFlag,
this.readPartialSize,
].concat(this.readPartialMessage);
const framedMessage = Buffer.concat(framedMessageBuffers, this.readMessageSize + 5);
this.readState = ReadState.NO_DATA;
result.push(framedMessage);
}
break;
default:
throw new Error('Unexpected read state');
}
}
return result;
}
}
exports.StreamDecoder = StreamDecoder;
//# sourceMappingURL=stream-decoder.js.map

View File

@@ -0,0 +1,202 @@
"use strict";
/*
* Copyright 2021 gRPC authors.
*
* Licensed 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.EndpointMap = void 0;
exports.isTcpSubchannelAddress = isTcpSubchannelAddress;
exports.subchannelAddressEqual = subchannelAddressEqual;
exports.subchannelAddressToString = subchannelAddressToString;
exports.stringToSubchannelAddress = stringToSubchannelAddress;
exports.endpointEqual = endpointEqual;
exports.endpointToString = endpointToString;
exports.endpointHasAddress = endpointHasAddress;
const net_1 = require("net");
function isTcpSubchannelAddress(address) {
return 'port' in address;
}
function subchannelAddressEqual(address1, address2) {
if (!address1 && !address2) {
return true;
}
if (!address1 || !address2) {
return false;
}
if (isTcpSubchannelAddress(address1)) {
return (isTcpSubchannelAddress(address2) &&
address1.host === address2.host &&
address1.port === address2.port);
}
else {
return !isTcpSubchannelAddress(address2) && address1.path === address2.path;
}
}
function subchannelAddressToString(address) {
if (isTcpSubchannelAddress(address)) {
if ((0, net_1.isIPv6)(address.host)) {
return '[' + address.host + ']:' + address.port;
}
else {
return address.host + ':' + address.port;
}
}
else {
return address.path;
}
}
const DEFAULT_PORT = 443;
function stringToSubchannelAddress(addressString, port) {
if ((0, net_1.isIP)(addressString)) {
return {
host: addressString,
port: port !== null && port !== void 0 ? port : DEFAULT_PORT,
};
}
else {
return {
path: addressString,
};
}
}
function endpointEqual(endpoint1, endpoint2) {
if (endpoint1.addresses.length !== endpoint2.addresses.length) {
return false;
}
for (let i = 0; i < endpoint1.addresses.length; i++) {
if (!subchannelAddressEqual(endpoint1.addresses[i], endpoint2.addresses[i])) {
return false;
}
}
return true;
}
function endpointToString(endpoint) {
return ('[' + endpoint.addresses.map(subchannelAddressToString).join(', ') + ']');
}
function endpointHasAddress(endpoint, expectedAddress) {
for (const address of endpoint.addresses) {
if (subchannelAddressEqual(address, expectedAddress)) {
return true;
}
}
return false;
}
function endpointEqualUnordered(endpoint1, endpoint2) {
if (endpoint1.addresses.length !== endpoint2.addresses.length) {
return false;
}
for (const address1 of endpoint1.addresses) {
let matchFound = false;
for (const address2 of endpoint2.addresses) {
if (subchannelAddressEqual(address1, address2)) {
matchFound = true;
break;
}
}
if (!matchFound) {
return false;
}
}
return true;
}
class EndpointMap {
constructor() {
this.map = new Set();
}
get size() {
return this.map.size;
}
getForSubchannelAddress(address) {
for (const entry of this.map) {
if (endpointHasAddress(entry.key, address)) {
return entry.value;
}
}
return undefined;
}
/**
* Delete any entries in this map with keys that are not in endpoints
* @param endpoints
*/
deleteMissing(endpoints) {
const removedValues = [];
for (const entry of this.map) {
let foundEntry = false;
for (const endpoint of endpoints) {
if (endpointEqualUnordered(endpoint, entry.key)) {
foundEntry = true;
}
}
if (!foundEntry) {
removedValues.push(entry.value);
this.map.delete(entry);
}
}
return removedValues;
}
get(endpoint) {
for (const entry of this.map) {
if (endpointEqualUnordered(endpoint, entry.key)) {
return entry.value;
}
}
return undefined;
}
set(endpoint, mapEntry) {
for (const entry of this.map) {
if (endpointEqualUnordered(endpoint, entry.key)) {
entry.value = mapEntry;
return;
}
}
this.map.add({ key: endpoint, value: mapEntry });
}
delete(endpoint) {
for (const entry of this.map) {
if (endpointEqualUnordered(endpoint, entry.key)) {
this.map.delete(entry);
return;
}
}
}
has(endpoint) {
for (const entry of this.map) {
if (endpointEqualUnordered(endpoint, entry.key)) {
return true;
}
}
return false;
}
clear() {
this.map.clear();
}
*keys() {
for (const entry of this.map) {
yield entry.key;
}
}
*values() {
for (const entry of this.map) {
yield entry.value;
}
}
*entries() {
for (const entry of this.map) {
yield [entry.key, entry.value];
}
}
}
exports.EndpointMap = EndpointMap;
//# sourceMappingURL=subchannel-address.js.map

View File

@@ -0,0 +1,545 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.Http2SubchannelCall = void 0;
const http2 = require("http2");
const os = require("os");
const constants_1 = require("./constants");
const metadata_1 = require("./metadata");
const stream_decoder_1 = require("./stream-decoder");
const logging = require("./logging");
const constants_2 = require("./constants");
const TRACER_NAME = 'subchannel_call';
/**
* Should do approximately the same thing as util.getSystemErrorName but the
* TypeScript types don't have that function for some reason so I just made my
* own.
* @param errno
*/
function getSystemErrorName(errno) {
for (const [name, num] of Object.entries(os.constants.errno)) {
if (num === errno) {
return name;
}
}
return 'Unknown system error ' + errno;
}
function mapHttpStatusCode(code) {
const details = `Received HTTP status code ${code}`;
let mappedStatusCode;
switch (code) {
// TODO(murgatroid99): handle 100 and 101
case 400:
mappedStatusCode = constants_1.Status.INTERNAL;
break;
case 401:
mappedStatusCode = constants_1.Status.UNAUTHENTICATED;
break;
case 403:
mappedStatusCode = constants_1.Status.PERMISSION_DENIED;
break;
case 404:
mappedStatusCode = constants_1.Status.UNIMPLEMENTED;
break;
case 429:
case 502:
case 503:
case 504:
mappedStatusCode = constants_1.Status.UNAVAILABLE;
break;
default:
mappedStatusCode = constants_1.Status.UNKNOWN;
}
return {
code: mappedStatusCode,
details: details,
metadata: new metadata_1.Metadata()
};
}
class Http2SubchannelCall {
constructor(http2Stream, callEventTracker, listener, transport, callId) {
var _a;
this.http2Stream = http2Stream;
this.callEventTracker = callEventTracker;
this.listener = listener;
this.transport = transport;
this.callId = callId;
this.isReadFilterPending = false;
this.isPushPending = false;
this.canPush = false;
/**
* Indicates that an 'end' event has come from the http2 stream, so there
* will be no more data events.
*/
this.readsClosed = false;
this.statusOutput = false;
this.unpushedReadMessages = [];
// This is populated (non-null) if and only if the call has ended
this.finalStatus = null;
this.internalError = null;
this.serverEndedCall = false;
this.connectionDropped = false;
const maxReceiveMessageLength = (_a = transport.getOptions()['grpc.max_receive_message_length']) !== null && _a !== void 0 ? _a : constants_1.DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH;
this.decoder = new stream_decoder_1.StreamDecoder(maxReceiveMessageLength);
http2Stream.on('response', (headers, flags) => {
let headersString = '';
for (const header of Object.keys(headers)) {
headersString += '\t\t' + header + ': ' + headers[header] + '\n';
}
this.trace('Received server headers:\n' + headersString);
this.httpStatusCode = headers[':status'];
if (flags & http2.constants.NGHTTP2_FLAG_END_STREAM) {
this.handleTrailers(headers);
}
else {
let metadata;
try {
metadata = metadata_1.Metadata.fromHttp2Headers(headers);
}
catch (error) {
this.endCall({
code: constants_1.Status.UNKNOWN,
details: error.message,
metadata: new metadata_1.Metadata(),
});
return;
}
this.listener.onReceiveMetadata(metadata);
}
});
http2Stream.on('trailers', (headers) => {
this.handleTrailers(headers);
});
http2Stream.on('data', (data) => {
/* If the status has already been output, allow the http2 stream to
* drain without processing the data. */
if (this.statusOutput) {
return;
}
this.trace('receive HTTP/2 data frame of length ' + data.length);
let messages;
try {
messages = this.decoder.write(data);
}
catch (e) {
/* Some servers send HTML error pages along with HTTP status codes.
* When the client attempts to parse this as a length-delimited
* message, the parsed message size is greater than the default limit,
* resulting in a message decoding error. In that situation, the HTTP
* error code information is more useful to the user than the
* RESOURCE_EXHAUSTED error is, so we report that instead. Normally,
* we delay processing the HTTP status until after the stream ends, to
* prioritize reporting the gRPC status from trailers if it is present,
* but when there is a message parsing error we end the stream early
* before processing trailers. */
if (this.httpStatusCode !== undefined && this.httpStatusCode !== 200) {
const mappedStatus = mapHttpStatusCode(this.httpStatusCode);
this.cancelWithStatus(mappedStatus.code, mappedStatus.details);
}
else {
this.cancelWithStatus(constants_1.Status.RESOURCE_EXHAUSTED, e.message);
}
return;
}
for (const message of messages) {
this.trace('parsed message of length ' + message.length);
this.callEventTracker.addMessageReceived();
this.tryPush(message);
}
});
http2Stream.on('end', () => {
this.readsClosed = true;
this.maybeOutputStatus();
});
http2Stream.on('close', () => {
this.serverEndedCall = true;
/* Use process.next tick to ensure that this code happens after any
* "error" event that may be emitted at about the same time, so that
* we can bubble up the error message from that event. */
process.nextTick(() => {
var _a;
this.trace('HTTP/2 stream closed with code ' + http2Stream.rstCode);
/* If we have a final status with an OK status code, that means that
* we have received all of the messages and we have processed the
* trailers and the call completed successfully, so it doesn't matter
* how the stream ends after that */
if (((_a = this.finalStatus) === null || _a === void 0 ? void 0 : _a.code) === constants_1.Status.OK) {
return;
}
let code;
let details = '';
switch (http2Stream.rstCode) {
case http2.constants.NGHTTP2_NO_ERROR:
/* If we get a NO_ERROR code and we already have a status, the
* stream completed properly and we just haven't fully processed
* it yet */
if (this.finalStatus !== null) {
return;
}
if (this.httpStatusCode && this.httpStatusCode !== 200) {
const mappedStatus = mapHttpStatusCode(this.httpStatusCode);
code = mappedStatus.code;
details = mappedStatus.details;
}
else {
code = constants_1.Status.INTERNAL;
details = `Received RST_STREAM with code ${http2Stream.rstCode} (Call ended without gRPC status)`;
}
break;
case http2.constants.NGHTTP2_REFUSED_STREAM:
code = constants_1.Status.UNAVAILABLE;
details = 'Stream refused by server';
break;
case http2.constants.NGHTTP2_CANCEL:
/* Bug reports indicate that Node synthesizes a NGHTTP2_CANCEL
* code from connection drops. We want to prioritize reporting
* an unavailable status when that happens. */
if (this.connectionDropped) {
code = constants_1.Status.UNAVAILABLE;
details = 'Connection dropped';
}
else {
code = constants_1.Status.CANCELLED;
details = 'Call cancelled';
}
break;
case http2.constants.NGHTTP2_ENHANCE_YOUR_CALM:
code = constants_1.Status.RESOURCE_EXHAUSTED;
details = 'Bandwidth exhausted or memory limit exceeded';
break;
case http2.constants.NGHTTP2_INADEQUATE_SECURITY:
code = constants_1.Status.PERMISSION_DENIED;
details = 'Protocol not secure enough';
break;
case http2.constants.NGHTTP2_INTERNAL_ERROR:
code = constants_1.Status.INTERNAL;
if (this.internalError === null) {
/* This error code was previously handled in the default case, and
* there are several instances of it online, so I wanted to
* preserve the original error message so that people find existing
* information in searches, but also include the more recognizable
* "Internal server error" message. */
details = `Received RST_STREAM with code ${http2Stream.rstCode} (Internal server error)`;
}
else {
if (this.internalError.code === 'ECONNRESET' ||
this.internalError.code === 'ETIMEDOUT') {
code = constants_1.Status.UNAVAILABLE;
details = this.internalError.message;
}
else {
/* The "Received RST_STREAM with code ..." error is preserved
* here for continuity with errors reported online, but the
* error message at the end will probably be more relevant in
* most cases. */
details = `Received RST_STREAM with code ${http2Stream.rstCode} triggered by internal client error: ${this.internalError.message}`;
}
}
break;
default:
code = constants_1.Status.INTERNAL;
details = `Received RST_STREAM with code ${http2Stream.rstCode}`;
}
// This is a no-op if trailers were received at all.
// This is OK, because status codes emitted here correspond to more
// catastrophic issues that prevent us from receiving trailers in the
// first place.
this.endCall({
code,
details,
metadata: new metadata_1.Metadata(),
rstCode: http2Stream.rstCode,
});
});
});
http2Stream.on('error', (err) => {
/* We need an error handler here to stop "Uncaught Error" exceptions
* from bubbling up. However, errors here should all correspond to
* "close" events, where we will handle the error more granularly */
/* Specifically looking for stream errors that were *not* constructed
* from a RST_STREAM response here:
* https://github.com/nodejs/node/blob/8b8620d580314050175983402dfddf2674e8e22a/lib/internal/http2/core.js#L2267
*/
if (err.code !== 'ERR_HTTP2_STREAM_ERROR') {
this.trace('Node error event: message=' +
err.message +
' code=' +
err.code +
' errno=' +
getSystemErrorName(err.errno) +
' syscall=' +
err.syscall);
this.internalError = err;
}
this.callEventTracker.onStreamEnd(false);
});
}
getDeadlineInfo() {
return [`remote_addr=${this.getPeer()}`];
}
onDisconnect() {
this.connectionDropped = true;
/* Give the call an event loop cycle to finish naturally before reporting
* the disconnection as an error. */
setImmediate(() => {
this.endCall({
code: constants_1.Status.UNAVAILABLE,
details: 'Connection dropped',
metadata: new metadata_1.Metadata(),
});
});
}
outputStatus() {
/* Precondition: this.finalStatus !== null */
if (!this.statusOutput) {
this.statusOutput = true;
this.trace('ended with status: code=' +
this.finalStatus.code +
' details="' +
this.finalStatus.details +
'"');
this.callEventTracker.onCallEnd(this.finalStatus);
/* We delay the actual action of bubbling up the status to insulate the
* cleanup code in this class from any errors that may be thrown in the
* upper layers as a result of bubbling up the status. In particular,
* if the status is not OK, the "error" event may be emitted
* synchronously at the top level, which will result in a thrown error if
* the user does not handle that event. */
process.nextTick(() => {
this.listener.onReceiveStatus(this.finalStatus);
});
/* Leave the http2 stream in flowing state to drain incoming messages, to
* ensure that the stream closure completes. The call stream already does
* not push more messages after the status is output, so the messages go
* nowhere either way. */
this.http2Stream.resume();
}
}
trace(text) {
logging.trace(constants_2.LogVerbosity.DEBUG, TRACER_NAME, '[' + this.callId + '] ' + text);
}
/**
* On first call, emits a 'status' event with the given StatusObject.
* Subsequent calls are no-ops.
* @param status The status of the call.
*/
endCall(status) {
/* If the status is OK and a new status comes in (e.g. from a
* deserialization failure), that new status takes priority */
if (this.finalStatus === null || this.finalStatus.code === constants_1.Status.OK) {
this.finalStatus = status;
this.maybeOutputStatus();
}
this.destroyHttp2Stream();
}
maybeOutputStatus() {
if (this.finalStatus !== null) {
/* The combination check of readsClosed and that the two message buffer
* arrays are empty checks that there all incoming data has been fully
* processed */
if (this.finalStatus.code !== constants_1.Status.OK ||
(this.readsClosed &&
this.unpushedReadMessages.length === 0 &&
!this.isReadFilterPending &&
!this.isPushPending)) {
this.outputStatus();
}
}
}
push(message) {
this.trace('pushing to reader message of length ' +
(message instanceof Buffer ? message.length : null));
this.canPush = false;
this.isPushPending = true;
process.nextTick(() => {
this.isPushPending = false;
/* If we have already output the status any later messages should be
* ignored, and can cause out-of-order operation errors higher up in the
* stack. Checking as late as possible here to avoid any race conditions.
*/
if (this.statusOutput) {
return;
}
this.listener.onReceiveMessage(message);
this.maybeOutputStatus();
});
}
tryPush(messageBytes) {
if (this.canPush) {
this.http2Stream.pause();
this.push(messageBytes);
}
else {
this.trace('unpushedReadMessages.push message of length ' + messageBytes.length);
this.unpushedReadMessages.push(messageBytes);
}
}
handleTrailers(headers) {
this.serverEndedCall = true;
this.callEventTracker.onStreamEnd(true);
let headersString = '';
for (const header of Object.keys(headers)) {
headersString += '\t\t' + header + ': ' + headers[header] + '\n';
}
this.trace('Received server trailers:\n' + headersString);
let metadata;
try {
metadata = metadata_1.Metadata.fromHttp2Headers(headers);
}
catch (e) {
metadata = new metadata_1.Metadata();
}
const metadataMap = metadata.getMap();
let status;
if (typeof metadataMap['grpc-status'] === 'string') {
const receivedStatus = Number(metadataMap['grpc-status']);
this.trace('received status code ' + receivedStatus + ' from server');
metadata.remove('grpc-status');
let details = '';
if (typeof metadataMap['grpc-message'] === 'string') {
try {
details = decodeURI(metadataMap['grpc-message']);
}
catch (e) {
details = metadataMap['grpc-message'];
}
metadata.remove('grpc-message');
this.trace('received status details string "' + details + '" from server');
}
status = {
code: receivedStatus,
details: details,
metadata: metadata
};
}
else if (this.httpStatusCode) {
status = mapHttpStatusCode(this.httpStatusCode);
status.metadata = metadata;
}
else {
status = {
code: constants_1.Status.UNKNOWN,
details: 'No status information received',
metadata: metadata
};
}
// This is a no-op if the call was already ended when handling headers.
this.endCall(status);
}
destroyHttp2Stream() {
var _a;
// The http2 stream could already have been destroyed if cancelWithStatus
// is called in response to an internal http2 error.
if (this.http2Stream.destroyed) {
return;
}
/* If the server ended the call, sending an RST_STREAM is redundant, so we
* just half close on the client side instead to finish closing the stream.
*/
if (this.serverEndedCall) {
this.http2Stream.end();
}
else {
/* If the call has ended with an OK status, communicate that when closing
* the stream, partly to avoid a situation in which we detect an error
* RST_STREAM as a result after we have the status */
let code;
if (((_a = this.finalStatus) === null || _a === void 0 ? void 0 : _a.code) === constants_1.Status.OK) {
code = http2.constants.NGHTTP2_NO_ERROR;
}
else {
code = http2.constants.NGHTTP2_CANCEL;
}
this.trace('close http2 stream with code ' + code);
this.http2Stream.close(code);
}
}
cancelWithStatus(status, details) {
this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"');
this.endCall({ code: status, details, metadata: new metadata_1.Metadata() });
}
getStatus() {
return this.finalStatus;
}
getPeer() {
return this.transport.getPeerName();
}
getCallNumber() {
return this.callId;
}
getAuthContext() {
return this.transport.getAuthContext();
}
startRead() {
/* If the stream has ended with an error, we should not emit any more
* messages and we should communicate that the stream has ended */
if (this.finalStatus !== null && this.finalStatus.code !== constants_1.Status.OK) {
this.readsClosed = true;
this.maybeOutputStatus();
return;
}
this.canPush = true;
if (this.unpushedReadMessages.length > 0) {
const nextMessage = this.unpushedReadMessages.shift();
this.push(nextMessage);
return;
}
/* Only resume reading from the http2Stream if we don't have any pending
* messages to emit */
this.http2Stream.resume();
}
sendMessageWithContext(context, message) {
this.trace('write() called with message of length ' + message.length);
const cb = (error) => {
/* nextTick here ensures that no stream action can be taken in the call
* stack of the write callback, in order to hopefully work around
* https://github.com/nodejs/node/issues/49147 */
process.nextTick(() => {
var _a;
let code = constants_1.Status.UNAVAILABLE;
if ((error === null || error === void 0 ? void 0 : error.code) ===
'ERR_STREAM_WRITE_AFTER_END') {
code = constants_1.Status.INTERNAL;
}
if (error) {
this.cancelWithStatus(code, `Write error: ${error.message}`);
}
(_a = context.callback) === null || _a === void 0 ? void 0 : _a.call(context);
});
};
this.trace('sending data chunk of length ' + message.length);
this.callEventTracker.addMessageSent();
try {
this.http2Stream.write(message, cb);
}
catch (error) {
this.endCall({
code: constants_1.Status.UNAVAILABLE,
details: `Write failed with error ${error.message}`,
metadata: new metadata_1.Metadata(),
});
}
}
halfClose() {
this.trace('end() called');
this.trace('calling end() on HTTP/2 stream');
this.http2Stream.end();
}
}
exports.Http2SubchannelCall = Http2SubchannelCall;
//# sourceMappingURL=subchannel-call.js.map

View File

@@ -0,0 +1,114 @@
"use strict";
/*
* Copyright 2022 gRPC authors.
*
* Licensed 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.BaseSubchannelWrapper = void 0;
class BaseSubchannelWrapper {
constructor(child) {
this.child = child;
this.healthy = true;
this.healthListeners = new Set();
this.refcount = 0;
this.dataWatchers = new Set();
child.addHealthStateWatcher(childHealthy => {
/* A change to the child health state only affects this wrapper's overall
* health state if this wrapper is reporting healthy. */
if (this.healthy) {
this.updateHealthListeners();
}
});
}
updateHealthListeners() {
for (const listener of this.healthListeners) {
listener(this.isHealthy());
}
}
getConnectivityState() {
return this.child.getConnectivityState();
}
addConnectivityStateListener(listener) {
this.child.addConnectivityStateListener(listener);
}
removeConnectivityStateListener(listener) {
this.child.removeConnectivityStateListener(listener);
}
startConnecting() {
this.child.startConnecting();
}
getAddress() {
return this.child.getAddress();
}
throttleKeepalive(newKeepaliveTime) {
this.child.throttleKeepalive(newKeepaliveTime);
}
ref() {
this.child.ref();
this.refcount += 1;
}
unref() {
this.child.unref();
this.refcount -= 1;
if (this.refcount === 0) {
this.destroy();
}
}
destroy() {
for (const watcher of this.dataWatchers) {
watcher.destroy();
}
}
getChannelzRef() {
return this.child.getChannelzRef();
}
isHealthy() {
return this.healthy && this.child.isHealthy();
}
addHealthStateWatcher(listener) {
this.healthListeners.add(listener);
}
removeHealthStateWatcher(listener) {
this.healthListeners.delete(listener);
}
addDataWatcher(dataWatcher) {
dataWatcher.setSubchannel(this.getRealSubchannel());
this.dataWatchers.add(dataWatcher);
}
setHealthy(healthy) {
if (healthy !== this.healthy) {
this.healthy = healthy;
/* A change to this wrapper's health state only affects the overall
* reported health state if the child is healthy. */
if (this.child.isHealthy()) {
this.updateHealthListeners();
}
}
}
getRealSubchannel() {
return this.child.getRealSubchannel();
}
realSubchannelEquals(other) {
return this.getRealSubchannel() === other.getRealSubchannel();
}
getCallCredentials() {
return this.child.getCallCredentials();
}
getChannel() {
return this.child.getChannel();
}
}
exports.BaseSubchannelWrapper = BaseSubchannelWrapper;
//# sourceMappingURL=subchannel-interface.js.map

View File

@@ -0,0 +1,137 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.SubchannelPool = void 0;
exports.getSubchannelPool = getSubchannelPool;
const channel_options_1 = require("./channel-options");
const subchannel_1 = require("./subchannel");
const subchannel_address_1 = require("./subchannel-address");
const uri_parser_1 = require("./uri-parser");
const transport_1 = require("./transport");
// 10 seconds in milliseconds. This value is arbitrary.
/**
* The amount of time in between checks for dropping subchannels that have no
* other references
*/
const REF_CHECK_INTERVAL = 10000;
class SubchannelPool {
/**
* A pool of subchannels use for making connections. Subchannels with the
* exact same parameters will be reused.
*/
constructor() {
this.pool = Object.create(null);
/**
* A timer of a task performing a periodic subchannel cleanup.
*/
this.cleanupTimer = null;
}
/**
* Unrefs all unused subchannels and cancels the cleanup task if all
* subchannels have been unrefed.
*/
unrefUnusedSubchannels() {
let allSubchannelsUnrefed = true;
/* These objects are created with Object.create(null), so they do not
* have a prototype, which means that for (... in ...) loops over them
* do not need to be filtered */
// eslint-disable-disable-next-line:forin
for (const channelTarget in this.pool) {
const subchannelObjArray = this.pool[channelTarget];
const refedSubchannels = subchannelObjArray.filter(value => !value.subchannel.unrefIfOneRef());
if (refedSubchannels.length > 0) {
allSubchannelsUnrefed = false;
}
/* For each subchannel in the pool, try to unref it if it has
* exactly one ref (which is the ref from the pool itself). If that
* does happen, remove the subchannel from the pool */
this.pool[channelTarget] = refedSubchannels;
}
/* Currently we do not delete keys with empty values. If that results
* in significant memory usage we should change it. */
// Cancel the cleanup task if all subchannels have been unrefed.
if (allSubchannelsUnrefed && this.cleanupTimer !== null) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
}
/**
* Ensures that the cleanup task is spawned.
*/
ensureCleanupTask() {
var _a, _b;
if (this.cleanupTimer === null) {
this.cleanupTimer = setInterval(() => {
this.unrefUnusedSubchannels();
}, REF_CHECK_INTERVAL);
// Unref because this timer should not keep the event loop running.
// Call unref only if it exists to address electron/electron#21162
(_b = (_a = this.cleanupTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
}
}
/**
* Get a subchannel if one already exists with exactly matching parameters.
* Otherwise, create and save a subchannel with those parameters.
* @param channelTarget
* @param subchannelTarget
* @param channelArguments
* @param channelCredentials
*/
getOrCreateSubchannel(channelTargetUri, subchannelTarget, channelArguments, channelCredentials) {
this.ensureCleanupTask();
const channelTarget = (0, uri_parser_1.uriToString)(channelTargetUri);
if (channelTarget in this.pool) {
const subchannelObjArray = this.pool[channelTarget];
for (const subchannelObj of subchannelObjArray) {
if ((0, subchannel_address_1.subchannelAddressEqual)(subchannelTarget, subchannelObj.subchannelAddress) &&
(0, channel_options_1.channelOptionsEqual)(channelArguments, subchannelObj.channelArguments) &&
channelCredentials._equals(subchannelObj.channelCredentials)) {
return subchannelObj.subchannel;
}
}
}
// If we get here, no matching subchannel was found
const subchannel = new subchannel_1.Subchannel(channelTargetUri, subchannelTarget, channelArguments, channelCredentials, new transport_1.Http2SubchannelConnector(channelTargetUri));
if (!(channelTarget in this.pool)) {
this.pool[channelTarget] = [];
}
this.pool[channelTarget].push({
subchannelAddress: subchannelTarget,
channelArguments,
channelCredentials,
subchannel,
});
subchannel.ref();
return subchannel;
}
}
exports.SubchannelPool = SubchannelPool;
const globalSubchannelPool = new SubchannelPool();
/**
* Get either the global subchannel pool, or a new subchannel pool.
* @param global
*/
function getSubchannelPool(global) {
if (global) {
return globalSubchannelPool;
}
else {
return new SubchannelPool();
}
}
//# sourceMappingURL=subchannel-pool.js.map

View File

@@ -0,0 +1,397 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.Subchannel = void 0;
const connectivity_state_1 = require("./connectivity-state");
const backoff_timeout_1 = require("./backoff-timeout");
const logging = require("./logging");
const constants_1 = require("./constants");
const uri_parser_1 = require("./uri-parser");
const subchannel_address_1 = require("./subchannel-address");
const channelz_1 = require("./channelz");
const single_subchannel_channel_1 = require("./single-subchannel-channel");
const TRACER_NAME = 'subchannel';
/* setInterval and setTimeout only accept signed 32 bit integers. JS doesn't
* have a constant for the max signed 32 bit integer, so this is a simple way
* to calculate it */
const KEEPALIVE_MAX_TIME_MS = ~(1 << 31);
class Subchannel {
/**
* A class representing a connection to a single backend.
* @param channelTarget The target string for the channel as a whole
* @param subchannelAddress The address for the backend that this subchannel
* will connect to
* @param options The channel options, plus any specific subchannel options
* for this subchannel
* @param credentials The channel credentials used to establish this
* connection
*/
constructor(channelTarget, subchannelAddress, options, credentials, connector) {
var _a;
this.channelTarget = channelTarget;
this.subchannelAddress = subchannelAddress;
this.options = options;
this.connector = connector;
/**
* The subchannel's current connectivity state. Invariant: `session` === `null`
* if and only if `connectivityState` is IDLE or TRANSIENT_FAILURE.
*/
this.connectivityState = connectivity_state_1.ConnectivityState.IDLE;
/**
* The underlying http2 session used to make requests.
*/
this.transport = null;
/**
* Indicates that the subchannel should transition from TRANSIENT_FAILURE to
* CONNECTING instead of IDLE when the backoff timeout ends.
*/
this.continueConnecting = false;
/**
* A list of listener functions that will be called whenever the connectivity
* state changes. Will be modified by `addConnectivityStateListener` and
* `removeConnectivityStateListener`
*/
this.stateListeners = new Set();
/**
* Tracks channels and subchannel pools with references to this subchannel
*/
this.refcount = 0;
// Channelz info
this.channelzEnabled = true;
this.dataProducers = new Map();
this.subchannelChannel = null;
const backoffOptions = {
initialDelay: options['grpc.initial_reconnect_backoff_ms'],
maxDelay: options['grpc.max_reconnect_backoff_ms'],
};
this.backoffTimeout = new backoff_timeout_1.BackoffTimeout(() => {
this.handleBackoffTimer();
}, backoffOptions);
this.backoffTimeout.unref();
this.subchannelAddressString = (0, subchannel_address_1.subchannelAddressToString)(subchannelAddress);
this.keepaliveTime = (_a = options['grpc.keepalive_time_ms']) !== null && _a !== void 0 ? _a : -1;
if (options['grpc.enable_channelz'] === 0) {
this.channelzEnabled = false;
this.channelzTrace = new channelz_1.ChannelzTraceStub();
this.callTracker = new channelz_1.ChannelzCallTrackerStub();
this.childrenTracker = new channelz_1.ChannelzChildrenTrackerStub();
this.streamTracker = new channelz_1.ChannelzCallTrackerStub();
}
else {
this.channelzTrace = new channelz_1.ChannelzTrace();
this.callTracker = new channelz_1.ChannelzCallTracker();
this.childrenTracker = new channelz_1.ChannelzChildrenTracker();
this.streamTracker = new channelz_1.ChannelzCallTracker();
}
this.channelzRef = (0, channelz_1.registerChannelzSubchannel)(this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled);
this.channelzTrace.addTrace('CT_INFO', 'Subchannel created');
this.trace('Subchannel constructed with options ' +
JSON.stringify(options, undefined, 2));
this.secureConnector = credentials._createSecureConnector(channelTarget, options);
}
getChannelzInfo() {
return {
state: this.connectivityState,
trace: this.channelzTrace,
callTracker: this.callTracker,
children: this.childrenTracker.getChildLists(),
target: this.subchannelAddressString,
};
}
trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, '(' +
this.channelzRef.id +
') ' +
this.subchannelAddressString +
' ' +
text);
}
refTrace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, 'subchannel_refcount', '(' +
this.channelzRef.id +
') ' +
this.subchannelAddressString +
' ' +
text);
}
handleBackoffTimer() {
if (this.continueConnecting) {
this.transitionToState([connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE], connectivity_state_1.ConnectivityState.CONNECTING);
}
else {
this.transitionToState([connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE], connectivity_state_1.ConnectivityState.IDLE);
}
}
/**
* Start a backoff timer with the current nextBackoff timeout
*/
startBackoff() {
this.backoffTimeout.runOnce();
}
stopBackoff() {
this.backoffTimeout.stop();
this.backoffTimeout.reset();
}
startConnectingInternal() {
let options = this.options;
if (options['grpc.keepalive_time_ms']) {
const adjustedKeepaliveTime = Math.min(this.keepaliveTime, KEEPALIVE_MAX_TIME_MS);
options = Object.assign(Object.assign({}, options), { 'grpc.keepalive_time_ms': adjustedKeepaliveTime });
}
this.connector
.connect(this.subchannelAddress, this.secureConnector, options)
.then(transport => {
if (this.transitionToState([connectivity_state_1.ConnectivityState.CONNECTING], connectivity_state_1.ConnectivityState.READY)) {
this.transport = transport;
if (this.channelzEnabled) {
this.childrenTracker.refChild(transport.getChannelzRef());
}
transport.addDisconnectListener(tooManyPings => {
this.transitionToState([connectivity_state_1.ConnectivityState.READY], connectivity_state_1.ConnectivityState.IDLE);
if (tooManyPings && this.keepaliveTime > 0) {
this.keepaliveTime *= 2;
logging.log(constants_1.LogVerbosity.ERROR, `Connection to ${(0, uri_parser_1.uriToString)(this.channelTarget)} at ${this.subchannelAddressString} rejected by server because of excess pings. Increasing ping interval to ${this.keepaliveTime} ms`);
}
});
}
else {
/* If we can't transition from CONNECTING to READY here, we will
* not be using this transport, so release its resources. */
transport.shutdown();
}
}, error => {
this.transitionToState([connectivity_state_1.ConnectivityState.CONNECTING], connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, `${error}`);
});
}
/**
* Initiate a state transition from any element of oldStates to the new
* state. If the current connectivityState is not in oldStates, do nothing.
* @param oldStates The set of states to transition from
* @param newState The state to transition to
* @returns True if the state changed, false otherwise
*/
transitionToState(oldStates, newState, errorMessage) {
var _a, _b;
if (oldStates.indexOf(this.connectivityState) === -1) {
return false;
}
if (errorMessage) {
this.trace(connectivity_state_1.ConnectivityState[this.connectivityState] +
' -> ' +
connectivity_state_1.ConnectivityState[newState] +
' with error "' + errorMessage + '"');
}
else {
this.trace(connectivity_state_1.ConnectivityState[this.connectivityState] +
' -> ' +
connectivity_state_1.ConnectivityState[newState]);
}
if (this.channelzEnabled) {
this.channelzTrace.addTrace('CT_INFO', 'Connectivity state change to ' + connectivity_state_1.ConnectivityState[newState]);
}
const previousState = this.connectivityState;
this.connectivityState = newState;
switch (newState) {
case connectivity_state_1.ConnectivityState.READY:
this.stopBackoff();
break;
case connectivity_state_1.ConnectivityState.CONNECTING:
this.startBackoff();
this.startConnectingInternal();
this.continueConnecting = false;
break;
case connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE:
if (this.channelzEnabled && this.transport) {
this.childrenTracker.unrefChild(this.transport.getChannelzRef());
}
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.shutdown();
this.transport = null;
/* If the backoff timer has already ended by the time we get to the
* TRANSIENT_FAILURE state, we want to immediately transition out of
* TRANSIENT_FAILURE as though the backoff timer is ending right now */
if (!this.backoffTimeout.isRunning()) {
process.nextTick(() => {
this.handleBackoffTimer();
});
}
break;
case connectivity_state_1.ConnectivityState.IDLE:
if (this.channelzEnabled && this.transport) {
this.childrenTracker.unrefChild(this.transport.getChannelzRef());
}
(_b = this.transport) === null || _b === void 0 ? void 0 : _b.shutdown();
this.transport = null;
break;
default:
throw new Error(`Invalid state: unknown ConnectivityState ${newState}`);
}
for (const listener of this.stateListeners) {
listener(this, previousState, newState, this.keepaliveTime, errorMessage);
}
return true;
}
ref() {
this.refTrace('refcount ' + this.refcount + ' -> ' + (this.refcount + 1));
this.refcount += 1;
}
unref() {
this.refTrace('refcount ' + this.refcount + ' -> ' + (this.refcount - 1));
this.refcount -= 1;
if (this.refcount === 0) {
this.channelzTrace.addTrace('CT_INFO', 'Shutting down');
(0, channelz_1.unregisterChannelzRef)(this.channelzRef);
this.secureConnector.destroy();
process.nextTick(() => {
this.transitionToState([connectivity_state_1.ConnectivityState.CONNECTING, connectivity_state_1.ConnectivityState.READY], connectivity_state_1.ConnectivityState.IDLE);
});
}
}
unrefIfOneRef() {
if (this.refcount === 1) {
this.unref();
return true;
}
return false;
}
createCall(metadata, host, method, listener) {
if (!this.transport) {
throw new Error('Cannot create call, subchannel not READY');
}
let statsTracker;
if (this.channelzEnabled) {
this.callTracker.addCallStarted();
this.streamTracker.addCallStarted();
statsTracker = {
onCallEnd: status => {
if (status.code === constants_1.Status.OK) {
this.callTracker.addCallSucceeded();
}
else {
this.callTracker.addCallFailed();
}
},
};
}
else {
statsTracker = {};
}
return this.transport.createCall(metadata, host, method, listener, statsTracker);
}
/**
* If the subchannel is currently IDLE, start connecting and switch to the
* CONNECTING state. If the subchannel is current in TRANSIENT_FAILURE,
* the next time it would transition to IDLE, start connecting again instead.
* Otherwise, do nothing.
*/
startConnecting() {
process.nextTick(() => {
/* First, try to transition from IDLE to connecting. If that doesn't happen
* because the state is not currently IDLE, check if it is
* TRANSIENT_FAILURE, and if so indicate that it should go back to
* connecting after the backoff timer ends. Otherwise do nothing */
if (!this.transitionToState([connectivity_state_1.ConnectivityState.IDLE], connectivity_state_1.ConnectivityState.CONNECTING)) {
if (this.connectivityState === connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) {
this.continueConnecting = true;
}
}
});
}
/**
* Get the subchannel's current connectivity state.
*/
getConnectivityState() {
return this.connectivityState;
}
/**
* Add a listener function to be called whenever the subchannel's
* connectivity state changes.
* @param listener
*/
addConnectivityStateListener(listener) {
this.stateListeners.add(listener);
}
/**
* Remove a listener previously added with `addConnectivityStateListener`
* @param listener A reference to a function previously passed to
* `addConnectivityStateListener`
*/
removeConnectivityStateListener(listener) {
this.stateListeners.delete(listener);
}
/**
* Reset the backoff timeout, and immediately start connecting if in backoff.
*/
resetBackoff() {
process.nextTick(() => {
this.backoffTimeout.reset();
this.transitionToState([connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE], connectivity_state_1.ConnectivityState.CONNECTING);
});
}
getAddress() {
return this.subchannelAddressString;
}
getChannelzRef() {
return this.channelzRef;
}
isHealthy() {
return true;
}
addHealthStateWatcher(listener) {
// Do nothing with the listener
}
removeHealthStateWatcher(listener) {
// Do nothing with the listener
}
getRealSubchannel() {
return this;
}
realSubchannelEquals(other) {
return other.getRealSubchannel() === this;
}
throttleKeepalive(newKeepaliveTime) {
if (newKeepaliveTime > this.keepaliveTime) {
this.keepaliveTime = newKeepaliveTime;
}
}
getCallCredentials() {
return this.secureConnector.getCallCredentials();
}
getChannel() {
if (!this.subchannelChannel) {
this.subchannelChannel = new single_subchannel_channel_1.SingleSubchannelChannel(this, this.channelTarget, this.options);
}
return this.subchannelChannel;
}
addDataWatcher(dataWatcher) {
throw new Error('Not implemented');
}
getOrCreateDataProducer(name, createDataProducer) {
const existingProducer = this.dataProducers.get(name);
if (existingProducer) {
return existingProducer;
}
const newProducer = createDataProducer(this);
this.dataProducers.set(name, newProducer);
return newProducer;
}
removeDataProducer(name) {
this.dataProducers.delete(name);
}
}
exports.Subchannel = Subchannel;
//# sourceMappingURL=subchannel.js.map

View File

@@ -0,0 +1,34 @@
"use strict";
/*
* Copyright 2019 gRPC authors.
*
* Licensed 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.CIPHER_SUITES = void 0;
exports.getDefaultRootsData = getDefaultRootsData;
const fs = require("fs");
exports.CIPHER_SUITES = process.env.GRPC_SSL_CIPHER_SUITES;
const DEFAULT_ROOTS_FILE_PATH = process.env.GRPC_DEFAULT_SSL_ROOTS_FILE_PATH;
let defaultRootsData = null;
function getDefaultRootsData() {
if (DEFAULT_ROOTS_FILE_PATH) {
if (defaultRootsData === null) {
defaultRootsData = fs.readFileSync(DEFAULT_ROOTS_FILE_PATH);
}
return defaultRootsData;
}
return null;
}
//# sourceMappingURL=tls-helpers.js.map

View File

@@ -0,0 +1,634 @@
"use strict";
/*
* Copyright 2023 gRPC authors.
*
* Licensed 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.Http2SubchannelConnector = void 0;
const http2 = require("http2");
const tls_1 = require("tls");
const channelz_1 = require("./channelz");
const constants_1 = require("./constants");
const http_proxy_1 = require("./http_proxy");
const logging = require("./logging");
const resolver_1 = require("./resolver");
const subchannel_address_1 = require("./subchannel-address");
const uri_parser_1 = require("./uri-parser");
const net = require("net");
const subchannel_call_1 = require("./subchannel-call");
const call_number_1 = require("./call-number");
const TRACER_NAME = 'transport';
const FLOW_CONTROL_TRACER_NAME = 'transport_flowctrl';
const clientVersion = require('../../package.json').version;
const { HTTP2_HEADER_AUTHORITY, HTTP2_HEADER_CONTENT_TYPE, HTTP2_HEADER_METHOD, HTTP2_HEADER_PATH, HTTP2_HEADER_TE, HTTP2_HEADER_USER_AGENT, } = http2.constants;
const KEEPALIVE_TIMEOUT_MS = 20000;
const tooManyPingsData = Buffer.from('too_many_pings', 'ascii');
class Http2Transport {
constructor(session, subchannelAddress, options,
/**
* Name of the remote server, if it is not the same as the subchannel
* address, i.e. if connecting through an HTTP CONNECT proxy.
*/
remoteName) {
this.session = session;
this.options = options;
this.remoteName = remoteName;
/**
* Timer reference indicating when to send the next ping or when the most recent ping will be considered lost.
*/
this.keepaliveTimer = null;
/**
* Indicates that the keepalive timer ran out while there were no active
* calls, and a ping should be sent the next time a call starts.
*/
this.pendingSendKeepalivePing = false;
this.activeCalls = new Set();
this.disconnectListeners = [];
this.disconnectHandled = false;
this.channelzEnabled = true;
this.keepalivesSent = 0;
this.messagesSent = 0;
this.messagesReceived = 0;
this.lastMessageSentTimestamp = null;
this.lastMessageReceivedTimestamp = null;
/* Populate subchannelAddressString and channelzRef before doing anything
* else, because they are used in the trace methods. */
this.subchannelAddressString = (0, subchannel_address_1.subchannelAddressToString)(subchannelAddress);
if (options['grpc.enable_channelz'] === 0) {
this.channelzEnabled = false;
this.streamTracker = new channelz_1.ChannelzCallTrackerStub();
}
else {
this.streamTracker = new channelz_1.ChannelzCallTracker();
}
this.channelzRef = (0, channelz_1.registerChannelzSocket)(this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled);
// Build user-agent string.
this.userAgent = [
options['grpc.primary_user_agent'],
`grpc-node-js/${clientVersion}`,
options['grpc.secondary_user_agent'],
]
.filter(e => e)
.join(' '); // remove falsey values first
if ('grpc.keepalive_time_ms' in options) {
this.keepaliveTimeMs = options['grpc.keepalive_time_ms'];
}
else {
this.keepaliveTimeMs = -1;
}
if ('grpc.keepalive_timeout_ms' in options) {
this.keepaliveTimeoutMs = options['grpc.keepalive_timeout_ms'];
}
else {
this.keepaliveTimeoutMs = KEEPALIVE_TIMEOUT_MS;
}
if ('grpc.keepalive_permit_without_calls' in options) {
this.keepaliveWithoutCalls =
options['grpc.keepalive_permit_without_calls'] === 1;
}
else {
this.keepaliveWithoutCalls = false;
}
session.once('close', () => {
this.trace('session closed');
this.handleDisconnect();
});
session.once('goaway', (errorCode, lastStreamID, opaqueData) => {
let tooManyPings = false;
/* See the last paragraph of
* https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md#basic-keepalive */
if (errorCode === http2.constants.NGHTTP2_ENHANCE_YOUR_CALM &&
opaqueData &&
opaqueData.equals(tooManyPingsData)) {
tooManyPings = true;
}
this.trace('connection closed by GOAWAY with code ' +
errorCode +
' and data ' +
(opaqueData === null || opaqueData === void 0 ? void 0 : opaqueData.toString()));
this.reportDisconnectToOwner(tooManyPings);
});
session.once('error', error => {
this.trace('connection closed with error ' + error.message);
this.handleDisconnect();
});
session.socket.once('close', (hadError) => {
this.trace('connection closed. hadError=' + hadError);
this.handleDisconnect();
});
if (logging.isTracerEnabled(TRACER_NAME)) {
session.on('remoteSettings', (settings) => {
this.trace('new settings received' +
(this.session !== session ? ' on the old connection' : '') +
': ' +
JSON.stringify(settings));
});
session.on('localSettings', (settings) => {
this.trace('local settings acknowledged by remote' +
(this.session !== session ? ' on the old connection' : '') +
': ' +
JSON.stringify(settings));
});
}
/* Start the keepalive timer last, because this can trigger trace logs,
* which should only happen after everything else is set up. */
if (this.keepaliveWithoutCalls) {
this.maybeStartKeepalivePingTimer();
}
if (session.socket instanceof tls_1.TLSSocket) {
this.authContext = {
transportSecurityType: 'ssl',
sslPeerCertificate: session.socket.getPeerCertificate()
};
}
else {
this.authContext = {};
}
}
getChannelzInfo() {
var _a, _b, _c;
const sessionSocket = this.session.socket;
const remoteAddress = sessionSocket.remoteAddress
? (0, subchannel_address_1.stringToSubchannelAddress)(sessionSocket.remoteAddress, sessionSocket.remotePort)
: null;
const localAddress = sessionSocket.localAddress
? (0, subchannel_address_1.stringToSubchannelAddress)(sessionSocket.localAddress, sessionSocket.localPort)
: null;
let tlsInfo;
if (this.session.encrypted) {
const tlsSocket = sessionSocket;
const cipherInfo = tlsSocket.getCipher();
const certificate = tlsSocket.getCertificate();
const peerCertificate = tlsSocket.getPeerCertificate();
tlsInfo = {
cipherSuiteStandardName: (_a = cipherInfo.standardName) !== null && _a !== void 0 ? _a : null,
cipherSuiteOtherName: cipherInfo.standardName ? null : cipherInfo.name,
localCertificate: certificate && 'raw' in certificate ? certificate.raw : null,
remoteCertificate: peerCertificate && 'raw' in peerCertificate
? peerCertificate.raw
: null,
};
}
else {
tlsInfo = null;
}
const socketInfo = {
remoteAddress: remoteAddress,
localAddress: localAddress,
security: tlsInfo,
remoteName: this.remoteName,
streamsStarted: this.streamTracker.callsStarted,
streamsSucceeded: this.streamTracker.callsSucceeded,
streamsFailed: this.streamTracker.callsFailed,
messagesSent: this.messagesSent,
messagesReceived: this.messagesReceived,
keepAlivesSent: this.keepalivesSent,
lastLocalStreamCreatedTimestamp: this.streamTracker.lastCallStartedTimestamp,
lastRemoteStreamCreatedTimestamp: null,
lastMessageSentTimestamp: this.lastMessageSentTimestamp,
lastMessageReceivedTimestamp: this.lastMessageReceivedTimestamp,
localFlowControlWindow: (_b = this.session.state.localWindowSize) !== null && _b !== void 0 ? _b : null,
remoteFlowControlWindow: (_c = this.session.state.remoteWindowSize) !== null && _c !== void 0 ? _c : null,
};
return socketInfo;
}
trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, '(' +
this.channelzRef.id +
') ' +
this.subchannelAddressString +
' ' +
text);
}
keepaliveTrace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, 'keepalive', '(' +
this.channelzRef.id +
') ' +
this.subchannelAddressString +
' ' +
text);
}
flowControlTrace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, FLOW_CONTROL_TRACER_NAME, '(' +
this.channelzRef.id +
') ' +
this.subchannelAddressString +
' ' +
text);
}
internalsTrace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, 'transport_internals', '(' +
this.channelzRef.id +
') ' +
this.subchannelAddressString +
' ' +
text);
}
/**
* Indicate to the owner of this object that this transport should no longer
* be used. That happens if the connection drops, or if the server sends a
* GOAWAY.
* @param tooManyPings If true, this was triggered by a GOAWAY with data
* indicating that the session was closed becaues the client sent too many
* pings.
* @returns
*/
reportDisconnectToOwner(tooManyPings) {
if (this.disconnectHandled) {
return;
}
this.disconnectHandled = true;
this.disconnectListeners.forEach(listener => listener(tooManyPings));
}
/**
* Handle connection drops, but not GOAWAYs.
*/
handleDisconnect() {
this.clearKeepaliveTimeout();
this.reportDisconnectToOwner(false);
for (const call of this.activeCalls) {
call.onDisconnect();
}
// Wait an event loop cycle before destroying the connection
setImmediate(() => {
this.session.destroy();
});
}
addDisconnectListener(listener) {
this.disconnectListeners.push(listener);
}
canSendPing() {
return (!this.session.destroyed &&
this.keepaliveTimeMs > 0 &&
(this.keepaliveWithoutCalls || this.activeCalls.size > 0));
}
maybeSendPing() {
var _a, _b;
if (!this.canSendPing()) {
this.pendingSendKeepalivePing = true;
return;
}
if (this.keepaliveTimer) {
console.error('keepaliveTimeout is not null');
return;
}
if (this.channelzEnabled) {
this.keepalivesSent += 1;
}
this.keepaliveTrace('Sending ping with timeout ' + this.keepaliveTimeoutMs + 'ms');
this.keepaliveTimer = setTimeout(() => {
this.keepaliveTimer = null;
this.keepaliveTrace('Ping timeout passed without response');
this.handleDisconnect();
}, this.keepaliveTimeoutMs);
(_b = (_a = this.keepaliveTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
let pingSendError = '';
try {
const pingSentSuccessfully = this.session.ping((err, duration, payload) => {
this.clearKeepaliveTimeout();
if (err) {
this.keepaliveTrace('Ping failed with error ' + err.message);
this.handleDisconnect();
}
else {
this.keepaliveTrace('Received ping response');
this.maybeStartKeepalivePingTimer();
}
});
if (!pingSentSuccessfully) {
pingSendError = 'Ping returned false';
}
}
catch (e) {
// grpc/grpc-node#2139
pingSendError = (e instanceof Error ? e.message : '') || 'Unknown error';
}
if (pingSendError) {
this.keepaliveTrace('Ping send failed: ' + pingSendError);
this.handleDisconnect();
}
}
/**
* Starts the keepalive ping timer if appropriate. If the timer already ran
* out while there were no active requests, instead send a ping immediately.
* If the ping timer is already running or a ping is currently in flight,
* instead do nothing and wait for them to resolve.
*/
maybeStartKeepalivePingTimer() {
var _a, _b;
if (!this.canSendPing()) {
return;
}
if (this.pendingSendKeepalivePing) {
this.pendingSendKeepalivePing = false;
this.maybeSendPing();
}
else if (!this.keepaliveTimer) {
this.keepaliveTrace('Starting keepalive timer for ' + this.keepaliveTimeMs + 'ms');
this.keepaliveTimer = setTimeout(() => {
this.keepaliveTimer = null;
this.maybeSendPing();
}, this.keepaliveTimeMs);
(_b = (_a = this.keepaliveTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
}
/* Otherwise, there is already either a keepalive timer or a ping pending,
* wait for those to resolve. */
}
/**
* Clears whichever keepalive timeout is currently active, if any.
*/
clearKeepaliveTimeout() {
if (this.keepaliveTimer) {
clearTimeout(this.keepaliveTimer);
this.keepaliveTimer = null;
}
}
removeActiveCall(call) {
this.activeCalls.delete(call);
if (this.activeCalls.size === 0) {
this.session.unref();
}
}
addActiveCall(call) {
this.activeCalls.add(call);
if (this.activeCalls.size === 1) {
this.session.ref();
if (!this.keepaliveWithoutCalls) {
this.maybeStartKeepalivePingTimer();
}
}
}
createCall(metadata, host, method, listener, subchannelCallStatsTracker) {
const headers = metadata.toHttp2Headers();
headers[HTTP2_HEADER_AUTHORITY] = host;
headers[HTTP2_HEADER_USER_AGENT] = this.userAgent;
headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc';
headers[HTTP2_HEADER_METHOD] = 'POST';
headers[HTTP2_HEADER_PATH] = method;
headers[HTTP2_HEADER_TE] = 'trailers';
let http2Stream;
/* In theory, if an error is thrown by session.request because session has
* become unusable (e.g. because it has received a goaway), this subchannel
* should soon see the corresponding close or goaway event anyway and leave
* READY. But we have seen reports that this does not happen
* (https://github.com/googleapis/nodejs-firestore/issues/1023#issuecomment-653204096)
* so for defense in depth, we just discard the session when we see an
* error here.
*/
try {
http2Stream = this.session.request(headers);
}
catch (e) {
this.handleDisconnect();
throw e;
}
this.flowControlTrace('local window size: ' +
this.session.state.localWindowSize +
' remote window size: ' +
this.session.state.remoteWindowSize);
this.internalsTrace('session.closed=' +
this.session.closed +
' session.destroyed=' +
this.session.destroyed +
' session.socket.destroyed=' +
this.session.socket.destroyed);
let eventTracker;
// eslint-disable-next-line prefer-const
let call;
if (this.channelzEnabled) {
this.streamTracker.addCallStarted();
eventTracker = {
addMessageSent: () => {
var _a;
this.messagesSent += 1;
this.lastMessageSentTimestamp = new Date();
(_a = subchannelCallStatsTracker.addMessageSent) === null || _a === void 0 ? void 0 : _a.call(subchannelCallStatsTracker);
},
addMessageReceived: () => {
var _a;
this.messagesReceived += 1;
this.lastMessageReceivedTimestamp = new Date();
(_a = subchannelCallStatsTracker.addMessageReceived) === null || _a === void 0 ? void 0 : _a.call(subchannelCallStatsTracker);
},
onCallEnd: status => {
var _a;
(_a = subchannelCallStatsTracker.onCallEnd) === null || _a === void 0 ? void 0 : _a.call(subchannelCallStatsTracker, status);
this.removeActiveCall(call);
},
onStreamEnd: success => {
var _a;
if (success) {
this.streamTracker.addCallSucceeded();
}
else {
this.streamTracker.addCallFailed();
}
(_a = subchannelCallStatsTracker.onStreamEnd) === null || _a === void 0 ? void 0 : _a.call(subchannelCallStatsTracker, success);
},
};
}
else {
eventTracker = {
addMessageSent: () => {
var _a;
(_a = subchannelCallStatsTracker.addMessageSent) === null || _a === void 0 ? void 0 : _a.call(subchannelCallStatsTracker);
},
addMessageReceived: () => {
var _a;
(_a = subchannelCallStatsTracker.addMessageReceived) === null || _a === void 0 ? void 0 : _a.call(subchannelCallStatsTracker);
},
onCallEnd: status => {
var _a;
(_a = subchannelCallStatsTracker.onCallEnd) === null || _a === void 0 ? void 0 : _a.call(subchannelCallStatsTracker, status);
this.removeActiveCall(call);
},
onStreamEnd: success => {
var _a;
(_a = subchannelCallStatsTracker.onStreamEnd) === null || _a === void 0 ? void 0 : _a.call(subchannelCallStatsTracker, success);
},
};
}
call = new subchannel_call_1.Http2SubchannelCall(http2Stream, eventTracker, listener, this, (0, call_number_1.getNextCallNumber)());
this.addActiveCall(call);
return call;
}
getChannelzRef() {
return this.channelzRef;
}
getPeerName() {
return this.subchannelAddressString;
}
getOptions() {
return this.options;
}
getAuthContext() {
return this.authContext;
}
shutdown() {
this.session.close();
(0, channelz_1.unregisterChannelzRef)(this.channelzRef);
}
}
class Http2SubchannelConnector {
constructor(channelTarget) {
this.channelTarget = channelTarget;
this.session = null;
this.isShutdown = false;
}
trace(text) {
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, (0, uri_parser_1.uriToString)(this.channelTarget) + ' ' + text);
}
createSession(secureConnectResult, address, options) {
if (this.isShutdown) {
return Promise.reject();
}
if (secureConnectResult.socket.closed) {
return Promise.reject('Connection closed before starting HTTP/2 handshake');
}
return new Promise((resolve, reject) => {
var _a, _b, _c, _d, _e, _f, _g;
let remoteName = null;
let realTarget = this.channelTarget;
if ('grpc.http_connect_target' in options) {
const parsedTarget = (0, uri_parser_1.parseUri)(options['grpc.http_connect_target']);
if (parsedTarget) {
realTarget = parsedTarget;
remoteName = (0, uri_parser_1.uriToString)(parsedTarget);
}
}
const scheme = secureConnectResult.secure ? 'https' : 'http';
const targetPath = (0, resolver_1.getDefaultAuthority)(realTarget);
const closeHandler = () => {
var _a;
(_a = this.session) === null || _a === void 0 ? void 0 : _a.destroy();
this.session = null;
// Leave time for error event to happen before rejecting
setImmediate(() => {
if (!reportedError) {
reportedError = true;
reject(`${errorMessage.trim()} (${new Date().toISOString()})`);
}
});
};
const errorHandler = (error) => {
var _a;
(_a = this.session) === null || _a === void 0 ? void 0 : _a.destroy();
errorMessage = error.message;
this.trace('connection failed with error ' + errorMessage);
if (!reportedError) {
reportedError = true;
reject(`${errorMessage} (${new Date().toISOString()})`);
}
};
const sessionOptions = {
createConnection: (authority, option) => {
return secureConnectResult.socket;
},
settings: {
initialWindowSize: (_d = (_a = options['grpc-node.flow_control_window']) !== null && _a !== void 0 ? _a : (_c = (_b = http2.getDefaultSettings) === null || _b === void 0 ? void 0 : _b.call(http2)) === null || _c === void 0 ? void 0 : _c.initialWindowSize) !== null && _d !== void 0 ? _d : 65535,
}
};
const session = http2.connect(`${scheme}://${targetPath}`, sessionOptions);
// Prepare window size configuration for remoteSettings handler
const defaultWin = (_g = (_f = (_e = http2.getDefaultSettings) === null || _e === void 0 ? void 0 : _e.call(http2)) === null || _f === void 0 ? void 0 : _f.initialWindowSize) !== null && _g !== void 0 ? _g : 65535; // 65 535 B
const connWin = options['grpc-node.flow_control_window'];
this.session = session;
let errorMessage = 'Failed to connect';
let reportedError = false;
session.unref();
session.once('remoteSettings', () => {
var _a;
// Send WINDOW_UPDATE now to avoid 65 KB start-window stall.
if (connWin && connWin > defaultWin) {
try {
// Node ≥ 14.18
session.setLocalWindowSize(connWin);
}
catch (_b) {
// Older Node: bump by the delta
const delta = connWin - ((_a = session.state.localWindowSize) !== null && _a !== void 0 ? _a : defaultWin);
if (delta > 0)
session.incrementWindowSize(delta);
}
}
session.removeAllListeners();
secureConnectResult.socket.removeListener('close', closeHandler);
secureConnectResult.socket.removeListener('error', errorHandler);
resolve(new Http2Transport(session, address, options, remoteName));
this.session = null;
});
session.once('close', closeHandler);
session.once('error', errorHandler);
secureConnectResult.socket.once('close', closeHandler);
secureConnectResult.socket.once('error', errorHandler);
});
}
tcpConnect(address, options) {
return (0, http_proxy_1.getProxiedConnection)(address, options).then(proxiedSocket => {
if (proxiedSocket) {
return proxiedSocket;
}
else {
return new Promise((resolve, reject) => {
const closeCallback = () => {
reject(new Error('Socket closed'));
};
const errorCallback = (error) => {
reject(error);
};
const socket = net.connect(address, () => {
socket.removeListener('close', closeCallback);
socket.removeListener('error', errorCallback);
resolve(socket);
});
socket.once('close', closeCallback);
socket.once('error', errorCallback);
});
}
});
}
async connect(address, secureConnector, options) {
if (this.isShutdown) {
return Promise.reject();
}
let tcpConnection = null;
let secureConnectResult = null;
const addressString = (0, subchannel_address_1.subchannelAddressToString)(address);
try {
this.trace(addressString + ' Waiting for secureConnector to be ready');
await secureConnector.waitForReady();
this.trace(addressString + ' secureConnector is ready');
tcpConnection = await this.tcpConnect(address, options);
tcpConnection.setNoDelay();
this.trace(addressString + ' Established TCP connection');
secureConnectResult = await secureConnector.connect(tcpConnection);
this.trace(addressString + ' Established secure connection');
return this.createSession(secureConnectResult, address, options);
}
catch (e) {
tcpConnection === null || tcpConnection === void 0 ? void 0 : tcpConnection.destroy();
secureConnectResult === null || secureConnectResult === void 0 ? void 0 : secureConnectResult.socket.destroy();
throw e;
}
}
shutdown() {
var _a;
this.isShutdown = true;
(_a = this.session) === null || _a === void 0 ? void 0 : _a.close();
this.session = null;
}
}
exports.Http2SubchannelConnector = Http2SubchannelConnector;
//# sourceMappingURL=transport.js.map

View File

@@ -0,0 +1,125 @@
"use strict";
/*
* Copyright 2020 gRPC authors.
*
* Licensed 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.parseUri = parseUri;
exports.splitHostPort = splitHostPort;
exports.combineHostPort = combineHostPort;
exports.uriToString = uriToString;
/*
* The groups correspond to URI parts as follows:
* 1. scheme
* 2. authority
* 3. path
*/
const URI_REGEX = /^(?:([A-Za-z0-9+.-]+):)?(?:\/\/([^/]*)\/)?(.+)$/;
function parseUri(uriString) {
const parsedUri = URI_REGEX.exec(uriString);
if (parsedUri === null) {
return null;
}
return {
scheme: parsedUri[1],
authority: parsedUri[2],
path: parsedUri[3],
};
}
const NUMBER_REGEX = /^\d+$/;
function splitHostPort(path) {
if (path.startsWith('[')) {
const hostEnd = path.indexOf(']');
if (hostEnd === -1) {
return null;
}
const host = path.substring(1, hostEnd);
/* Only an IPv6 address should be in bracketed notation, and an IPv6
* address should have at least one colon */
if (host.indexOf(':') === -1) {
return null;
}
if (path.length > hostEnd + 1) {
if (path[hostEnd + 1] === ':') {
const portString = path.substring(hostEnd + 2);
if (NUMBER_REGEX.test(portString)) {
return {
host: host,
port: +portString,
};
}
else {
return null;
}
}
else {
return null;
}
}
else {
return {
host,
};
}
}
else {
const splitPath = path.split(':');
/* Exactly one colon means that this is host:port. Zero colons means that
* there is no port. And multiple colons means that this is a bare IPv6
* address with no port */
if (splitPath.length === 2) {
if (NUMBER_REGEX.test(splitPath[1])) {
return {
host: splitPath[0],
port: +splitPath[1],
};
}
else {
return null;
}
}
else {
return {
host: path,
};
}
}
}
function combineHostPort(hostPort) {
if (hostPort.port === undefined) {
return hostPort.host;
}
else {
// Only an IPv6 host should include a colon
if (hostPort.host.includes(':')) {
return `[${hostPort.host}]:${hostPort.port}`;
}
else {
return `${hostPort.host}:${hostPort.port}`;
}
}
}
function uriToString(uri) {
let result = '';
if (uri.scheme !== undefined) {
result += uri.scheme + ':';
}
if (uri.authority !== undefined) {
result += '//' + uri.authority + '/';
}
result += uri.path;
return result;
}
//# sourceMappingURL=uri-parser.js.map

View File

@@ -0,0 +1,246 @@
"use strict";
/**
* @license
* Copyright 2018 gRPC authors.
*
* Licensed 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.loadFileDescriptorSetFromObject = exports.loadFileDescriptorSetFromBuffer = exports.fromJSON = exports.loadSync = exports.load = exports.IdempotencyLevel = exports.isAnyExtension = exports.Long = void 0;
const camelCase = require("lodash.camelcase");
const Protobuf = require("protobufjs");
const descriptor = require("protobufjs/ext/descriptor");
const util_1 = require("./util");
const Long = require("long");
exports.Long = Long;
function isAnyExtension(obj) {
return ('@type' in obj) && (typeof obj['@type'] === 'string');
}
exports.isAnyExtension = isAnyExtension;
var IdempotencyLevel;
(function (IdempotencyLevel) {
IdempotencyLevel["IDEMPOTENCY_UNKNOWN"] = "IDEMPOTENCY_UNKNOWN";
IdempotencyLevel["NO_SIDE_EFFECTS"] = "NO_SIDE_EFFECTS";
IdempotencyLevel["IDEMPOTENT"] = "IDEMPOTENT";
})(IdempotencyLevel = exports.IdempotencyLevel || (exports.IdempotencyLevel = {}));
const descriptorOptions = {
longs: String,
enums: String,
bytes: String,
defaults: true,
oneofs: true,
json: true,
};
function joinName(baseName, name) {
if (baseName === '') {
return name;
}
else {
return baseName + '.' + name;
}
}
function isHandledReflectionObject(obj) {
return (obj instanceof Protobuf.Service ||
obj instanceof Protobuf.Type ||
obj instanceof Protobuf.Enum);
}
function isNamespaceBase(obj) {
return obj instanceof Protobuf.Namespace || obj instanceof Protobuf.Root;
}
function getAllHandledReflectionObjects(obj, parentName) {
const objName = joinName(parentName, obj.name);
if (isHandledReflectionObject(obj)) {
return [[objName, obj]];
}
else {
if (isNamespaceBase(obj) && typeof obj.nested !== 'undefined') {
return Object.keys(obj.nested)
.map(name => {
return getAllHandledReflectionObjects(obj.nested[name], objName);
})
.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
}
}
return [];
}
function createDeserializer(cls, options) {
return function deserialize(argBuf) {
return cls.toObject(cls.decode(argBuf), options);
};
}
function createSerializer(cls) {
return function serialize(arg) {
if (Array.isArray(arg)) {
throw new Error(`Failed to serialize message: expected object with ${cls.name} structure, got array instead`);
}
const message = cls.fromObject(arg);
return cls.encode(message).finish();
};
}
function mapMethodOptions(options) {
return (options || []).reduce((obj, item) => {
for (const [key, value] of Object.entries(item)) {
switch (key) {
case 'uninterpreted_option':
obj.uninterpreted_option.push(item.uninterpreted_option);
break;
default:
obj[key] = value;
}
}
return obj;
}, {
deprecated: false,
idempotency_level: IdempotencyLevel.IDEMPOTENCY_UNKNOWN,
uninterpreted_option: [],
});
}
function createMethodDefinition(method, serviceName, options, fileDescriptors) {
/* This is only ever called after the corresponding root.resolveAll(), so we
* can assume that the resolved request and response types are non-null */
const requestType = method.resolvedRequestType;
const responseType = method.resolvedResponseType;
return {
path: '/' + serviceName + '/' + method.name,
requestStream: !!method.requestStream,
responseStream: !!method.responseStream,
requestSerialize: createSerializer(requestType),
requestDeserialize: createDeserializer(requestType, options),
responseSerialize: createSerializer(responseType),
responseDeserialize: createDeserializer(responseType, options),
// TODO(murgatroid99): Find a better way to handle this
originalName: camelCase(method.name),
requestType: createMessageDefinition(requestType, options, fileDescriptors),
responseType: createMessageDefinition(responseType, options, fileDescriptors),
options: mapMethodOptions(method.parsedOptions),
};
}
function createServiceDefinition(service, name, options, fileDescriptors) {
const def = {};
for (const method of service.methodsArray) {
def[method.name] = createMethodDefinition(method, name, options, fileDescriptors);
}
return def;
}
function createMessageDefinition(message, options, fileDescriptors) {
const messageDescriptor = message.toDescriptor('proto3');
return {
format: 'Protocol Buffer 3 DescriptorProto',
type: messageDescriptor.$type.toObject(messageDescriptor, descriptorOptions),
fileDescriptorProtos: fileDescriptors,
serialize: createSerializer(message),
deserialize: createDeserializer(message, options)
};
}
function createEnumDefinition(enumType, fileDescriptors) {
const enumDescriptor = enumType.toDescriptor('proto3');
return {
format: 'Protocol Buffer 3 EnumDescriptorProto',
type: enumDescriptor.$type.toObject(enumDescriptor, descriptorOptions),
fileDescriptorProtos: fileDescriptors,
};
}
/**
* function createDefinition(obj: Protobuf.Service, name: string, options:
* Options): ServiceDefinition; function createDefinition(obj: Protobuf.Type,
* name: string, options: Options): MessageTypeDefinition; function
* createDefinition(obj: Protobuf.Enum, name: string, options: Options):
* EnumTypeDefinition;
*/
function createDefinition(obj, name, options, fileDescriptors) {
if (obj instanceof Protobuf.Service) {
return createServiceDefinition(obj, name, options, fileDescriptors);
}
else if (obj instanceof Protobuf.Type) {
return createMessageDefinition(obj, options, fileDescriptors);
}
else if (obj instanceof Protobuf.Enum) {
return createEnumDefinition(obj, fileDescriptors);
}
else {
throw new Error('Type mismatch in reflection object handling');
}
}
function createPackageDefinition(root, options) {
const def = {};
root.resolveAll();
const descriptorList = root.toDescriptor('proto3').file;
const bufferList = descriptorList.map(value => Buffer.from(descriptor.FileDescriptorProto.encode(value).finish()));
for (const [name, obj] of getAllHandledReflectionObjects(root, '')) {
def[name] = createDefinition(obj, name, options, bufferList);
}
return def;
}
function createPackageDefinitionFromDescriptorSet(decodedDescriptorSet, options) {
options = options || {};
const root = Protobuf.Root.fromDescriptor(decodedDescriptorSet);
root.resolveAll();
return createPackageDefinition(root, options);
}
/**
* Load a .proto file with the specified options.
* @param filename One or multiple file paths to load. Can be an absolute path
* or relative to an include path.
* @param options.keepCase Preserve field names. The default is to change them
* to camel case.
* @param options.longs The type that should be used to represent `long` values.
* Valid options are `Number` and `String`. Defaults to a `Long` object type
* from a library.
* @param options.enums The type that should be used to represent `enum` values.
* The only valid option is `String`. Defaults to the numeric value.
* @param options.bytes The type that should be used to represent `bytes`
* values. Valid options are `Array` and `String`. The default is to use
* `Buffer`.
* @param options.defaults Set default values on output objects. Defaults to
* `false`.
* @param options.arrays Set empty arrays for missing array values even if
* `defaults` is `false`. Defaults to `false`.
* @param options.objects Set empty objects for missing object values even if
* `defaults` is `false`. Defaults to `false`.
* @param options.oneofs Set virtual oneof properties to the present field's
* name
* @param options.json Represent Infinity and NaN as strings in float fields,
* and automatically decode google.protobuf.Any values.
* @param options.includeDirs Paths to search for imported `.proto` files.
*/
function load(filename, options) {
return (0, util_1.loadProtosWithOptions)(filename, options).then(loadedRoot => {
return createPackageDefinition(loadedRoot, options);
});
}
exports.load = load;
function loadSync(filename, options) {
const loadedRoot = (0, util_1.loadProtosWithOptionsSync)(filename, options);
return createPackageDefinition(loadedRoot, options);
}
exports.loadSync = loadSync;
function fromJSON(json, options) {
options = options || {};
const loadedRoot = Protobuf.Root.fromJSON(json);
loadedRoot.resolveAll();
return createPackageDefinition(loadedRoot, options);
}
exports.fromJSON = fromJSON;
function loadFileDescriptorSetFromBuffer(descriptorSet, options) {
const decodedDescriptorSet = descriptor.FileDescriptorSet.decode(descriptorSet);
return createPackageDefinitionFromDescriptorSet(decodedDescriptorSet, options);
}
exports.loadFileDescriptorSetFromBuffer = loadFileDescriptorSetFromBuffer;
function loadFileDescriptorSetFromObject(descriptorSet, options) {
const decodedDescriptorSet = descriptor.FileDescriptorSet.fromObject(descriptorSet);
return createPackageDefinitionFromDescriptorSet(decodedDescriptorSet, options);
}
exports.loadFileDescriptorSetFromObject = loadFileDescriptorSetFromObject;
(0, util_1.addCommonProtos)();
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1,89 @@
"use strict";
/**
* @license
* Copyright 2018 gRPC authors.
*
* Licensed 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.addCommonProtos = exports.loadProtosWithOptionsSync = exports.loadProtosWithOptions = void 0;
const fs = require("fs");
const path = require("path");
const Protobuf = require("protobufjs");
function addIncludePathResolver(root, includePaths) {
const originalResolvePath = root.resolvePath;
root.resolvePath = (origin, target) => {
if (path.isAbsolute(target)) {
return target;
}
for (const directory of includePaths) {
const fullPath = path.join(directory, target);
try {
fs.accessSync(fullPath, fs.constants.R_OK);
return fullPath;
}
catch (err) {
continue;
}
}
process.emitWarning(`${target} not found in any of the include paths ${includePaths}`);
return originalResolvePath(origin, target);
};
}
async function loadProtosWithOptions(filename, options) {
const root = new Protobuf.Root();
options = options || {};
if (!!options.includeDirs) {
if (!Array.isArray(options.includeDirs)) {
return Promise.reject(new Error('The includeDirs option must be an array'));
}
addIncludePathResolver(root, options.includeDirs);
}
const loadedRoot = await root.load(filename, options);
loadedRoot.resolveAll();
return loadedRoot;
}
exports.loadProtosWithOptions = loadProtosWithOptions;
function loadProtosWithOptionsSync(filename, options) {
const root = new Protobuf.Root();
options = options || {};
if (!!options.includeDirs) {
if (!Array.isArray(options.includeDirs)) {
throw new Error('The includeDirs option must be an array');
}
addIncludePathResolver(root, options.includeDirs);
}
const loadedRoot = root.loadSync(filename, options);
loadedRoot.resolveAll();
return loadedRoot;
}
exports.loadProtosWithOptionsSync = loadProtosWithOptionsSync;
/**
* Load Google's well-known proto files that aren't exposed by Protobuf.js.
*/
function addCommonProtos() {
// Protobuf.js exposes: any, duration, empty, field_mask, struct, timestamp,
// and wrappers. compiler/plugin is excluded in Protobuf.js and here.
// Using constant strings for compatibility with tools like Webpack
const apiDescriptor = require('protobufjs/google/protobuf/api.json');
const descriptorDescriptor = require('protobufjs/google/protobuf/descriptor.json');
const sourceContextDescriptor = require('protobufjs/google/protobuf/source_context.json');
const typeDescriptor = require('protobufjs/google/protobuf/type.json');
Protobuf.common('api', apiDescriptor.nested.google.nested.protobuf.nested);
Protobuf.common('descriptor', descriptorDescriptor.nested.google.nested.protobuf.nested);
Protobuf.common('source_context', sourceContextDescriptor.nested.google.nested.protobuf.nested);
Protobuf.common('type', typeDescriptor.nested.google.nested.protobuf.nested);
}
exports.addCommonProtos = addCommonProtos;
//# sourceMappingURL=util.js.map

File diff suppressed because it is too large Load Diff