diff --git a/README.md b/README.md index a04f43f..c3f1869 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,14 @@ ## Install ```bash -$ npm i egg-grpc --save +$ npm i egg-grpc-ssl --save ``` ```js // {app_root}/config/plugin.js exports.grpc = { enable: true, - package: 'egg-grpc', + package: 'egg-grpc-ssl', }; ``` @@ -41,10 +41,23 @@ exports.grpc = { ```js // {app_root}/config/config.default.js exports.grpc = { - endpoint: 'localhost:50051', - // dir: 'app/proto', // proto files dir, relative path - // property: 'grpc', // default attach to `ctx.grpc.**` - // loadOpts: { convertFieldsToCamelCase: true, }, // message field case: `string user_name` -> `userName` + clients: { + grpc1: { + property: 'grpc1', // default attach to `ctx.grpc.**` + endpoint: 'localhost:50051', + // dir: 'app/proto', // proto files dir, relative path + // loadOpts: { convertFieldsToCamelCase: true }, // message field case: `string user_name` -> `userName` + clientSsl: { + enable: false, + // grpc.credentials.createSsl + rootCerts: 'config/cert/server.crt', + options: { + 'grpc.ssl_target_name_override': 'example.server', + 'grpc.default_authority': 'example.server', + }, + }, + }, + }, }; ``` @@ -211,11 +224,11 @@ stream.end(data3); ## Example -see [grpc.tests.js](test/grpc.tests.js). +see [test/grpc.tests.js](test/grpc.tests.js). ## Questions & Suggestions -Please open an issue [here](https://github.com/eggjs/egg/issues). +Please open an issue [here](https://github.com/xdxiaodong/egg-grpc/issues). ## License diff --git a/README.zh_CN.md b/README.zh_CN.md index 7958314..8d4840b 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -25,14 +25,14 @@ ## 安装 ```bash -$ npm i egg-grpc --save +$ npm i egg-grpc-ssl --save ``` ```js // {app_root}/config/plugin.js exports.grpc = { enable: true, - package: 'egg-grpc', + package: 'egg-grpc-ssl', }; ``` @@ -41,10 +41,23 @@ exports.grpc = { ```js // {app_root}/config/config.default.js exports.grpc = { - endpoint: 'localhost:50051', - // dir: 'app/proto', // proto 文件目录,相对路径 - // property: 'grpc', // 默认挂载到 `ctx.grpc.**` - // loadOpts: { convertFieldsToCamelCase: true, }, // message field case: `string user_name` -> `userName` + clients: { + grpc1: { + property: 'grpc1', // default attach to `ctx.grpc.**` + endpoint: 'localhost:50051', + // dir: 'app/proto', // proto files dir, relative path + // loadOpts: { convertFieldsToCamelCase: true }, // message field case: `string user_name` -> `userName` + clientSsl: { + enable: false, + // grpc.credentials.createSsl + rootCerts: 'config/cert/server.crt', + options: { + 'grpc.ssl_target_name_override': 'example.server', + 'grpc.default_authority': 'example.server', + }, + }, + }, + }, }; ``` @@ -211,11 +224,11 @@ stream.end(data3); ## 示例 -参见 [grpc.tests.js](test/grpc.tests.js). +参见 [test/grpc.tests.js](test/grpc.tests.js). ## 问题反馈 -访问并发起 [issue](https://github.com/eggjs/egg/issues). +访问并发起 [issue](https://github.com/xdxiaodong/egg-grpc/issues). ## License diff --git a/app.js b/app.js index b9b2c93..6537c14 100644 --- a/app.js +++ b/app.js @@ -4,6 +4,5 @@ const loader = require('./lib/grpc_loader'); module.exports = app => { // grpc.setLogger(app.coreLogger); - const GrpcLoader = app.loader.GrpcLoader = loader(app); - new GrpcLoader({}).load(); + loader(app); }; diff --git a/config/config.default.js b/config/config.default.js index d0129f6..8fde579 100644 --- a/config/config.default.js +++ b/config/config.default.js @@ -4,20 +4,63 @@ * grpc config * @member Config#grpc * @property {String} dir - proto files dir, relative path - * @property {String} property - default attach to `ctx.grpc.**` + * @property {String} property - default attach to `ctx.grpc.**`,与object.key同名,存在多个实例时需要配置该项 * @property {Object} loadOpts - options pass to `grpc.load(file, type, opts)` * @property {Boolean} loadOpts.convertFieldsToCamelCase - default to true, `string user_name` -> `userName` * @property {Object} clientOpts - options pass to `new Client(host, credentials, opts)` * @property {String} endpoint - default andress to connect, for debug or showcase purpose * @property {Number} timeout - default 5000ms + * @property {Boolean} clientSsl.enable - enable client ssl + * @property {Buffer} clientSsl.rootCerts - The root certificate data file path,作为grpc client时仅提供该项作为证书即可 + * @property {Buffer} clientSsl.privateKey - The client certificate private key file path, if applicable + * @property {Buffer} clientSsl.certChain - The client certificate cert chain file path, if applicable + * @property {Object} clientSsl.verifyOptions - Additional peer verification options, if desired + * @property {Object} clientSsl.options - 主要用于grpc.ssl_target_name_override和grpc.default_authority的配置 */ exports.grpc = { - dir: 'app/proto', - property: 'grpc', - loadOpts: { - convertFieldsToCamelCase: true, + default: { + dir: 'app/proto', + property: 'grpc', + loadOpts: { + convertFieldsToCamelCase: true, + }, + clientOpts: {}, + endpoint: 'localhost:50051', + timeout: 5000, + /** + * 2019-12-03 by 张晓东 + * 通过扩展原有配置的形式,使其支持tls/ssl安全 + */ + clientSsl: { + enable: false, + // grpc.credentials.createSsl + // config/grpc/cert.server.crt + rootCerts: '', // + privateKey: '', // 作为grpc client时无需填写 + certChain: '', // 作为grpc client时无需填写 + verifyOptions: {}, + options: { + 'grpc.ssl_target_name_override': 'example.server', + 'grpc.default_authority': 'example.server', + }, + }, }, - clientOpts: {}, - endpoint: 'localhost:50051', - timeout: 5000, + // clients: { + // // this.ctx.grpc1 + // grpc1: { + // property: 'grpc1', + // dir: 'grpc', + // // 服务端地址 + // endpoint: 'localhost:8090', + // clientSsl: { + // enable: true, + // // grpc.credentials.createSsl + // rootCerts: 'grpc/cert/server.crt', + // options: { + // "grpc.ssl_target_name_override": 'example.server', + // "grpc.default_authority": 'example.server' + // } + // } + // }, + // } }; diff --git a/lib/base_grpc.js b/lib/base_grpc.js index 673a647..e0562df 100644 --- a/lib/base_grpc.js +++ b/lib/base_grpc.js @@ -18,12 +18,14 @@ module.exports = class BaseGrpc { constructor(ctx, ProtoClass) { this.ctx = ctx; this.app = ctx.app; - this.config = this.app.config.grpc; + // this.config = this.app.config.grpc; + const key = ProtoClass.grpcconfig; // 获取对应的config的key值 + this.config = this.app.config.grpc.clients[key]; // 获取对应的key值的config对象 this.ProtoClass = ProtoClass; // delegate client rpc to this for (const key of Object.keys(this.ProtoClass.service)) { - this[key] = function(...args) { + this[key] = function (...args) { return this._invokeRPC(key, ...args); }; } @@ -40,9 +42,13 @@ module.exports = class BaseGrpc { if (!this[CLIENT]) { // options should NOT reuse, otherwise the user-agent will oom you! // https://github.com/grpc/grpc/blob/master/src/node/src/client.js#L464 - this[CLIENT] = new this.ProtoClass(this.config.endpoint, grpc.credentials.createInsecure(), Object.assign({}, this.config.clientOpts)); + if (!this.config.clientSsl.enable) { + this[CLIENT] = new this.ProtoClass(this.config.endpoint, grpc.credentials.createInsecure(), Object.assign({}, this.config.clientOpts)); + } else { + const ssl_creds = grpc.credentials.createSsl(this.config.clientSsl.rootCertsData, this.config.clientSsl.privateKeyData, this.config.clientSsl.certChainData, this.config.clientSsl.verifyOptions); + this[CLIENT] = new this.ProtoClass(this.config.endpoint, ssl_creds, Object.assign({}, this.config.clientOpts, this.config.clientSsl.options)); + } } - // TODO: config.ssl return this[CLIENT]; } @@ -83,7 +89,7 @@ module.exports = class BaseGrpc { metadata = args[0]; options = undefined; } else { - [ metadata, options, callback ] = args; + [metadata, options, callback] = args; } const invokeArgs = this._beforeRequest({ client, rpc, metadata, options, attrs, callback }); return this._invokeClientStreamRequest(invokeArgs); diff --git a/lib/grpc_loader.js b/lib/grpc_loader.js index 5cb37f7..1927fa1 100644 --- a/lib/grpc_loader.js +++ b/lib/grpc_loader.js @@ -5,9 +5,57 @@ const grpc = require('grpc'); const traverse = require('traverse'); const extend = require('extend2'); const debug = require('debug')('grpc'); +const fs = require('fs'); module.exports = app => { - const config = app.config.grpc; + // 获取多个配置 + const multiConfig = app.config.grpc.clients; + // 获取所有配置的对象键值 + const keys = Object.keys(multiConfig); + // 循环获取多个配置,进行加载,采用类官方插件多实例实现方案 + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + // 合并默认配置 + const config = Object.assign({}, app.config.grpc.default); + Object.assign(config, multiConfig[key]); + const config_clientSsl = Object.assign({}, config.clientSsl); + Object.assign(config.clientSsl, app.config.grpc.default.clientSsl); + Object.assign(config.clientSsl, config_clientSsl); + const GrpcLoader = createGrpc(config, app); + new GrpcLoader({}).load(); + } +}; + +/** + * @param {Object} config 框架处理之后的配置项,如果应用配置了多个实例,会将每一个配置项分别传入并调用多次该函数 + * @param {Application} app 当前的应用 + * @return {Object} 返回创建的实例 + */ +function createGrpc(config, app) { + // grpc ssl + if (config.clientSsl.enable) { + config.clientSsl.rootCertsData = undefined; + config.clientSsl.privateKeyData = undefined; + config.clientSsl.certChainData = undefined; + if (config.clientSsl.rootCerts !== '') { + const rootCerts = path.join(app.baseDir, config.clientSsl.rootCerts); + if (fs.existsSync(rootCerts)) { + config.clientSsl.rootCertsData = fs.readFileSync(rootCerts); + } + } + if (config.clientSsl.privateKey !== '') { + const privateKey = path.join(app.baseDir, config.clientSsl.privateKey); + if (fs.existsSync(privateKey)) { + config.clientSsl.privateKeyData = fs.readFileSync(privateKey); + } + } + if (config.clientSsl.certChain !== '') { + const certChain = path.join(app.baseDir, config.clientSsl.certChain); + if (fs.existsSync(certChain)) { + config.clientSsl.certChainData = fs.readFileSync(certChain); + } + } + } const defaults = { call: true, @@ -44,13 +92,14 @@ module.exports = app => { // traverse origin grpc proto to extract rpc service // `/example.Test/Echo` -> `app.grpcClasses.example.test` -> `yield ctx.grpc.example.test.echo()` - traverse(exports).forEach(function(proto) { + traverse(exports).forEach(function (proto) { /* istanbul ignore next */ if (this.circular) this.remove(); if (proto.name === 'Client' || proto.name === 'ServiceClient') { const properties = this.path.map(camelize); proto.paths = properties; + proto.grpcconfig = config.property; // key const item = { fullpath, properties, @@ -83,4 +132,5 @@ module.exports = app => { } return GrpcLoader; -}; +} + diff --git a/package.json b/package.json index dd9cccc..043c60e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "egg-grpc", - "version": "1.0.3", + "name": "egg-grpc-ssl", + "version": "1.0.6", "description": "grpc plugin for egg", "eggPlugin": { "name": "grpc" @@ -50,12 +50,12 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/eggjs/egg-grpc.git" + "url": "git+https://github.com/xdxiaodong/egg-grpc.git" }, "bugs": { - "url": "https://github.com/eggjs/egg/issues" + "url": "https://github.com/xdxiaodong/egg-grpc/issues" }, - "homepage": "https://github.com/eggjs/egg-grpc#readme", - "author": "TZ ", + "homepage": "https://github.com/xdxiaodong/egg-grpc#readme", + "author": "xiaodong ", "license": "MIT" }