UNPKG

firecomm

Version:

A complete framework for gRPC-node.js

416 lines (396 loc) 14.9 kB
const grpc = require("grpc"); const fs = require("fs"); // import health check methods const healthcheck = require("./custom-services/healthcheck-pkg"); const checkHandler = require("./custom-services/healthcheck-handlers/check"); const watchHandler = require("./custom-services/healthcheck-handlers/watch"); const compose = require("./compose"); /** * @class To run your service methods, you will need to create a gRPC server. Firecomm extends the native gRPC-node `Server` class, so any and all methods found on the native class are supported by Firecomm's class. The Firecomm `Server` class-instances have the following capabilities. - Instantiate an arbitrary number secure and insecure sockets with `.bind()` - Add your service definition and middleware functions with `.addService()` - Fire up the server with `.start()` - All other native gRPC `Server` class methods * @example const { Server } = require( "firecomm" ); const server = new Server( ERROR_HANDLER ); * @param {function} uncaughtErrorHandler Function that will handler * any errors thrown by a method handler, error handlers defined here, * will apply to errors thrown by handlers from all services added to * this server instance * @returns {Server} An instance of the `Server` class * @example const { Server } = require( "firecomm" ); const server = new Server(function( ERROR, CALL ) { if ( ERROR ) CALL.throw( ERROR ); }); */ module.exports = class Server extends grpc.Server { constructor(/** @type {function} */ uncaughtErrorHandler, options) { // user put both handler and options in the right order if ( typeof uncaughtErrorHandler === "function" && typeof options === "object" ) { super(options); this.uncaughtErrorHandler = uncaughtErrorHandler; // user put options in the first and left options undefined with no error handler } else if ( typeof uncaughtErrorHandler === "function" && options === undefined ) { super(); this.uncaughtErrorHandler = uncaughtErrorHandler; // user passed options first and left options as error handler } else if ( typeof uncaughtErrorHandler === "object" && typeof options === "function" ) { super(uncaughtErrorHandler); this.uncaughtErrorHandler = options; // user passed options first and left error handler empty } else if ( typeof uncaughtErrorHandler === "object" && options === undefined ) { super(uncaughtErrorHandler); this.uncaughtErrorHandler = options; // user passed both as undefined } else if (uncaughtErrorHandler === undefined && options === undefined) { super(); this.uncaughtErrorHandler = uncaughtErrorHandler; } else { throw new Error( "TypeError: Optional arguments supported are config object and error handling function" ); } this._statusMap = {}; this.addService(healthcheck.HealthCheck, { check: checkHandler.bind(this), watch: watchHandler.bind(this) }); this._ports = []; } /** * Connects handlers and middleware functionality to your gRPC methods * as defined in your `.proto` file. * @example const { Server } = require( 'firecomm' ); * const server = new Server(); * server.addService( * SERVICE, RPC_METHODS_OBJECT, SERVICE_MIDDLEWARE, ERROR_HANDLER ); * @param {Object} serviceDefinition Service as it is named on your `.proto` file. Is a property on the built package. * @param {Object} methodHandlers maps each `RPC_METHOD` to its `HANDLER_FUNCTION` or `MIDDLEWARE_STACK`. * @param {function | []} serviceMiddleware Either a middleware function or an array of middleware functions that will add to the front of the middleware stack for every method in the service. * @param {function} uncaughtErrorHandler function that handles uncaught errors thrown in any of your service method handlers for this service, overwrites the `Server` level error handler if one has been specified * @example const { Server, build } = require( "firecomm" ); const package = build('./PROTO_PATH'); const middleware1 = (call) => { console.log(call.req.body); } const middleware2 = (call) => { console.log(call.meta); } const handler = (call) => { call.send({ message: "hello world" }) } const server = new Server(); server.addService( package.serviceName, { handlerName: [ middleware2, handler ] }, middleware1, function( ERROR, CALL ) { if ( ERROR ) CALL.throw( ERROR ); } ); */ addService( /** @type {{}}} */ serviceDefinition, /** @type {{}} */ handlerObject, /** @type {(function|[]} */ serviceMiddleware, /** @type {function} */ uncaughtErrorHandler ) { // serviceDefinition is from routeGuide // checking to see if they are inputting Service.service or Service let service; if (!serviceDefinition.hasOwnProperty("service")) { throw new Error( "Must not access the service property on the .proto service from package definition. Simply pass packageName.serviceName" ); } else { service = serviceDefinition.service; } // console.log(service); // add to the status map this._statusMap[serviceDefinition._serviceName] = "SERVING"; // handlerObject ~= { unaryChat: [waitFor, unaryChat], serverStream, // clientStream, bidiChat } // handlers ~= { unaryChat: [ [Function], [Function: unaryChat] ], // serverStream: [Function: serverStream], // clientStream: [Function: clientStream], // bidiChat: [Function: bidiChat] } // keys ~= [ 'unaryChat', 'serverStream', 'clientStream', 'bidiChat' ] const handlers = { ...handlerObject }; const keys = Object.keys(handlerObject); for (let i = 0; i < keys.length; i++) { let middlewareStack = handlerObject[keys[i]]; if (serviceMiddleware) { if (Array.isArray(serviceMiddleware)) { middlewareStack = serviceMiddleware.concat(middlewareStack); } else { middlewareStack = [serviceMiddleware].concat(middlewareStack); } } else { if (typeof middlewareStack === "function") { middlewareStack = [middlewareStack]; } } // define the error handler if (!uncaughtErrorHandler) { uncaughtErrorHandler = this.uncaughtErrorHandler; } handlers[keys[i]] = compose( { handler: keys[i], middlewareStack }, service, uncaughtErrorHandler ); } // console.log('handlers:', handlers); super.addService(service, handlers); return this; // console.log(this instanceof grpc.Server) } setStatus(serviceName, status) { if (!this._statusMap.hasOwnProperty(serviceName)) { throw new Error( "Status map does not have the service name you requested." ); } this._statusMap[serviceName] = status; } /** * * @param {string=} serviceName * If serviceName not provided, returns whole status map. * If serviceName passed, returns the status of that service or null if it is not found in the statusMap * */ getStatus(serviceName) { if (serviceName === undefined) { return { ...this._statusMap }; } else if (!this._statusMap.hasOwnProperty(serviceName)) { return null; } else { return this._statusMap[serviceName]; } } /** * Invoke this method to connect your server instance to a channel. * Allowsfor the creation of secure and insecure channels. * @param {string|[string]} PORT Socket in the form of * `IP`:`PORT`forFirecomm `Server` class to listen on. * @param {{}=} [config] if no config is supplied, an insecure * channelwill be created (no TLS handshake required and traffic * unencrypted, tocreate a secure connection, supply the paths to an RSA * private key and certificate * @example const { Server } = require( "firecomm" ); const privateKey = require( "./certificate.crt" ) const certificate = require( "./private_key.key" ) const server = new Server(); server.bind( '0.0.0.0:3000', {certificate, privateKey } ) */ bind(ports, sslConfigs) { const boundPorts = []; let readCertificate; let readPrivateKey; if (typeof ports !== "string" && !Array.isArray(ports) && !sslConfigs) { throw new Error("PORT must be a string or array of strings."); } else if (typeof ports === "string" && !sslConfigs) { boundPorts.push( super.bind(ports, grpc.ServerCredentials.createInsecure()) ); } else if ( typeof ports === "string" && !Array.isArray(sslConfigs) && typeof sslConfigs === "object" ) { readCertificate = fs.readFileSync(sslConfigs.certificate); readPrivateKey = fs.readFileSync(sslConfigs.privateKey); boundPorts.push( super.bind( ports, grpc.ServerCredentials.createSsl(readCertificate, [ { private_key: readPrivateKey, cert_chain: readCertificate } ]) ) ); } else if (typeof ports === "string" && Array.isArray(sslConfigs)) { readCertificate = fs.readFileSync(sslConfigs[0].certificate); readPrivateKey = fs.readFileSync(sslConfigs[0].privateKey); boundPorts.push( super.bind( ports, grpc.ServerCredentials.createSsl(readCertificate, [ { private_key: readPrivateKey, cert_chain: readCertificate } ]) ) ); } else if (Array.isArray(ports) && !sslConfigs) { console.log(sslConfigs); ports.forEach(port => { boundPorts.push( super.bind(port, grpc.ServerCredentials.createInsecure()) ); }); } else if ( Array.isArray(ports) && !Array.isArray(sslConfigs) && typeof sslConfigs === "object" ) { readCertificate = fs.readFileSync(sslConfigs.certificate); readPrivateKey = fs.readFileSync(sslConfigs.privateKey); ports.forEach(port => { boundPorts.push( super.bind( port, grpc.ServerCredentials.createSsl(readCertificate, [ { private_key: readPrivateKey, cert_chain: readCertificate } ]) ) ); }); } else if ( Array.isArray(ports) && Array.isArray(sslConfigs) && ports.length !== sslConfigs.length ) { readCertificate = fs.readFileSync(sslConfigs[0].certificate); readPrivateKey = fs.readFileSync(sslConfigs[0].privateKey); ports.forEach((port, index) => { boundPorts.push( super.bind( port, grpc.ServerCredentials.createSsl(readCertificate, [ { private_key: readPrivateKey, cert_chain: readCertificate } ]) ) ); }); } else if ( Array.isArray(ports) && Array.isArray(sslConfigs) && ports.length === sslConfigs.length ) { ports.forEach((port, index) => { readCertificate = fs.readFileSync(sslConfigs[0].certificate); readPrivateKey = fs.readFileSync(sslConfigs[0].privateKey); boundPorts.push( super.bind( port, grpc.ServerCredentials.createSsl(readCertificate, [ { private_key: readPrivateKey, cert_chain: readCertificate } ]) ) ); }); } // Future: support an array of ports or unlimited args // let ports = []; // let configs = []; // if (!Array.isArray(PORT)) { // ports.push(PORT); // } else { // // going to assume its not an object and is in fact an array // PORT.forEach(e => ports.push(e)); // } // if (!Array.isArray(config)) { // configs.push(config); // } else { // // going to assume its not an object and is in fact an array // config.forEach(e => configs.push(e)); // } // if (ports.length !== configs.length && configs.length !== 1) { // throw new Error( // "Ports and configs lengths should match, unless you want the same certificate and key to encrypt all ports. In that case, ports is an array, and supply a single config." // ); // } // let singleConfig = configs.length === 1; // const boundPorts = []; // for (let i = 0; i < ports.length; i++) { // // if config does not exist, create an Insecure server // // else, take the contents of config (certificate and private key) and // // apply them to create an encrypted connection // // if (singleConfig && config === undefined) { // // boundPorts.push( // // super.bindAsync(ports[i], grpc.ServerCredentials.createInsecure()) // // ); // // continue; // // } // // else if (!configs[i]) { // // boundPorts.push( // // super.bindAsync(ports[i], grpc.ServerCredentials.createInsecure()) // // ); // // } else { // // // we are deconstructing keys here that have the PATH value to the file // // let privateKey; // // let certificate; // // if (singleConfig) { // // const { // // privateKey: unreadPrivateKey, // // certificate: unreadCertificate // // } = configs[0]; // // privateKey = unreadPrivateKey; // // certificate = unreadCertificate; // // } else { // // const { // // privateKey: unreadPrivateKey, // // certificate: unreadCertificate // // } = configs[i]; // // privateKey = unreadPrivateKey; // // certificate = unreadCertificate; // // } // // if (privateKey === undefined || certificate === undefined) { // // throw new Error( // // "If supplying credentials, config object must have privateKey and certificate properties" // // ); // // } // // // creating buffer data types here // // let readCertificate = fs.readFileSync(certificate); // // let readPrivateKey = fs.readFileSync(privateKey); // // // create an array which is "A list of private key and certificate chain // // // pairs to be used for authenticating the server" // // let listOfObjects = [ // // { private_key: readPrivateKey, cert_chain: readCertificate } // // ]; // // boundPorts.push( // // super.bindAsync( // // ports[i], // // grpc.ServerCredentials.createSsl(readCertificate, listOfObjects) // // ) // // ); // } // } this._ports = this._ports.concat(boundPorts); return this; } /** * Starts your server instance. */ start() { super.start(); } };