UNPKG

grpc-gcp

Version:

Extension for supporting Google Cloud Platform specific features for gRPC.

204 lines 8.82 kB
"use strict"; 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