grpc-gcp
Version:
Extension for supporting Google Cloud Platform specific features for gRPC.
204 lines • 8.82 kB
JavaScript
;
const util = require("util");
const gcp_channel_factory_1 = require("./gcp_channel_factory");
const protoRoot = require("./generated/grpc_gcp");
var ApiConfig = protoRoot.grpc.gcp.ApiConfig;
var AffinityConfig = protoRoot.grpc.gcp.AffinityConfig;
const setup = (grpc) => {
const GcpChannelFactory = (0, gcp_channel_factory_1.getGcpChannelFactoryClass)(grpc);
/**
* Create ApiConfig proto message from config object.
* @param apiDefinition Api object that specifies channel pool configuation.
* @return A protobuf message type.
*/
function createGcpApiConfig(apiDefinition) {
return ApiConfig.fromObject(apiDefinition);
}
/**
* Function for creating a gcp channel factory.
* @memberof grpc-gcp
* @param address The address of the server to connect to.
* @param credentials Channel credentials to use when connecting
* @param options A map of channel options that will be passed to the core.
* @return {GcpChannelFactory} A GcpChannelFactory instance.
*/
function gcpChannelFactoryOverride(address, credentials, options) {
return new GcpChannelFactory(address, credentials, options);
}
/**
* Pass in call properties and return a new object with modified values.
* This function will be used together with gcpChannelFactoryOverride
* when constructing a grpc Client.
* @memberof grpc-gcp
* @param callProperties Call properties with channel factory object.
* @return Modified call properties with selected grpc channel object.
*/
function gcpCallInvocationTransformer(callProperties) {
if (!callProperties.channel || !(callProperties.channel instanceof GcpChannelFactory)) {
// The gcpCallInvocationTransformer needs to use gcp channel factory.
return callProperties;
}
const channelFactory = callProperties.channel;
const argument = callProperties.argument;
const metadata = callProperties.metadata;
const call = callProperties.call;
const methodDefinition = callProperties.methodDefinition;
const path = methodDefinition.path;
const callOptions = callProperties.callOptions;
const callback = callProperties.callback;
const preProcessResult = preProcess(channelFactory, path, argument);
const channelRef = preProcessResult.channelRef;
const boundKey = preProcessResult.boundKey;
const postProcessInterceptor = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
options, nextCall) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let firstMessage;
const requester = {
start: (metadata, listener, next) => {
const newListener = {
onReceiveMetadata: (metadata, next) => {
next(metadata);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onReceiveMessage: (message, next) => {
if (!firstMessage)
firstMessage = message;
next(message);
},
onReceiveStatus: (status, next) => {
if (status.code === grpc.status.OK) {
postProcess(channelFactory, channelRef, path, boundKey, firstMessage);
}
next(status);
},
};
next(metadata, newListener);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sendMessage: (message, next) => {
next(message);
},
halfClose: (next) => {
next();
},
cancel: (next) => {
next();
},
};
return new grpc.InterceptingCall(nextCall(options), requester);
};
// Append interceptor to existing interceptors list.
const newCallOptions = Object.assign({}, callOptions);
const interceptors = callOptions.interceptors
? callOptions.interceptors
: [];
newCallOptions.interceptors = interceptors.concat([postProcessInterceptor]);
if (channelFactory.shouldRequestDebugHeaders(channelRef.getDebugHeadersRequestedAt())) {
metadata.set('x-return-encrypted-headers', 'all_response');
channelRef.notifyDebugHeadersRequested();
}
return {
argument,
metadata,
call,
channel: channelRef.getChannel(),
methodDefinition,
callOptions: newCallOptions,
callback,
};
}
/**
* Handle channel affinity and pick a channel before call starts.
* @param channelFactory The channel management factory.
* @param path Method path.
* @param argument The request arguments object.
* @return Result containing bound affinity key and the chosen channel ref
* object.
*/
function preProcess(channelFactory, path,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
argument) {
const affinityConfig = channelFactory.getAffinityConfig(path);
let boundKey;
if (argument && affinityConfig) {
const command = affinityConfig.command;
if (command === AffinityConfig.Command.BOUND ||
command === AffinityConfig.Command.UNBIND) {
boundKey = getAffinityKeyFromMessage(affinityConfig.affinityKey, argument);
}
}
const channelRef = channelFactory.getChannelRef(boundKey);
channelRef.activeStreamsCountIncr();
return {
boundKey,
channelRef,
};
}
/**
* Handle channel affinity and streams count after call is done.
* @param channelFactory The channel management factory.
* @param channelRef ChannelRef instance that contains a real grpc channel.
* @param path Method path.
* @param boundKey Affinity key bound to a channel.
* @param responseMsg Response proto message.
*/
function postProcess(channelFactory, channelRef, path, boundKey,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
responseMsg) {
if (!channelFactory || !responseMsg)
return;
const affinityConfig = channelFactory.getAffinityConfig(path);
if (affinityConfig && affinityConfig.command) {
const command = affinityConfig.command;
if (command === AffinityConfig.Command.BIND) {
const affinityKey = getAffinityKeyFromMessage(affinityConfig.affinityKey, responseMsg);
channelFactory.bind(channelRef, affinityKey);
}
else if (command === AffinityConfig.Command.UNBIND) {
channelFactory.unbind(boundKey);
}
}
channelRef.activeStreamsCountDecr();
}
/**
* Retrieve affinity key specified in the proto message.
* @param affinityKeyName affinity key locator.
* @param message proto message that contains affinity info.
* @return Affinity key string.
*/
function getAffinityKeyFromMessage(affinityKeyName,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
message) {
if (affinityKeyName) {
let currMessage = message;
const names = affinityKeyName.split('.');
let i = 0;
for (; i < names.length; i++) {
if (currMessage[names[i]]) {
// check if the proto message is generated by protobufjs.
currMessage = currMessage[names[i]];
}
else {
// otherwise use jspb format.
const getter = 'get' + names[i].charAt(0).toUpperCase() + names[i].substr(1);
if (!currMessage || typeof currMessage[getter] !== 'function')
break;
currMessage = currMessage[getter]();
}
}
if (i !== 0 && i === names.length)
return currMessage;
}
console.error(util.format('Cannot find affinity value from proto message using affinity_key: %s.', affinityKeyName));
return '';
}
return {
createGcpApiConfig,
gcpChannelFactoryOverride,
gcpCallInvocationTransformer,
GcpChannelFactory,
};
};
module.exports = (grpc) => setup(grpc);
//# sourceMappingURL=index.js.map