├── .gitignore ├── .jshintrc ├── .travis.yml ├── AUTHORS ├── LICENSE ├── Makefile ├── README-Chinese.md ├── README.md ├── histroy.md ├── index.js ├── lib ├── rpc-client │ ├── client.js │ ├── failureProcess.js │ ├── mailbox.js │ ├── mailboxes │ │ ├── blackhole.js │ │ ├── mqtt-mailbox.js │ │ ├── mqtt2-mailbox.js │ │ ├── tcp-mailbox.js │ │ ├── ws-mailbox.js │ │ └── ws2-mailbox.js │ ├── mailstation.js │ └── router.js ├── rpc-server │ ├── acceptor.js │ ├── acceptors │ │ ├── mqtt-acceptor.js │ │ ├── mqtt2-acceptor.js │ │ ├── tcp-acceptor.js │ │ ├── ws-acceptor.js │ │ └── ws2-acceptor.js │ ├── dispatcher.js │ ├── gateway.js │ └── server.js └── util │ ├── buffer │ ├── inputBuffer.js │ └── outputBuffer.js │ ├── coder.js │ ├── consistentHash.js │ ├── constants.js │ ├── proxy.js │ ├── tracer.js │ └── utils.js ├── package.json ├── sample ├── bench_client.js ├── bench_mqtt ├── bench_mqtt2 ├── bench_server ├── buffer │ └── buffer.js ├── client.js ├── mqtt │ ├── client.js │ └── server.js ├── remote │ └── test │ │ └── service.js ├── server.js ├── skill.js ├── test.js ├── thrift │ ├── client.js │ ├── gen-nodejs │ │ ├── Calculator.js │ │ ├── SharedService.js │ │ ├── shared_types.js │ │ └── tutorial_types.js │ └── server.js ├── ws │ ├── client.js │ └── server.js ├── zlib │ └── bench.js └── zmq │ ├── client.js │ └── server.js └── test ├── mock-remote ├── area │ ├── addOneRemote.js │ ├── addThreeRemote.js │ └── whoAmIRemote.js └── connector │ ├── addTwoService.js │ └── whoAmIRemote.js ├── rpc-client ├── client.js ├── mailstation.js ├── router.js ├── tcp-mailbox.js └── ws-mailbox.js ├── rpc-server ├── acceptor.js ├── client │ ├── mock-client.js │ ├── mock-tcp-client.js │ └── mock-ws-client.js ├── dispatcher.js ├── gateway.js └── server.js └── util └── proxy.js /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | node_modules/ 3 | coverage.html 4 | lib-cov/ 5 | coverage.html -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "describe", 4 | "it", 5 | "before", 6 | "beforeEach", 7 | "after", 8 | "afterEach", 9 | "window", 10 | "__resources__" 11 | ], 12 | "es5": true, 13 | "node": true, 14 | "eqeqeq": true, 15 | "undef": true, 16 | "curly": true, 17 | "bitwise": true, 18 | "immed": false, 19 | "newcap": true, 20 | "nonew": true, 21 | "white": false, 22 | "strict": false 23 | } 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | * Yongchang Zhou 2 | * fantasyni 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012 NetEase, Inc. and other pomelo contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MOCHA_OPTS= 2 | TESTS = test/* 3 | REPORTER = dot 4 | TIMEOUT = 5000 5 | 6 | test: 7 | @NODE_ENV=test ./node_modules/.bin/mocha \ 8 | --reporter $(REPORTER) --timeout $(TIMEOUT) $(TESTS) \ 9 | $(MOCHA_OPTS) 10 | 11 | test-cov: lib-cov 12 | @POMELO_RPC_COV=1 $(MAKE) test REPORTER=html-cov > coverage.html 13 | 14 | lib-cov: 15 | @jscoverage lib lib-cov 16 | 17 | clean: 18 | rm -f coverage.html 19 | rm -fr lib-cov 20 | 21 | .PHONY: test clean -------------------------------------------------------------------------------- /README-Chinese.md: -------------------------------------------------------------------------------- 1 | #pomelo-rpc - rpc framework for pomelo 2 | pomelo-rpc是pomelo项目底层的rpc框架,提供了一个多服务器进程间进行rpc调用的基础设施。 3 | pomelo-rpc分为客户端和服务器端两个部分。 4 | 客户端部分提供了rpc代理生成,消息路由和网络通讯等功能,并支持动态添加代理和远程服务器配置。 5 | 服务器端提供了远程服务暴露,请求派发,网络通讯等功能。 6 | 7 | 远程服务代码加载由pomelo-loader模块完成,相关规则可以参考https://github.com/node-pomelo/pomelo-loader 8 | 9 | + Tags: node.js 10 | 11 | ##安装 12 | ``` 13 | npm install pomelo-rpc 14 | ``` 15 | 16 | ##用法 17 | ###Server 18 | ``` javascript 19 | var Server = require('pomelo-rpc').server; 20 | 21 | // remote service path info list 22 | var paths = [ 23 | {namespace: 'user', path: __dirname + '/remote/test'} 24 | ]; 25 | 26 | var port = 3333; 27 | 28 | var server = Server.create({paths: paths, port: port}); 29 | server.start(); 30 | console.log('rpc server started.'); 31 | ``` 32 | 33 | ###Client 34 | ``` javascript 35 | var Client = require('pomelo-rpc').client; 36 | 37 | // remote service interface path info list 38 | var records = [ 39 | {namespace: 'user', serverType: 'test', path: __dirname + '/remote/test'} 40 | ]; 41 | 42 | // server info list 43 | var servers = [ 44 | {id: 'test-server-1', serverType: 'test', host: '127.0.0.1', port: 3333} 45 | ]; 46 | 47 | // route parameter passed to route function 48 | var routeParam = null; 49 | 50 | // route context passed to route function 51 | var routeContext = servers; 52 | 53 | // route function to caculate the remote server id 54 | var routeFunc = function(routeParam, msg, routeContext, cb) { 55 | cb(null, routeContext[0].id); 56 | }; 57 | 58 | var client = Client.create({routeContext: routeContext, router: routeFunc}); 59 | 60 | client.start(function(err) { 61 | console.log('rpc client start ok.'); 62 | 63 | client.addProxies(records); 64 | client.addServers(servers); 65 | 66 | client.proxies.user.test.service.echo(routeParam, 'hello', function(err, resp) { 67 | if(err) { 68 | console.error(err.stack); 69 | } 70 | console.log(resp); 71 | }); 72 | }); 73 | ``` 74 | 75 | ##Server API 76 | ###Server.create(opts) 77 | 创建一个rpc server实例。根据配置信息加载远程服务代码,并生成底层acceptor。 78 | ###参数 79 | + opts.port - rpc server监听端口 80 | + opts.paths - 远程服务信息列表, [{namespace: 远程服务名字空间, path: 远程服务代码目录}, ...]. 81 | + opts.context - 传递给远程服务的上下文信息。 82 | + opts.acceptorFactory(opts, msgCB) - (可选)opts.port:监听的端口,opts.services:已加载的远程服务集合,结构为:{namespace: {name: service}}。msgCB(msg, cb):消息到达回调。该方法返回返回值为acceptor实例。 83 | 84 | ###server.start 85 | 启动rpc server实例。 86 | 87 | ###server.stop 88 | 停止rpc server实例,关闭底层的acceptor监听。 89 | 90 | ###Acceptor 91 | 负责rpc server底层的监听和rpc协议的具体实现。可以通过传入acceptorFactory来定制自己的acceptor,从而实现不同的rpc协议和策略。 92 | 93 | ###acceptor.listen(port) 94 | 让acceptor实例开始监听port端口。 95 | 96 | ###acceptor.close 97 | 关闭acceptor实例。 98 | 99 | ##Client API 100 | ###Client.create(opts) 101 | 创建一个rpc client实例。根据配置生成代理。 102 | ####参数 103 | + opts.context - 传递给mailbox的上下文信息。 104 | + opts.routeContext - (可选)传递给router函数的上下文。 105 | + opts.router(routeParam, msg, routeContext, cb) - (可选)rpc消息路由函数。其中,routeParam是路由的相关的参数,对应于rpc代理第一个参数,可以通过这个参数传递请求用户的相关信息,如session; msg是rpc的描述消息; routeContext是opts.routeContext。 106 | + opts.mailBoxFactory(serverInfo, opts) - (可选)构建mailbox实例的工厂方法。 107 | 108 | ###client.addProxies(records) 109 | 加载新的代理代码。 110 | ####参数 111 | + records - 代理代码的配置信息列表。格式:[{namespace: service_name_space, serverType: remote_server_type, path: path_to_remote_service_interfaces}]; 112 | 113 | ###client.addServers(servers) 114 | 添加新的远程服务器配置信息。 115 | ####参数 116 | + servers - 远程服务器信息列表。格式:[{id: remote_server_id, serverType: remote_server_type, host: remote_server_host, port: remote_server_port}] 117 | 118 | ###client.start(cb) 119 | 启动rpc client实例,之后可以通过代理或rpcInvoke方法发起远程调用。 120 | 121 | ###client.stop 122 | 关闭rpc client实例,并停止底层所有mailbox。 123 | 124 | ###client.rpcInvoke(serverId, msg, cb) 125 | 直接发起rpc调用。 126 | ####参数 127 | + serverId - 远程服务器的id。 128 | + msg - rpc描述消息,格式:{namespace: 远程服务命名空间, serverType: 远程服务器类型, service: 远程服务名称, method: 远程服务方法名, args: 远程方法调用参数列表}。 129 | + cb - 远程服务调用结果回调。 130 | 131 | ###MailBox 132 | 负责rpc cliente底层的连接和rpc协议的具体实现。一个mailbox实例对应一个远程服务器。可以通过传入mailBoxFactory来定制自己的mailbox,从而实现不同的rpc协议和策略。 133 | 134 | ###mailbox.connect(cb) 135 | 让mailbox实例连接到目标服务器。 136 | 137 | ###mailbox.close 138 | 关闭mailbox实例。 139 | 140 | ###mailbox.send(msg, opts, cb) 141 | 让mailbox实例发送rpc消息到关联的远程服务器。 142 | ####参数 143 | + msg - rpc描述消息,参考clienet.rpcInvoke。 144 | + opts - send操作的附加选项,预留,暂时无用。 145 | + cb - rpc回调函数。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #pomelo-rpc - rpc framework for pomelo 2 | 3 | pomelo-rpc is the low level RPC framework for pomelo project. It contains two parts: client and server. 4 | 5 | The client part generates the RPC client proxy, routes the message to the appropriate remote server and manages the network communications. Support add proxies and remote server information dynamically. 6 | 7 | The server part exports the remote services, dispatches the remote requests to the services and also manages the network communications. 8 | 9 | And the remote service codes would loaded by pomelo-loader module and more details please access this [link](https://github.com/node-pomelo/pomelo-loader). 10 | 11 | + Tags: node.js 12 | 13 | ##Installation 14 | ``` 15 | npm install pomelo-rpc 16 | ``` 17 | 18 | ##Usage 19 | ###Server 20 | ``` javascript 21 | var Server = require('pomelo-rpc').server; 22 | 23 | // remote service path info list 24 | var paths = [ 25 | {namespace: 'user', path: __dirname + '/remote/test'} 26 | ]; 27 | 28 | var port = 3333; 29 | 30 | var server = Server.create({paths: paths, port: port}); 31 | server.start(); 32 | console.log('rpc server started.'); 33 | ``` 34 | 35 | ###Client 36 | ``` javascript 37 | var Client = require('pomelo-rpc').client; 38 | 39 | // remote service interface path info list 40 | var records = [ 41 | {namespace: 'user', serverType: 'test', path: __dirname + '/remote/test'} 42 | ]; 43 | 44 | // server info list 45 | var servers = [ 46 | {id: 'test-server-1', serverType: 'test', host: '127.0.0.1', port: 3333} 47 | ]; 48 | 49 | // route parameter passed to route function 50 | var routeParam = null; 51 | 52 | // route context passed to route function 53 | var routeContext = servers; 54 | 55 | // route function to caculate the remote server id 56 | var routeFunc = function(routeParam, msg, routeContext, cb) { 57 | cb(null, routeContext[0].id); 58 | }; 59 | 60 | var client = Client.create({routeContext: routeContext, router: routeFunc}); 61 | 62 | client.start(function(err) { 63 | console.log('rpc client start ok.'); 64 | 65 | client.addProxies(records); 66 | client.addServers(servers); 67 | 68 | client.proxies.user.test.service.echo(routeParam, 'hello', function(err, resp) { 69 | if(err) { 70 | console.error(err.stack); 71 | } 72 | console.log(resp); 73 | }); 74 | }); 75 | ``` 76 | 77 | ##Server API 78 | ###Server.create(opts) 79 | Create a RPC server instance. Intitiate the instance and acceptor with the configure. 80 | ###Parameters 81 | + opts.port - rpc server listening port. 82 | + opts.paths - remote service path infos, format: [{namespace: remote service namespace, path: remote service path}, ...]. 83 | + opts.context - remote service context. 84 | + opts.acceptorFactory(opts, msgCB) - (optional) acceptor factory method. opts.port:port that acceptor would listen,opts.services:loaded remote services,format: {namespace: {name: service}}. msgCB(msg, cb): remote request arrived callback. the method should return a acceptor instance. 85 | 86 | ###server.start 87 | Start the remote server instance. 88 | 89 | ###server.stop 90 | Stop the remote server instance and the acceptor. 91 | 92 | ###Acceptor 93 | Implement the low level network communication with specified protocol. Customize the protocol by passing an acceptorFactory to return different acceptors. 94 | 95 | ###acceptor.listen(port) 96 | Listen the specified port. 97 | 98 | ###acceptor.close 99 | Stop the acceptor. 100 | 101 | ##Client API 102 | ###Client.create(opts) 103 | Create an RPC client instance which would generate proxies for the RPC client. 104 | ####Parameters 105 | + opts.context - context for mailbox. 106 | + opts.routeContext - (optional)context for route function. 107 | + opts.router(routeParam, msg, routeContext, cb) - (optional) route function which decides the RPC message should be send to which remote server. routeParam: route parameter, msg: RPC descriptioin message, routeContext: opts.routeContext. 108 | + opts.mailBoxFactory(serverInfo, opts) - (optional) mail box factory method. 109 | 110 | ###client.addProxies(records) 111 | Load new proxy codes. 112 | ####Parameters 113 | + records - new proxy code configure information list。Format: [{namespace: service_name_space, serverType: remote_server_type, path: path_to_remote_service_interfaces}]; 114 | 115 | ###client.addServers(servers) 116 | Add new remote server informations. 117 | ####Parameters 118 | + servers - remote server information list. Format: [{id: remote_server_id, serverType: remote_server_type, host: remote_server_host, port: remote_server_port}] 119 | 120 | ###client.start(cb) 121 | Start the RPC client. 122 | 123 | ###client.stop 124 | Stop the RPC client and stop all the mail box connections to remote servers. 125 | 126 | ###client.rpcInvoke(serverId, msg, cb) 127 | Invoke an RPC request. 128 | ####Parameters 129 | + serverId - remote server id. 130 | + msg - RPC description message. format: {namespace: remote service namespace, serverType: remote server type, service: remote service name, method: remote service method name, args: remote service args}. 131 | + cb - remote service callback function. 132 | 133 | ###MailBox 134 | Implement the low level network communication with remote server. A mail box instance stands for a remote server. Customize the protocol by passing a mailBoxFactory parameter to client to return different mail box instances. 135 | 136 | ###mailbox.connect(cb) 137 | Connect to the remote server. 138 | 139 | ###mailbox.close 140 | Close mail box instance and disconnect with the remote server. 141 | 142 | ###mailbox.send(msg, opts, cb) 143 | Send the RPC message to the associated remote server. 144 | ####Parameters 145 | + msg - RPC description message, see also clienet.rpcInvoke. 146 | + opts - reserved. 147 | + cb - RPC callback function. -------------------------------------------------------------------------------- /histroy.md: -------------------------------------------------------------------------------- 1 | 1.0.7 / 2017-01-20 2 | ================= 3 | * [NEW] use mqtt-mailbox, mqtt-acceptor 4 | 5 | 1.0.6 / 2017-01-19 6 | ================= 7 | * [NEW] use pure javascript implemented mqtt protocol 8 | * [NEW] improved performance 9 | * [NEW] better benchmark samples 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | if(process.env.POMELO_RPC_COV) { 2 | module.exports.client = require('./lib-cov/rpc-client/client'); 3 | module.exports.server = require('./lib-cov/rpc-server/server'); 4 | } else { 5 | module.exports.client = require('./lib/rpc-client/client'); 6 | module.exports.server = require('./lib/rpc-server/server'); 7 | } -------------------------------------------------------------------------------- /lib/rpc-client/failureProcess.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('pomelo-rpc', 'failprocess'); 2 | var constants = require('../util/constants'); 3 | var utils = require('../util/utils'); 4 | 5 | module.exports = function(code, tracer, serverId, msg, opts) { 6 | var cb = tracer && tracer.cb; 7 | var mode = opts.failMode; 8 | var FAIL_MODE = constants.FAIL_MODE; 9 | var method = failfast; 10 | 11 | if (mode == FAIL_MODE.FAILOVER) { 12 | method = failover; 13 | } else if (mode == FAIL_MODE.FAILBACK) { 14 | method = failback; 15 | } else if (mode == FAIL_MODE.FAILFAST) { 16 | 17 | } 18 | // switch (mode) { 19 | // case constants.FAIL_MODE.FAILOVER: 20 | // method = failover; 21 | // break; 22 | // case constants.FAIL_MODE.FAILBACK: 23 | // method = failback; 24 | // break; 25 | // case constants.FAIL_MODE.FAILFAST: 26 | // method = failfast; 27 | // break; 28 | // case constants.FAIL_MODE.FAILSAFE: 29 | // default: 30 | // method = failfast; 31 | // break; 32 | // } 33 | method.call(this, code, tracer, serverId, msg, opts, cb); 34 | }; 35 | 36 | /** 37 | * Failover rpc failure process. This will try other servers with option retries. 38 | * 39 | * @param code {Number} error code number. 40 | * @param tracer {Object} current rpc tracer. 41 | * @param serverId {String} rpc remote target server id. 42 | * @param msg {Object} rpc message. 43 | * @param opts {Object} rpc client options. 44 | * @param cb {Function} user rpc callback. 45 | * 46 | * @api private 47 | */ 48 | var failover = function(code, tracer, serverId, msg, opts, cb) { 49 | var servers; 50 | var self = this; 51 | var counter = 0; 52 | var success = true; 53 | var serverType = msg.serverType; 54 | if (!tracer || !tracer.servers) { 55 | servers = self.serversMap[serverType]; 56 | } else { 57 | servers = tracer.servers; 58 | } 59 | 60 | var index = servers.indexOf(serverId); 61 | if (index >= 0) { 62 | servers.splice(index, 1); 63 | } 64 | tracer && (tracer.servers = servers); 65 | 66 | if (!servers.length) { 67 | logger.error('[pomelo-rpc] rpc failed with all this type of servers, with serverType: %s', serverType); 68 | cb(new Error('rpc failed with all this type of servers, with serverType: ' + serverType)); 69 | return; 70 | } 71 | self.dispatch.call(self, tracer, servers[0], msg, opts, cb); 72 | }; 73 | 74 | /** 75 | * Failsafe rpc failure process. 76 | * 77 | * @param code {Number} error code number. 78 | * @param tracer {Object} current rpc tracer. 79 | * @param serverId {String} rpc remote target server id. 80 | * @param msg {Object} rpc message. 81 | * @param opts {Object} rpc client options. 82 | * @param cb {Function} user rpc callback. 83 | * 84 | * @api private 85 | */ 86 | var failsafe = function(code, tracer, serverId, msg, opts, cb) { 87 | var self = this; 88 | var retryTimes = opts.retryTimes || constants.DEFAULT_PARAM.FAILSAFE_RETRIES; 89 | var retryConnectTime = opts.retryConnectTime || constants.DEFAULT_PARAM.FAILSAFE_CONNECT_TIME; 90 | 91 | if (!tracer.retryTimes) { 92 | tracer.retryTimes = 1; 93 | } else { 94 | tracer.retryTimes += 1; 95 | } 96 | switch (code) { 97 | case constants.RPC_ERROR.SERVER_NOT_STARTED: 98 | case constants.RPC_ERROR.NO_TRAGET_SERVER: 99 | cb(new Error('rpc client is not started or cannot find remote server.')); 100 | break; 101 | case constants.RPC_ERROR.FAIL_CONNECT_SERVER: 102 | if (tracer.retryTimes <= retryTimes) { 103 | setTimeout(function() { 104 | self.connect(tracer, serverId, cb); 105 | }, retryConnectTime * tracer.retryTimes); 106 | } else { 107 | cb(new Error('rpc client failed to connect to remote server: ' + serverId)); 108 | } 109 | break; 110 | case constants.RPC_ERROR.FAIL_FIND_MAILBOX: 111 | case constants.RPC_ERROR.FAIL_SEND_MESSAGE: 112 | if (tracer.retryTimes <= retryTimes) { 113 | setTimeout(function() { 114 | self.dispatch.call(self, tracer, serverId, msg, opts, cb); 115 | }, retryConnectTime * tracer.retryTimes); 116 | } else { 117 | cb(new Error('rpc client failed to send message to remote server: ' + serverId)); 118 | } 119 | break; 120 | case constants.RPC_ERROR.FILTER_ERROR: 121 | cb(new Error('rpc client filter encounters error.')); 122 | break; 123 | default: 124 | cb(new Error('rpc client unknown error.')); 125 | } 126 | }; 127 | 128 | /** 129 | * Failback rpc failure process. This will try the same server with sendInterval option and retries option. 130 | * 131 | * @param code {Number} error code number. 132 | * @param tracer {Object} current rpc tracer. 133 | * @param serverId {String} rpc remote target server id. 134 | * @param msg {Object} rpc message. 135 | * @param opts {Object} rpc client options. 136 | * @param cb {Function} user rpc callback. 137 | * 138 | * @api private 139 | */ 140 | var failback = function(code, tracer, serverId, msg, opts, cb) { 141 | // todo record message in background and send the message at timing 142 | }; 143 | 144 | /** 145 | * Failfast rpc failure process. This will ignore error in rpc client. 146 | * 147 | * @param code {Number} error code number. 148 | * @param tracer {Object} current rpc tracer. 149 | * @param serverId {String} rpc remote target server id. 150 | * @param msg {Object} rpc message. 151 | * @param opts {Object} rpc client options. 152 | * @param cb {Function} user rpc callback. 153 | * 154 | * @api private 155 | */ 156 | var failfast = function(code, tracer, serverId, msg, opts, cb) { 157 | logger.error('rpc failed with error, remote server: %s, msg: %j, error code: %s', serverId, msg, code); 158 | cb && cb(new Error('rpc failed with error code: ' + code)); 159 | }; -------------------------------------------------------------------------------- /lib/rpc-client/mailbox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default mailbox factory 3 | */ 4 | var Mailbox = require('./mailboxes/mqtt-mailbox'); 5 | // var Ws2Mailbox = require('./mailboxes/ws2-mailbox'); 6 | // var WsMailbox = require('./mailboxes/ws-mailbox'); 7 | 8 | /** 9 | * default mailbox factory 10 | * 11 | * @param {Object} serverInfo single server instance info, {id, host, port, ...} 12 | * @param {Object} opts construct parameters 13 | * @return {Object} mailbox instancef 14 | */ 15 | module.exports.create = function(serverInfo, opts) { 16 | // var mailbox = opts.mailbox || 'mqtt'; 17 | // var Mailbox = null; 18 | // if (mailbox == 'ws') { 19 | // Mailbox = WsMailbox; 20 | // } else if (mailbox == 'ws2') { 21 | // Mailbox = Ws2Mailbox; 22 | // } else if (mailbox == 'mqtt') { 23 | // Mailbox = MqttMailbox; 24 | // } 25 | return Mailbox.create(serverInfo, opts); 26 | }; -------------------------------------------------------------------------------- /lib/rpc-client/mailboxes/blackhole.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('pomelo-rpc', 'blackhole'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var utils = require('../../util/utils'); 4 | 5 | var exp = module.exports = new EventEmitter(); 6 | 7 | exp.connect = function(tracer, cb) { 8 | tracer && tracer.info('client', __filename, 'connect', 'connect to blackhole'); 9 | process.nextTick(function() { 10 | cb(new Error('fail to connect to remote server and switch to blackhole.')); 11 | }); 12 | }; 13 | 14 | exp.close = function(cb) {}; 15 | 16 | exp.send = function(tracer, msg, opts, cb) { 17 | tracer && tracer.info('client', __filename, 'send', 'send rpc msg to blackhole'); 18 | logger.info('message into blackhole: %j', msg); 19 | process.nextTick(function() { 20 | cb(tracer, new Error('message was forward to blackhole.')); 21 | }); 22 | }; -------------------------------------------------------------------------------- /lib/rpc-client/mailboxes/mqtt-mailbox.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('pomelo-rpc', 'mqtt-mailbox'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var constants = require('../../util/constants'); 4 | var Tracer = require('../../util/tracer'); 5 | var MqttCon = require('mqtt-connection'); 6 | var utils = require('../../util/utils'); 7 | var util = require('util'); 8 | var net = require('net'); 9 | 10 | var CONNECT_TIMEOUT = 2000; 11 | 12 | var MailBox = function(server, opts) { 13 | EventEmitter.call(this); 14 | this.curId = 0; 15 | this.id = server.id; 16 | this.host = server.host; 17 | this.port = server.port; 18 | this.requests = {}; 19 | this.timeout = {}; 20 | this.queue = []; 21 | this.bufferMsg = opts.bufferMsg; 22 | this.keepalive = opts.keepalive || constants.DEFAULT_PARAM.KEEPALIVE; 23 | this.interval = opts.interval || constants.DEFAULT_PARAM.INTERVAL; 24 | this.timeoutValue = opts.timeout || constants.DEFAULT_PARAM.CALLBACK_TIMEOUT; 25 | this.keepaliveTimer = null; 26 | this.lastPing = -1; 27 | this.lastPong = -1; 28 | this.connected = false; 29 | this.closed = false; 30 | this.opts = opts; 31 | this.serverId = opts.context.serverId; 32 | }; 33 | 34 | util.inherits(MailBox, EventEmitter); 35 | 36 | MailBox.prototype.connect = function(tracer, cb) { 37 | tracer && tracer.info('client', __filename, 'connect', 'mqtt-mailbox try to connect'); 38 | if (this.connected) { 39 | tracer && tracer.error('client', __filename, 'connect', 'mailbox has already connected'); 40 | return cb(new Error('mailbox has already connected.')); 41 | } 42 | 43 | var self = this; 44 | 45 | var stream = net.createConnection(this.port, this.host); 46 | this.socket = MqttCon(stream); 47 | 48 | var connectTimeout = setTimeout(function() { 49 | logger.error('rpc client %s connect to remote server %s timeout', self.serverId, self.id); 50 | self.emit('close', self.id); 51 | }, CONNECT_TIMEOUT); 52 | 53 | this.socket.connect({ 54 | clientId: 'MQTT_RPC_' + Date.now() 55 | }, function() { 56 | if (self.connected) { 57 | return; 58 | } 59 | 60 | clearTimeout(connectTimeout); 61 | self.connected = true; 62 | if (self.bufferMsg) { 63 | self._interval = setInterval(function() { 64 | flush(self); 65 | }, self.interval); 66 | } 67 | 68 | self.setupKeepAlive(); 69 | cb(); 70 | }); 71 | 72 | this.socket.on('publish', function(pkg) { 73 | pkg = pkg.payload.toString(); 74 | try { 75 | pkg = JSON.parse(pkg); 76 | if (pkg instanceof Array) { 77 | processMsgs(self, pkg); 78 | } else { 79 | processMsg(self, pkg); 80 | } 81 | } catch (err) { 82 | logger.error('rpc client %s process remote server %s message with error: %s', self.serverId, self.id, err.stack); 83 | } 84 | }); 85 | 86 | this.socket.on('error', function(err) { 87 | logger.error('rpc socket %s is error, remote server %s host: %s, port: %s', self.serverId, self.id, self.host, self.port); 88 | self.emit('close', self.id); 89 | }); 90 | 91 | this.socket.on('pingresp', function() { 92 | self.lastPong = Date.now(); 93 | }); 94 | 95 | this.socket.on('disconnect', function(reason) { 96 | logger.error('rpc socket %s is disconnect from remote server %s, reason: %s', self.serverId, self.id, reason); 97 | var reqs = self.requests; 98 | for (var id in reqs) { 99 | var ReqCb = reqs[id]; 100 | ReqCb(tracer, new Error(self.serverId + ' disconnect with remote server ' + self.id)); 101 | } 102 | self.emit('close', self.id); 103 | }); 104 | }; 105 | 106 | /** 107 | * close mailbox 108 | */ 109 | MailBox.prototype.close = function() { 110 | if (this.closed) { 111 | return; 112 | } 113 | this.closed = true; 114 | this.connected = false; 115 | if (this._interval) { 116 | clearInterval(this._interval); 117 | this._interval = null; 118 | } 119 | this.socket.destroy(); 120 | }; 121 | 122 | /** 123 | * send message to remote server 124 | * 125 | * @param msg {service:"", method:"", args:[]} 126 | * @param opts {} attach info to send method 127 | * @param cb declaration decided by remote interface 128 | */ 129 | MailBox.prototype.send = function(tracer, msg, opts, cb) { 130 | tracer && tracer.info('client', __filename, 'send', 'mqtt-mailbox try to send'); 131 | if (!this.connected) { 132 | tracer && tracer.error('client', __filename, 'send', 'mqtt-mailbox not init'); 133 | cb(tracer, new Error(this.serverId + ' mqtt-mailbox is not init ' + this.id)); 134 | return; 135 | } 136 | 137 | if (this.closed) { 138 | tracer && tracer.error('client', __filename, 'send', 'mailbox has already closed'); 139 | cb(tracer, new Error(this.serverId + ' mqtt-mailbox has already closed ' + this.id)); 140 | return; 141 | } 142 | 143 | var id = this.curId++; 144 | this.requests[id] = cb; 145 | setCbTimeout(this, id, tracer, cb); 146 | 147 | var pkg; 148 | if (tracer && tracer.isEnabled) { 149 | pkg = { 150 | traceId: tracer.id, 151 | seqId: tracer.seq, 152 | source: tracer.source, 153 | remote: tracer.remote, 154 | id: id, 155 | msg: msg 156 | }; 157 | } else { 158 | pkg = { 159 | id: id, 160 | msg: msg 161 | }; 162 | } 163 | if (this.bufferMsg) { 164 | enqueue(this, pkg); 165 | } else { 166 | doSend(this.socket, pkg); 167 | } 168 | }; 169 | 170 | MailBox.prototype.setupKeepAlive = function() { 171 | var self = this; 172 | this.keepaliveTimer = setInterval(function() { 173 | self.checkKeepAlive(); 174 | }, this.keepalive); 175 | } 176 | 177 | MailBox.prototype.checkKeepAlive = function() { 178 | if (this.closed) { 179 | return; 180 | } 181 | 182 | // console.log('checkKeepAlive lastPing %d lastPong %d ~~~', this.lastPing, this.lastPong); 183 | var now = Date.now(); 184 | var KEEP_ALIVE_TIMEOUT = this.keepalive * 2; 185 | if (this.lastPing > 0) { 186 | if (this.lastPong < this.lastPing) { 187 | if (now - this.lastPing > KEEP_ALIVE_TIMEOUT) { 188 | logger.error('mqtt rpc client %s checkKeepAlive timeout from remote server %s for %d lastPing: %s lastPong: %s', this.serverId, this.id, KEEP_ALIVE_TIMEOUT, this.lastPing, this.lastPong); 189 | this.emit('close', this.id); 190 | this.lastPing = -1; 191 | // this.close(); 192 | } 193 | } else { 194 | this.socket.pingreq(); 195 | this.lastPing = Date.now(); 196 | } 197 | } else { 198 | this.socket.pingreq(); 199 | this.lastPing = Date.now(); 200 | } 201 | } 202 | 203 | var enqueue = function(mailbox, msg) { 204 | mailbox.queue.push(msg); 205 | }; 206 | 207 | var flush = function(mailbox) { 208 | if (mailbox.closed || !mailbox.queue.length) { 209 | return; 210 | } 211 | doSend(mailbox.socket, mailbox.queue); 212 | mailbox.queue = []; 213 | }; 214 | 215 | var doSend = function(socket, msg) { 216 | socket.publish({ 217 | topic: 'rpc', 218 | payload: JSON.stringify(msg) 219 | }); 220 | } 221 | 222 | var processMsgs = function(mailbox, pkgs) { 223 | for (var i = 0, l = pkgs.length; i < l; i++) { 224 | processMsg(mailbox, pkgs[i]); 225 | } 226 | }; 227 | 228 | var processMsg = function(mailbox, pkg) { 229 | var pkgId = pkg.id; 230 | clearCbTimeout(mailbox, pkgId); 231 | var cb = mailbox.requests[pkgId]; 232 | if (!cb) { 233 | return; 234 | } 235 | 236 | delete mailbox.requests[pkgId]; 237 | var rpcDebugLog = mailbox.opts.rpcDebugLog; 238 | var tracer = null; 239 | var sendErr = null; 240 | if (rpcDebugLog) { 241 | tracer = new Tracer(mailbox.opts.rpcLogger, mailbox.opts.rpcDebugLog, mailbox.opts.clientId, pkg.source, pkg.resp, pkg.traceId, pkg.seqId); 242 | } 243 | var pkgResp = pkg.resp; 244 | 245 | cb(tracer, sendErr, pkgResp); 246 | }; 247 | 248 | var setCbTimeout = function(mailbox, id, tracer, cb) { 249 | var timer = setTimeout(function() { 250 | // logger.warn('rpc request is timeout, id: %s, host: %s, port: %s', id, mailbox.host, mailbox.port); 251 | clearCbTimeout(mailbox, id); 252 | if (mailbox.requests[id]) { 253 | delete mailbox.requests[id]; 254 | } 255 | var eMsg = util.format('rpc %s callback timeout %d, remote server %s host: %s, port: %s', mailbox.serverId, mailbox.timeoutValue, id, mailbox.host, mailbox.port); 256 | logger.error(eMsg); 257 | cb(tracer, new Error(eMsg)); 258 | }, mailbox.timeoutValue); 259 | mailbox.timeout[id] = timer; 260 | }; 261 | 262 | var clearCbTimeout = function(mailbox, id) { 263 | if (!mailbox.timeout[id]) { 264 | logger.warn('timer is not exsits, serverId: %s remote: %s, host: %s, port: %s', mailbox.serverId, id, mailbox.host, mailbox.port); 265 | return; 266 | } 267 | clearTimeout(mailbox.timeout[id]); 268 | delete mailbox.timeout[id]; 269 | }; 270 | 271 | /** 272 | * Factory method to create mailbox 273 | * 274 | * @param {Object} server remote server info {id:"", host:"", port:""} 275 | * @param {Object} opts construct parameters 276 | * opts.bufferMsg {Boolean} msg should be buffered or send immediately. 277 | * opts.interval {Boolean} msg queue flush interval if bufferMsg is true. default is 50 ms 278 | */ 279 | module.exports.create = function(server, opts) { 280 | return new MailBox(server, opts || {}); 281 | }; -------------------------------------------------------------------------------- /lib/rpc-client/mailboxes/mqtt2-mailbox.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('pomelo-rpc', 'mqtt2-mailbox'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var Constants = require('../../util/constants'); 4 | var Tracer = require('../../util/tracer'); 5 | var MqttCon = require('mqtt-connection'); 6 | var utils = require('../../util/utils'); 7 | var Coder = require('../../util/coder'); 8 | var util = require('util'); 9 | var net = require('net'); 10 | 11 | var CONNECT_TIMEOUT = 2000; 12 | 13 | var MailBox = function(server, opts) { 14 | EventEmitter.call(this); 15 | this.curId = 0; 16 | this.id = server.id; 17 | this.host = server.host; 18 | this.port = server.port; 19 | this.requests = {}; 20 | this.timeout = {}; 21 | this.queue = []; 22 | this.servicesMap = {}; 23 | this.bufferMsg = opts.bufferMsg; 24 | this.keepalive = opts.keepalive || Constants.DEFAULT_PARAM.KEEPALIVE; 25 | this.interval = opts.interval || Constants.DEFAULT_PARAM.INTERVAL; 26 | this.timeoutValue = opts.timeout || Constants.DEFAULT_PARAM.CALLBACK_TIMEOUT; 27 | this.keepaliveTimer = null; 28 | this.lastPing = -1; 29 | this.lastPong = -1; 30 | this.connected = false; 31 | this.closed = false; 32 | this.opts = opts; 33 | this.serverId = opts.context.serverId; 34 | }; 35 | 36 | util.inherits(MailBox, EventEmitter); 37 | 38 | MailBox.prototype.connect = function(tracer, cb) { 39 | tracer && tracer.info('client', __filename, 'connect', 'mqtt-mailbox try to connect'); 40 | if (this.connected) { 41 | tracer && tracer.error('client', __filename, 'connect', 'mailbox has already connected'); 42 | return cb(new Error('mailbox has already connected.')); 43 | } 44 | 45 | var self = this; 46 | 47 | var stream = net.createConnection(this.port, this.host); 48 | this.socket = MqttCon(stream); 49 | 50 | var connectTimeout = setTimeout(function() { 51 | logger.error('rpc client %s connect to remote server %s timeout', self.serverId, self.id); 52 | self.emit('close', self.id); 53 | }, CONNECT_TIMEOUT); 54 | 55 | this.socket.connect({ 56 | clientId: 'MQTT_RPC_' + Date.now() 57 | }, function() { 58 | if (self.connected) { 59 | return; 60 | } 61 | 62 | clearTimeout(connectTimeout); 63 | self.connected = true; 64 | if (self.bufferMsg) { 65 | self._interval = setInterval(function() { 66 | flush(self); 67 | }, self.interval); 68 | } 69 | 70 | self.setupKeepAlive(); 71 | }); 72 | 73 | this.socket.on('publish', function(pkg) { 74 | if(pkg.topic == Constants['TOPIC_HANDSHAKE']) { 75 | upgradeHandshake(self, pkg.payload); 76 | return cb(); 77 | } 78 | try { 79 | pkg = Coder.decodeClient(pkg.payload); 80 | processMsg(self, pkg); 81 | } catch (err) { 82 | logger.error('rpc client %s process remote server %s message with error: %s', self.serverId, self.id, err.stack); 83 | } 84 | }); 85 | 86 | this.socket.on('error', function(err) { 87 | logger.error('rpc socket %s is error, remote server %s host: %s, port: %s', self.serverId, self.id, self.host, self.port); 88 | self.emit('close', self.id); 89 | self.close(); 90 | }); 91 | 92 | this.socket.on('pingresp', function() { 93 | self.lastPong = Date.now(); 94 | }); 95 | 96 | this.socket.on('disconnect', function(reason) { 97 | logger.error('rpc socket %s is disconnect from remote server %s, reason: %s', self.serverId, self.id, reason); 98 | var reqs = self.requests; 99 | for (var id in reqs) { 100 | var ReqCb = reqs[id]; 101 | ReqCb(tracer, new Error(self.serverId + ' disconnect with remote server ' + self.id)); 102 | } 103 | self.emit('close', self.id); 104 | }); 105 | }; 106 | 107 | /** 108 | * close mailbox 109 | */ 110 | MailBox.prototype.close = function() { 111 | this.closed = true; 112 | this.connected = false; 113 | if (this._interval) { 114 | clearInterval(this._interval); 115 | this._interval = null; 116 | } 117 | if(this.keepaliveTimer) { 118 | clearInterval(this.keepaliveTimer); 119 | this.keepaliveTimer = null; 120 | } 121 | if(this.socket) { 122 | this.socket.destroy(); 123 | } 124 | }; 125 | 126 | /** 127 | * send message to remote server 128 | * 129 | * @param msg {service:"", method:"", args:[]} 130 | * @param opts {} attach info to send method 131 | * @param cb declaration decided by remote interface 132 | */ 133 | MailBox.prototype.send = function(tracer, msg, opts, cb) { 134 | tracer && tracer.info('client', __filename, 'send', 'mqtt-mailbox try to send'); 135 | if (!this.connected) { 136 | tracer && tracer.error('client', __filename, 'send', 'mqtt-mailbox not init'); 137 | cb(tracer, new Error(this.serverId + ' mqtt-mailbox is not init ' + this.id)); 138 | return; 139 | } 140 | 141 | if (this.closed) { 142 | tracer && tracer.error('client', __filename, 'send', 'mailbox has already closed'); 143 | cb(tracer, new Error(this.serverId + ' mqtt-mailbox has already closed ' + this.id)); 144 | return; 145 | } 146 | 147 | var id = this.curId++; 148 | this.requests[id] = cb; 149 | setCbTimeout(this, id, tracer, cb); 150 | 151 | var pkg; 152 | if (tracer && tracer.isEnabled) { 153 | pkg = { 154 | traceId: tracer.id, 155 | seqId: tracer.seq, 156 | source: tracer.source, 157 | remote: tracer.remote, 158 | id: id, 159 | msg: msg 160 | }; 161 | } else { 162 | pkg = Coder.encodeClient(id, msg, this.servicesMap); 163 | // pkg = { 164 | // id: id, 165 | // msg: msg 166 | // }; 167 | } 168 | if (this.bufferMsg) { 169 | enqueue(this, pkg); 170 | } else { 171 | doSend(this.socket, pkg); 172 | } 173 | }; 174 | 175 | MailBox.prototype.setupKeepAlive = function() { 176 | var self = this; 177 | this.keepaliveTimer = setInterval(function() { 178 | self.checkKeepAlive(); 179 | }, this.keepalive); 180 | } 181 | 182 | MailBox.prototype.checkKeepAlive = function() { 183 | if (this.closed) { 184 | return; 185 | } 186 | 187 | // console.log('checkKeepAlive lastPing %d lastPong %d ~~~', this.lastPing, this.lastPong); 188 | var now = Date.now(); 189 | var KEEP_ALIVE_TIMEOUT = this.keepalive * 2; 190 | if (this.lastPing > 0) { 191 | if (this.lastPong < this.lastPing) { 192 | if (now - this.lastPing > KEEP_ALIVE_TIMEOUT) { 193 | logger.error('mqtt rpc client %s checkKeepAlive timeout from remote server %s for %d lastPing: %s lastPong: %s', this.serverId, this.id, KEEP_ALIVE_TIMEOUT, this.lastPing, this.lastPong); 194 | this.emit('close', this.id); 195 | this.lastPing = -1; 196 | // this.close(); 197 | } 198 | } else { 199 | this.socket.pingreq(); 200 | this.lastPing = Date.now(); 201 | } 202 | } else { 203 | this.socket.pingreq(); 204 | this.lastPing = Date.now(); 205 | } 206 | } 207 | 208 | var enqueue = function(mailbox, msg) { 209 | mailbox.queue.push(msg); 210 | }; 211 | 212 | var flush = function(mailbox) { 213 | if (mailbox.closed || !mailbox.queue.length) { 214 | return; 215 | } 216 | doSend(mailbox.socket, mailbox.queue); 217 | mailbox.queue = []; 218 | }; 219 | 220 | var doSend = function(socket, msg) { 221 | socket.publish({ 222 | topic: 'rpc', 223 | payload: msg 224 | // payload: JSON.stringify(msg) 225 | }); 226 | } 227 | 228 | var upgradeHandshake = function(mailbox, msg) { 229 | var servicesMap = JSON.parse(msg.toString()); 230 | mailbox.servicesMap = servicesMap; 231 | } 232 | 233 | var processMsgs = function(mailbox, pkgs) { 234 | for (var i = 0, l = pkgs.length; i < l; i++) { 235 | processMsg(mailbox, pkgs[i]); 236 | } 237 | }; 238 | 239 | var processMsg = function(mailbox, pkg) { 240 | var pkgId = pkg.id; 241 | clearCbTimeout(mailbox, pkgId); 242 | var cb = mailbox.requests[pkgId]; 243 | if (!cb) { 244 | return; 245 | } 246 | 247 | delete mailbox.requests[pkgId]; 248 | var rpcDebugLog = mailbox.opts.rpcDebugLog; 249 | var tracer = null; 250 | var sendErr = null; 251 | if (rpcDebugLog) { 252 | tracer = new Tracer(mailbox.opts.rpcLogger, mailbox.opts.rpcDebugLog, mailbox.opts.clientId, pkg.source, pkg.resp, pkg.traceId, pkg.seqId); 253 | } 254 | var pkgResp = pkg.resp; 255 | 256 | cb(tracer, sendErr, pkgResp); 257 | }; 258 | 259 | var setCbTimeout = function(mailbox, id, tracer, cb) { 260 | // console.log('setCbTimeout %d', id); 261 | var timer = setTimeout(function() { 262 | // logger.warn('rpc request is timeout, id: %s, host: %s, port: %s', id, mailbox.host, mailbox.port); 263 | clearCbTimeout(mailbox, id); 264 | if (mailbox.requests[id]) { 265 | delete mailbox.requests[id]; 266 | } 267 | var eMsg = util.format('rpc %s callback timeout %d, remote server %s host: %s, port: %s', mailbox.serverId, mailbox.timeoutValue, id, mailbox.host, mailbox.port); 268 | logger.error(eMsg); 269 | cb(tracer, new Error(eMsg)); 270 | }, mailbox.timeoutValue); 271 | mailbox.timeout[id] = timer; 272 | }; 273 | 274 | var clearCbTimeout = function(mailbox, id) { 275 | // console.log('clearCbTimeout %d', id); 276 | if (!mailbox.timeout[id]) { 277 | logger.warn('timer is not exsits, serverId: %s remote: %s, host: %s, port: %s', mailbox.serverId, id, mailbox.host, mailbox.port); 278 | return; 279 | } 280 | clearTimeout(mailbox.timeout[id]); 281 | delete mailbox.timeout[id]; 282 | }; 283 | 284 | /** 285 | * Factory method to create mailbox 286 | * 287 | * @param {Object} server remote server info {id:"", host:"", port:""} 288 | * @param {Object} opts construct parameters 289 | * opts.bufferMsg {Boolean} msg should be buffered or send immediately. 290 | * opts.interval {Boolean} msg queue flush interval if bufferMsg is true. default is 50 ms 291 | */ 292 | module.exports.create = function(server, opts) { 293 | return new MailBox(server, opts || {}); 294 | }; -------------------------------------------------------------------------------- /lib/rpc-client/mailboxes/tcp-mailbox.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var Tracer = require('../../util/tracer'); 3 | var utils = require('../../util/utils'); 4 | var Composer = require('stream-pkg'); 5 | var util = require('util'); 6 | var net = require('net'); 7 | 8 | var DEFAULT_CALLBACK_TIMEOUT = 10 * 1000; 9 | var DEFAULT_INTERVAL = 50; 10 | 11 | var MailBox = function(server, opts) { 12 | EventEmitter.call(this); 13 | this.opts = opts || {}; 14 | this.id = server.id; 15 | this.host = server.host; 16 | this.port = server.port; 17 | this.socket = null; 18 | this.composer = new Composer({ 19 | maxLength: opts.pkgSize 20 | }); 21 | this.requests = {}; 22 | this.timeout = {}; 23 | this.curId = 0; 24 | this.queue = []; 25 | this.bufferMsg = opts.bufferMsg; 26 | this.interval = opts.interval || DEFAULT_INTERVAL; 27 | this.timeoutValue = opts.timeout || DEFAULT_CALLBACK_TIMEOUT; 28 | this.connected = false; 29 | this.closed = false; 30 | }; 31 | util.inherits(MailBox, EventEmitter); 32 | 33 | var pro = MailBox.prototype; 34 | 35 | pro.connect = function(tracer, cb) { 36 | tracer.info('client', __filename, 'connect', 'tcp-mailbox try to connect'); 37 | if (this.connected) { 38 | utils.invokeCallback(cb, new Error('mailbox has already connected.')); 39 | return; 40 | } 41 | 42 | this.socket = net.connect({ 43 | port: this.port, 44 | host: this.host 45 | }, function(err) { 46 | // success to connect 47 | self.connected = true; 48 | if (self.bufferMsg) { 49 | // start flush interval 50 | self._interval = setInterval(function() { 51 | flush(self); 52 | }, self.interval); 53 | } 54 | utils.invokeCallback(cb, err); 55 | }); 56 | 57 | var self = this; 58 | 59 | this.composer.on('data', function(data) { 60 | var pkg = JSON.parse(data.toString()); 61 | if (pkg instanceof Array) { 62 | processMsgs(self, pkg); 63 | } else { 64 | processMsg(self, pkg); 65 | } 66 | }); 67 | 68 | this.socket.on('data', function(data) { 69 | self.composer.feed(data); 70 | }); 71 | 72 | this.socket.on('error', function(err) { 73 | if (!self.connected) { 74 | utils.invokeCallback(cb, err); 75 | return; 76 | } 77 | self.emit('error', err, self); 78 | }); 79 | 80 | this.socket.on('end', function() { 81 | self.emit('close', self.id); 82 | }); 83 | 84 | // TODO: reconnect and heartbeat 85 | }; 86 | 87 | /** 88 | * close mailbox 89 | */ 90 | pro.close = function() { 91 | if (this.closed) { 92 | return; 93 | } 94 | this.closed = true; 95 | this.connected = false; 96 | if (this._interval) { 97 | clearInterval(this._interval); 98 | this._interval = null; 99 | } 100 | if (this.socket) { 101 | this.socket.end(); 102 | this.socket = null; 103 | } 104 | }; 105 | 106 | /** 107 | * send message to remote server 108 | * 109 | * @param msg {service:"", method:"", args:[]} 110 | * @param opts {} attach info to send method 111 | * @param cb declaration decided by remote interface 112 | */ 113 | pro.send = function(tracer, msg, opts, cb) { 114 | tracer.info('client', __filename, 'send', 'tcp-mailbox try to send'); 115 | if (!this.connected) { 116 | utils.invokeCallback(cb, tracer, new Error('not init.')); 117 | return; 118 | } 119 | 120 | if (this.closed) { 121 | utils.invokeCallback(cb, tracer, new Error('mailbox alread closed.')); 122 | return; 123 | } 124 | 125 | var id = this.curId++; 126 | this.requests[id] = cb; 127 | setCbTimeout(this, id, tracer, cb); 128 | var pkg; 129 | 130 | if (tracer.isEnabled) { 131 | pkg = { 132 | traceId: tracer.id, 133 | seqId: tracer.seq, 134 | source: tracer.source, 135 | remote: tracer.remote, 136 | id: id, 137 | msg: msg 138 | }; 139 | } else { 140 | pkg = { 141 | id: id, 142 | msg: msg 143 | }; 144 | } 145 | 146 | if (this.bufferMsg) { 147 | enqueue(this, pkg); 148 | } else { 149 | this.socket.write(this.composer.compose(JSON.stringify(pkg))); 150 | } 151 | }; 152 | 153 | var enqueue = function(mailbox, msg) { 154 | mailbox.queue.push(msg); 155 | }; 156 | 157 | var flush = function(mailbox) { 158 | if (mailbox.closed || !mailbox.queue.length) { 159 | return; 160 | } 161 | mailbox.socket.write(mailbox.composer.compose(JSON.stringify(mailbox.queue))); 162 | mailbox.queue = []; 163 | }; 164 | 165 | var processMsgs = function(mailbox, pkgs) { 166 | for (var i = 0, l = pkgs.length; i < l; i++) { 167 | processMsg(mailbox, pkgs[i]); 168 | } 169 | }; 170 | 171 | var processMsg = function(mailbox, pkg) { 172 | clearCbTimeout(mailbox, pkg.id); 173 | var cb = mailbox.requests[pkg.id]; 174 | if (!cb) { 175 | return; 176 | } 177 | delete mailbox.requests[pkg.id]; 178 | 179 | var tracer = new Tracer(mailbox.opts.rpcLogger, mailbox.opts.rpcDebugLog, mailbox.opts.clientId, pkg.source, pkg.resp, pkg.traceId, pkg.seqId); 180 | var args = [tracer, null]; 181 | 182 | pkg.resp.forEach(function(arg) { 183 | args.push(arg); 184 | }); 185 | 186 | cb.apply(null, args); 187 | }; 188 | 189 | var setCbTimeout = function(mailbox, id, tracer, cb) { 190 | var timer = setTimeout(function() { 191 | clearCbTimeout(mailbox, id); 192 | if (!!mailbox.requests[id]) { 193 | delete mailbox.requests[id]; 194 | } 195 | logger.error('rpc callback timeout, remote server host: %s, port: %s', mailbox.host, mailbox.port); 196 | utils.invokeCallback(cb, tracer, new Error('rpc callback timeout')); 197 | }, mailbox.timeoutValue); 198 | mailbox.timeout[id] = timer; 199 | }; 200 | 201 | var clearCbTimeout = function(mailbox, id) { 202 | if (!mailbox.timeout[id]) { 203 | console.warn('timer not exists, id: %s', id); 204 | return; 205 | } 206 | clearTimeout(mailbox.timeout[id]); 207 | delete mailbox.timeout[id]; 208 | }; 209 | 210 | /** 211 | * Factory method to create mailbox 212 | * 213 | * @param {Object} server remote server info {id:"", host:"", port:""} 214 | * @param {Object} opts construct parameters 215 | * opts.bufferMsg {Boolean} msg should be buffered or send immediately. 216 | * opts.interval {Boolean} msg queue flush interval if bufferMsg is true. default is 50 ms 217 | */ 218 | module.exports.create = function(server, opts) { 219 | return new MailBox(server, opts || {}); 220 | }; -------------------------------------------------------------------------------- /lib/rpc-client/mailboxes/ws-mailbox.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('pomelo-rpc', 'ws-mailbox'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var constants = require('../../util/constants'); 4 | var Tracer = require('../../util/tracer'); 5 | var client = require('socket.io-client'); 6 | var utils = require('../../util/utils'); 7 | var util = require('util'); 8 | 9 | var MailBox = function(server, opts) { 10 | EventEmitter.call(this); 11 | this.curId = 0; 12 | this.id = server.id; 13 | this.host = server.host; 14 | this.port = server.port; 15 | this.requests = {}; 16 | this.timeout = {}; 17 | this.queue = []; 18 | this.bufferMsg = opts.bufferMsg; 19 | this.interval = opts.interval || constants.DEFAULT_PARAM.INTERVAL; 20 | this.timeoutValue = opts.timeout || constants.DEFAULT_PARAM.CALLBACK_TIMEOUT; 21 | this.connected = false; 22 | this.closed = false; 23 | this.opts = opts; 24 | }; 25 | util.inherits(MailBox, EventEmitter); 26 | 27 | var pro = MailBox.prototype; 28 | 29 | pro.connect = function(tracer, cb) { 30 | tracer && tracer.info('client', __filename, 'connect', 'ws-mailbox try to connect'); 31 | if (this.connected) { 32 | tracer && tracer.error('client', __filename, 'connect', 'mailbox has already connected'); 33 | cb(new Error('mailbox has already connected.')); 34 | return; 35 | } 36 | var self = this; 37 | this.socket = client.connect(this.host + ':' + this.port, { 38 | 'force new connection': true, 39 | 'reconnect': false 40 | }); 41 | this.socket.on('message', function(pkg) { 42 | try { 43 | if (pkg instanceof Array) { 44 | processMsgs(self, pkg); 45 | } else { 46 | processMsg(self, pkg); 47 | } 48 | } catch (err) { 49 | logger.error('rpc client process message with error: %s', err.stack); 50 | } 51 | }); 52 | 53 | this.socket.on('connect', function() { 54 | if (self.connected) { 55 | return; 56 | } 57 | self.connected = true; 58 | if (self.bufferMsg) { 59 | self._interval = setInterval(function() { 60 | flush(self); 61 | }, self.interval); 62 | } 63 | cb(); 64 | }); 65 | 66 | this.socket.on('error', function(err) { 67 | logger.error('rpc socket is error, remote server host: %s, port: %s', self.host, self.port); 68 | self.emit('close', self.id); 69 | cb(err); 70 | }); 71 | 72 | this.socket.on('disconnect', function(reason) { 73 | logger.error('rpc socket is disconnect, reason: %s', reason); 74 | var reqs = self.requests, 75 | cb; 76 | for (var id in reqs) { 77 | cb = reqs[id]; 78 | cb(tracer, new Error('disconnect with remote server.')); 79 | } 80 | self.emit('close', self.id); 81 | }); 82 | }; 83 | 84 | /** 85 | * close mailbox 86 | */ 87 | pro.close = function() { 88 | if (this.closed) { 89 | return; 90 | } 91 | this.closed = true; 92 | this.connected = false; 93 | if (this._interval) { 94 | clearInterval(this._interval); 95 | this._interval = null; 96 | } 97 | this.socket.disconnect(); 98 | }; 99 | 100 | /** 101 | * send message to remote server 102 | * 103 | * @param msg {service:"", method:"", args:[]} 104 | * @param opts {} attach info to send method 105 | * @param cb declaration decided by remote interface 106 | */ 107 | pro.send = function(tracer, msg, opts, cb) { 108 | tracer && tracer.info('client', __filename, 'send', 'ws-mailbox try to send'); 109 | if (!this.connected) { 110 | tracer && tracer.error('client', __filename, 'send', 'ws-mailbox not init'); 111 | cb(tracer, new Error('ws-mailbox is not init')); 112 | return; 113 | } 114 | 115 | if (this.closed) { 116 | tracer && tracer.error('client', __filename, 'send', 'mailbox has already closed'); 117 | cb(tracer, new Error('ws-mailbox has already closed')); 118 | return; 119 | } 120 | 121 | var id = this.curId++; 122 | this.requests[id] = cb; 123 | setCbTimeout(this, id, tracer, cb); 124 | 125 | var pkg; 126 | if (tracer && tracer.isEnabled) { 127 | pkg = { 128 | traceId: tracer.id, 129 | seqId: tracer.seq, 130 | source: tracer.source, 131 | remote: tracer.remote, 132 | id: id, 133 | msg: msg 134 | }; 135 | } else { 136 | pkg = { 137 | id: id, 138 | msg: msg 139 | }; 140 | } 141 | if (this.bufferMsg) { 142 | enqueue(this, pkg); 143 | } else { 144 | this.socket.emit('message', pkg); 145 | } 146 | }; 147 | 148 | var enqueue = function(mailbox, msg) { 149 | mailbox.queue.push(msg); 150 | }; 151 | 152 | var flush = function(mailbox) { 153 | if (mailbox.closed || !mailbox.queue.length) { 154 | return; 155 | } 156 | mailbox.socket.emit('message', mailbox.queue); 157 | mailbox.queue = []; 158 | }; 159 | 160 | var processMsgs = function(mailbox, pkgs) { 161 | for (var i = 0, l = pkgs.length; i < l; i++) { 162 | processMsg(mailbox, pkgs[i]); 163 | } 164 | }; 165 | 166 | var processMsg = function(mailbox, pkg) { 167 | clearCbTimeout(mailbox, pkg.id); 168 | var cb = mailbox.requests[pkg.id]; 169 | if (!cb) { 170 | return; 171 | } 172 | delete mailbox.requests[pkg.id]; 173 | var rpcDebugLog = mailbox.opts.rpcDebugLog; 174 | var tracer = null; 175 | var sendErr = null; 176 | if (rpcDebugLog) { 177 | tracer = new Tracer(mailbox.opts.rpcLogger, mailbox.opts.rpcDebugLog, mailbox.opts.clientId, pkg.source, pkg.resp, pkg.traceId, pkg.seqId); 178 | } 179 | var pkgResp = pkg.resp; 180 | // var args = [tracer, null]; 181 | 182 | // pkg.resp.forEach(function(arg){ 183 | // args.push(arg); 184 | // }); 185 | 186 | cb(tracer, sendErr, pkgResp); 187 | }; 188 | 189 | var setCbTimeout = function(mailbox, id, tracer, cb) { 190 | var timer = setTimeout(function() { 191 | logger.warn('rpc request is timeout, id: %s, host: %s, port: %s', id, mailbox.host, mailbox.port); 192 | clearCbTimeout(mailbox, id); 193 | if (!!mailbox.requests[id]) { 194 | delete mailbox.requests[id]; 195 | } 196 | logger.error('rpc callback timeout, remote server host: %s, port: %s', mailbox.host, mailbox.port); 197 | cb(tracer, new Error('rpc callback timeout')); 198 | }, mailbox.timeoutValue); 199 | mailbox.timeout[id] = timer; 200 | }; 201 | 202 | var clearCbTimeout = function(mailbox, id) { 203 | if (!mailbox.timeout[id]) { 204 | logger.warn('timer is not exsits, id: %s, host: %s, port: %s', id, mailbox.host, mailbox.port); 205 | return; 206 | } 207 | clearTimeout(mailbox.timeout[id]); 208 | delete mailbox.timeout[id]; 209 | }; 210 | 211 | /** 212 | * Factory method to create mailbox 213 | * 214 | * @param {Object} server remote server info {id:"", host:"", port:""} 215 | * @param {Object} opts construct parameters 216 | * opts.bufferMsg {Boolean} msg should be buffered or send immediately. 217 | * opts.interval {Boolean} msg queue flush interval if bufferMsg is true. default is 50 ms 218 | */ 219 | module.exports.create = function(server, opts) { 220 | return new MailBox(server, opts || {}); 221 | }; -------------------------------------------------------------------------------- /lib/rpc-client/mailboxes/ws2-mailbox.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var utils = require('../../util/utils'); 4 | var wsClient = require('ws'); 5 | var zlib = require('zlib'); 6 | var Tracer = require('../../util/tracer'); 7 | var DEFAULT_CALLBACK_TIMEOUT = 10 * 1000; 8 | var DEFAULT_INTERVAL = 50; 9 | 10 | var KEEP_ALIVE_TIMEOUT = 10 * 1000; 11 | var KEEP_ALIVE_INTERVAL = 30 * 1000; 12 | 13 | var DEFAULT_ZIP_LENGTH = 1024 * 10; 14 | var useZipCompress = false; 15 | 16 | var MailBox = function(server, opts) { 17 | EventEmitter.call(this); 18 | this.id = server.id; 19 | this.host = server.host; 20 | this.port = server.port; 21 | this.requests = {}; 22 | this.timeout = {}; 23 | this.curId = 0; 24 | this.queue = []; 25 | this.bufferMsg = opts.bufferMsg; 26 | this.interval = opts.interval || DEFAULT_INTERVAL; 27 | this.timeoutValue = opts.timeout || DEFAULT_CALLBACK_TIMEOUT; 28 | this.connected = false; 29 | this.closed = false; 30 | this.opts = opts; 31 | this._KPinterval = null; 32 | this._KP_last_ping_time = -1; 33 | this._KP_last_pong_time = -1; 34 | DEFAULT_ZIP_LENGTH = opts.doZipLength || DEFAULT_ZIP_LENGTH; 35 | useZipCompress = opts.useZipCompress || false; 36 | }; 37 | util.inherits(MailBox, EventEmitter); 38 | 39 | var pro = MailBox.prototype; 40 | 41 | pro.connect = function(tracer, cb) { 42 | tracer && tracer.info('client', __filename, 'connect', 'ws-mailbox try to connect'); 43 | if (this.connected) { 44 | tracer && tracer.error('client', __filename, 'connect', 'mailbox has already connected'); 45 | cb(new Error('mailbox has already connected.')); 46 | return; 47 | } 48 | 49 | this.socket = wsClient.connect('ws://' + this.host + ':' + this.port); 50 | //this.socket = wsClient.connect(this.host + ':' + this.port, {'force new connection': true, 'reconnect': false}); 51 | 52 | var self = this; 53 | this.socket.on('message', function(data, flags) { 54 | try { 55 | // console.log("ws rpc client received message = " + data); 56 | var msg = data; 57 | 58 | msg = JSON.parse(msg); 59 | 60 | if (msg.body instanceof Array) { 61 | processMsgs(self, msg.body); 62 | } else { 63 | processMsg(self, msg.body); 64 | } 65 | } catch (e) { 66 | console.error('ws rpc client process message with error: %j', e.stack); 67 | } 68 | }); 69 | 70 | this.socket.on('open', function() { 71 | if (self.connected) { 72 | //ignore reconnect 73 | return; 74 | } 75 | // success to connect 76 | self.connected = true; 77 | if (self.bufferMsg) { 78 | // start flush interval 79 | self._interval = setInterval(function() { 80 | flush(self); 81 | }, self.interval); 82 | } 83 | self._KPinterval = setInterval(function() { 84 | checkKeepAlive(self); 85 | }, KEEP_ALIVE_INTERVAL); 86 | utils.invokeCallback(cb); 87 | }); 88 | 89 | this.socket.on('error', function(err) { 90 | utils.invokeCallback(cb, err); 91 | self.close(); 92 | }); 93 | 94 | this.socket.on('close', function(code, message) { 95 | var reqs = self.requests, 96 | cb; 97 | for (var id in reqs) { 98 | cb = reqs[id]; 99 | utils.invokeCallback(cb, new Error('disconnect with remote server.')); 100 | } 101 | self.emit('close', self.id); 102 | self.close(); 103 | }); 104 | 105 | // this.socket.on('ping', function (data, flags) { 106 | // }); 107 | this.socket.on('pong', function(data, flags) { 108 | ////console.log('ws received pong: %s', data); 109 | self._KP_last_pong_time = Date.now(); 110 | }); 111 | 112 | }; 113 | 114 | var checkKeepAlive = function(mailbox) { 115 | if (mailbox.closed) { 116 | return; 117 | } 118 | var now = Date.now(); 119 | if (mailbox._KP_last_ping_time > 0) { 120 | if (mailbox._KP_last_pong_time < mailbox._KP_last_ping_time) { 121 | if (now - mailbox._KP_last_ping_time > KEEP_ALIVE_TIMEOUT) { 122 | console.error('ws rpc client checkKeepAlive error because > KEEP_ALIVE_TIMEOUT'); 123 | mailbox.close(); 124 | return; 125 | } else { 126 | return; 127 | } 128 | } 129 | if (mailbox._KP_last_pong_time >= mailbox._KP_last_ping_time) { 130 | mailbox.socket.ping(); 131 | mailbox._KP_last_ping_time = Date.now(); 132 | return; 133 | } 134 | } else { 135 | mailbox.socket.ping(); 136 | mailbox._KP_last_ping_time = Date.now(); 137 | } 138 | }; 139 | 140 | /** 141 | * close mailbox 142 | */ 143 | pro.close = function() { 144 | if (this.closed) { 145 | return; 146 | } 147 | this.closed = true; 148 | this.connected = false; 149 | if (this._interval) { 150 | clearInterval(this._interval); 151 | this._interval = null; 152 | } 153 | if (this._KPinterval) { 154 | clearInterval(this._KPinterval); 155 | this._KPinterval = null; 156 | } 157 | this.socket.close(); 158 | this._KP_last_ping_time = -1; 159 | this._KP_last_pong_time = -1; 160 | }; 161 | 162 | /** 163 | * send message to remote server 164 | * 165 | * @param msg {service:"", method:"", args:[]} 166 | * @param opts {} attach info to send method 167 | * @param cb declaration decided by remote interface 168 | */ 169 | pro.send = function(tracer, msg, opts, cb) { 170 | tracer && tracer.info('client', __filename, 'send', 'ws-mailbox try to send'); 171 | if (!this.connected) { 172 | tracer && tracer.error('client', __filename, 'send', 'ws-mailbox not init'); 173 | cb(tracer, new Error('not init.')); 174 | return; 175 | } 176 | 177 | if (this.closed) { 178 | tracer && tracer.error('client', __filename, 'send', 'mailbox alread closed'); 179 | cb(tracer, new Error('mailbox alread closed.')); 180 | return; 181 | } 182 | 183 | var id = this.curId++; 184 | this.requests[id] = cb; 185 | setCbTimeout(this, id); 186 | 187 | var pkg; 188 | if (tracer && tracer.isEnabled) { 189 | pkg = { 190 | traceId: tracer.id, 191 | seqId: tracer.seq, 192 | source: tracer.source, 193 | remote: tracer.remote, 194 | id: id, 195 | msg: msg 196 | }; 197 | } else { 198 | pkg = { 199 | id: id, 200 | msg: msg 201 | }; 202 | } 203 | if (this.bufferMsg) { 204 | enqueue(this, pkg); 205 | } else { 206 | doSend(this.socket, pkg); 207 | //this.socket.send(JSON.stringify({body: pkg})); 208 | } 209 | }; 210 | 211 | var enqueue = function(mailbox, msg) { 212 | mailbox.queue.push(msg); 213 | }; 214 | 215 | var flush = function(mailbox) { 216 | if (mailbox.closed || !mailbox.queue.length) { 217 | return; 218 | } 219 | doSend(mailbox.socket, mailbox.queue); 220 | //mailbox.socket.send(JSON.stringify({body: mailbox.queue})); 221 | mailbox.queue = []; 222 | }; 223 | 224 | var doSend = function(socket, dataObj) { 225 | var str = JSON.stringify({ 226 | body: dataObj 227 | }); 228 | // console.log("ws rpc client send str = " + str); 229 | //console.log("ws rpc client send str len = " + str.length); 230 | //console.log("ws rpc client send message, len = " + str.length); 231 | socket.send(str); 232 | }; 233 | 234 | var processMsgs = function(mailbox, pkgs) { 235 | for (var i = 0, l = pkgs.length; i < l; i++) { 236 | processMsg(mailbox, pkgs[i]); 237 | } 238 | }; 239 | 240 | var processMsg = function(mailbox, pkg) { 241 | clearCbTimeout(mailbox, pkg.id); 242 | var cb = mailbox.requests[pkg.id]; 243 | if (!cb) { 244 | return; 245 | } 246 | delete mailbox.requests[pkg.id]; 247 | var rpcDebugLog = mailbox.opts.rpcDebugLog; 248 | var tracer = null; 249 | var sendErr = null; 250 | if (rpcDebugLog) { 251 | tracer = new Tracer(mailbox.opts.rpcLogger, mailbox.opts.rpcDebugLog, mailbox.opts.clientId, pkg.source, pkg.resp, pkg.traceId, pkg.seqId); 252 | } 253 | var pkgResp = pkg.resp; 254 | // var args = [tracer, null]; 255 | 256 | // pkg.resp.forEach(function(arg){ 257 | // args.push(arg); 258 | // }); 259 | 260 | cb(tracer, sendErr, pkgResp); 261 | }; 262 | 263 | var setCbTimeout = function(mailbox, id) { 264 | var timer = setTimeout(function() { 265 | clearCbTimeout(mailbox, id); 266 | if (!!mailbox.requests[id]) { 267 | delete mailbox.requests[id]; 268 | } 269 | }, mailbox.timeoutValue); 270 | mailbox.timeout[id] = timer; 271 | }; 272 | 273 | var clearCbTimeout = function(mailbox, id) { 274 | if (!mailbox.timeout[id]) { 275 | console.warn('timer is not exsits, id: %s', id); 276 | return; 277 | } 278 | clearTimeout(mailbox.timeout[id]); 279 | delete mailbox.timeout[id]; 280 | }; 281 | 282 | /** 283 | * Factory method to create mailbox 284 | * 285 | * @param {Object} server remote server info {id:"", host:"", port:""} 286 | * @param {Object} opts construct parameters 287 | * opts.bufferMsg {Boolean} msg should be buffered or send immediately. 288 | * opts.interval {Boolean} msg queue flush interval if bufferMsg is true. default is 50 ms 289 | */ 290 | module.exports.create = function(server, opts) { 291 | return new MailBox(server, opts || {}); 292 | }; -------------------------------------------------------------------------------- /lib/rpc-client/router.js: -------------------------------------------------------------------------------- 1 | var ConsistentHash = require('../util/consistentHash'); 2 | var utils = require('../util/utils'); 3 | var crc = require('crc'); 4 | 5 | /** 6 | * Calculate route info and return an appropriate server id. 7 | * 8 | * @param session {Object} session object for current rpc request 9 | * @param msg {Object} rpc message. {serverType, service, method, args, opts} 10 | * @param context {Object} context of client 11 | * @param cb(err, serverId) 12 | */ 13 | var defRoute = function(session, msg, context, cb) { 14 | var list = context.getServersByType(msg.serverType); 15 | if (!list || !list.length) { 16 | cb(new Error('can not find server info for type:' + msg.serverType)); 17 | return; 18 | } 19 | var uid = session ? (session.uid || '') : ''; 20 | var index = Math.abs(crc.crc32(uid.toString())) % list.length; 21 | cb(null, list[index].id); 22 | }; 23 | 24 | /** 25 | * Random algorithm for calculating server id. 26 | * 27 | * @param client {Object} rpc client. 28 | * @param serverType {String} rpc target serverType. 29 | * @param msg {Object} rpc message. 30 | * @param cb {Function} cb(err, serverId). 31 | */ 32 | var rdRoute = function(client, serverType, msg, cb) { 33 | var servers = client._station.serversMap[serverType]; 34 | if (!servers || !servers.length) { 35 | cb(new Error('rpc servers not exist with serverType: ' + serverType)); 36 | return; 37 | } 38 | var index = Math.floor(Math.random() * servers.length); 39 | cb(null, servers[index]); 40 | }; 41 | 42 | /** 43 | * Round-Robin algorithm for calculating server id. 44 | * 45 | * @param client {Object} rpc client. 46 | * @param serverType {String} rpc target serverType. 47 | * @param msg {Object} rpc message. 48 | * @param cb {Function} cb(err, serverId). 49 | */ 50 | var rrRoute = function(client, serverType, msg, cb) { 51 | var servers = client._station.serversMap[serverType]; 52 | if (!servers || !servers.length) { 53 | cb(new Error('rpc servers not exist with serverType: ' + serverType)); 54 | return; 55 | } 56 | var index; 57 | if (!client.rrParam) { 58 | client.rrParam = {}; 59 | } 60 | if (!!client.rrParam[serverType]) { 61 | index = client.rrParam[serverType]; 62 | } else { 63 | index = 0; 64 | } 65 | cb(null, servers[index % servers.length]); 66 | if (index++ === Number.MAX_VALUE) { 67 | index = 0; 68 | } 69 | client.rrParam[serverType] = index; 70 | }; 71 | 72 | /** 73 | * Weight-Round-Robin algorithm for calculating server id. 74 | * 75 | * @param client {Object} rpc client. 76 | * @param serverType {String} rpc target serverType. 77 | * @param msg {Object} rpc message. 78 | * @param cb {Function} cb(err, serverId). 79 | */ 80 | var wrrRoute = function(client, serverType, msg, cb) { 81 | var servers = client._station.serversMap[serverType]; 82 | if (!servers || !servers.length) { 83 | cb(new Error('rpc servers not exist with serverType: ' + serverType)); 84 | return; 85 | } 86 | var index, weight; 87 | if (!client.wrrParam) { 88 | client.wrrParam = {}; 89 | } 90 | if (!!client.wrrParam[serverType]) { 91 | index = client.wrrParam[serverType].index; 92 | weight = client.wrrParam[serverType].weight; 93 | } else { 94 | index = -1; 95 | weight = 0; 96 | } 97 | var getMaxWeight = function() { 98 | var maxWeight = -1; 99 | for (var i = 0; i < servers.length; i++) { 100 | var server = client._station.servers[servers[i]]; 101 | if (!!server.weight && server.weight > maxWeight) { 102 | maxWeight = server.weight; 103 | } 104 | } 105 | return maxWeight; 106 | }; 107 | while (true) { 108 | index = (index + 1) % servers.length; 109 | if (index === 0) { 110 | weight = weight - 1; 111 | if (weight <= 0) { 112 | weight = getMaxWeight(); 113 | if (weight <= 0) { 114 | cb(new Error('rpc wrr route get invalid weight.')); 115 | return; 116 | } 117 | } 118 | } 119 | var server = client._station.servers[servers[index]]; 120 | if (server.weight >= weight) { 121 | client.wrrParam[serverType] = { 122 | index: index, 123 | weight: weight 124 | }; 125 | cb(null, server.id); 126 | return; 127 | } 128 | } 129 | }; 130 | 131 | /** 132 | * Least-Active algorithm for calculating server id. 133 | * 134 | * @param client {Object} rpc client. 135 | * @param serverType {String} rpc target serverType. 136 | * @param msg {Object} rpc message. 137 | * @param cb {Function} cb(err, serverId). 138 | */ 139 | var laRoute = function(client, serverType, msg, cb) { 140 | var servers = client._station.serversMap[serverType]; 141 | if (!servers || !servers.length) { 142 | return cb(new Error('rpc servers not exist with serverType: ' + serverType)); 143 | } 144 | var actives = []; 145 | if (!client.laParam) { 146 | client.laParam = {}; 147 | } 148 | if (!!client.laParam[serverType]) { 149 | for (var j = 0; j < servers.length; j++) { 150 | var count = client.laParam[serverType][servers[j]]; 151 | if (!count) { 152 | client.laParam[servers[j]] = count = 0; 153 | } 154 | actives.push(count); 155 | } 156 | } else { 157 | client.laParam[serverType] = {}; 158 | for (var i = 0; i < servers.length; i++) { 159 | client.laParam[serverType][servers[i]] = 0; 160 | actives.push(0); 161 | } 162 | } 163 | var rs = []; 164 | var minInvoke = Number.MAX_VALUE; 165 | for (var k = 0; k < actives.length; k++) { 166 | if (actives[k] < minInvoke) { 167 | minInvoke = actives[k]; 168 | rs = []; 169 | rs.push(servers[k]); 170 | } else if (actives[k] === minInvoke) { 171 | rs.push(servers[k]); 172 | } 173 | } 174 | var index = Math.floor(Math.random() * rs.length); 175 | var serverId = rs[index]; 176 | client.laParam[serverType][serverId] += 1; 177 | cb(null, serverId); 178 | }; 179 | 180 | /** 181 | * Consistent-Hash algorithm for calculating server id. 182 | * 183 | * @param client {Object} rpc client. 184 | * @param serverType {String} rpc target serverType. 185 | * @param msg {Object} rpc message. 186 | * @param cb {Function} cb(err, serverId). 187 | */ 188 | var chRoute = function(client, serverType, msg, cb) { 189 | var servers = client._station.serversMap[serverType]; 190 | if (!servers || !servers.length) { 191 | return cb(new Error('rpc servers not exist with serverType: ' + serverType)); 192 | } 193 | 194 | var index, con; 195 | if (!client.chParam) { 196 | client.chParam = {}; 197 | } 198 | if (!!client.chParam[serverType]) { 199 | con = client.chParam[serverType].consistentHash; 200 | } else { 201 | client.opts.station = client._station; 202 | con = new ConsistentHash(servers, client.opts); 203 | } 204 | var hashFieldIndex = client.opts.hashFieldIndex; 205 | var field = msg.args[hashFieldIndex] || JSON.stringify(msg); 206 | cb(null, con.getNode(field)); 207 | client.chParam[serverType] = { 208 | consistentHash: con 209 | }; 210 | }; 211 | 212 | module.exports = { 213 | rr: rrRoute, 214 | wrr: wrrRoute, 215 | la: laRoute, 216 | ch: chRoute, 217 | rd: rdRoute, 218 | df: defRoute 219 | }; -------------------------------------------------------------------------------- /lib/rpc-server/acceptor.js: -------------------------------------------------------------------------------- 1 | var acceptor = require('./acceptors/mqtt-acceptor'); 2 | // var acceptor = require('./acceptors/ws2-acceptor'); 3 | 4 | module.exports.create = function(opts, cb) { 5 | return acceptor.create(opts, cb); 6 | }; -------------------------------------------------------------------------------- /lib/rpc-server/acceptors/mqtt-acceptor.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('pomelo-rpc', 'mqtt-acceptor'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var Tracer = require('../../util/tracer'); 4 | var utils = require('../../util/utils'); 5 | var MqttCon = require('mqtt-connection'); 6 | var util = require('util'); 7 | var net = require('net'); 8 | 9 | var curId = 1; 10 | 11 | var Acceptor = function(opts, cb) { 12 | EventEmitter.call(this); 13 | this.interval = opts.interval; // flush interval in ms 14 | this.bufferMsg = opts.bufferMsg; 15 | this.rpcLogger = opts.rpcLogger; 16 | this.rpcDebugLog = opts.rpcDebugLog; 17 | this._interval = null; // interval object 18 | this.sockets = {}; 19 | this.msgQueues = {}; 20 | this.cb = cb; 21 | }; 22 | 23 | util.inherits(Acceptor, EventEmitter); 24 | 25 | var pro = Acceptor.prototype; 26 | 27 | pro.listen = function(port) { 28 | //check status 29 | if (!!this.inited) { 30 | this.cb(new Error('already inited.')); 31 | return; 32 | } 33 | this.inited = true; 34 | 35 | var self = this; 36 | 37 | this.server = new net.Server(); 38 | this.server.listen(port); 39 | 40 | this.server.on('error', function(err) { 41 | logger.error('rpc server is error: %j', err.stack); 42 | self.emit('error', err); 43 | }); 44 | 45 | this.server.on('connection', function(stream) { 46 | var socket = MqttCon(stream); 47 | socket['id'] = curId++; 48 | 49 | socket.on('connect', function(pkg) { 50 | console.log('connected'); 51 | }); 52 | 53 | socket.on('publish', function(pkg) { 54 | pkg = pkg.payload.toString(); 55 | var isArray = false; 56 | try { 57 | pkg = JSON.parse(pkg); 58 | if (pkg instanceof Array) { 59 | processMsgs(socket, self, pkg); 60 | isArray = true; 61 | } else { 62 | processMsg(socket, self, pkg); 63 | } 64 | } catch (err) { 65 | if (!isArray) { 66 | doSend(socket, { 67 | id: pkg.id, 68 | resp: [cloneError(err)] 69 | }); 70 | } 71 | logger.error('process rpc message error %s', err.stack); 72 | } 73 | }); 74 | 75 | socket.on('pingreq', function() { 76 | socket.pingresp(); 77 | }); 78 | 79 | socket.on('error', function() { 80 | self.onSocketClose(socket); 81 | }); 82 | 83 | socket.on('close', function() { 84 | self.onSocketClose(socket); 85 | }); 86 | 87 | self.sockets[socket.id] = socket; 88 | 89 | socket.on('disconnect', function(reason) { 90 | self.onSocketClose(socket); 91 | }); 92 | }); 93 | 94 | if (this.bufferMsg) { 95 | this._interval = setInterval(function() { 96 | flush(self); 97 | }, this.interval); 98 | } 99 | }; 100 | 101 | pro.close = function() { 102 | if (this.closed) { 103 | return; 104 | } 105 | this.closed = true; 106 | if (this._interval) { 107 | clearInterval(this._interval); 108 | this._interval = null; 109 | } 110 | this.server.close(); 111 | this.emit('closed'); 112 | }; 113 | 114 | pro.onSocketClose = function(socket) { 115 | if (!socket['closed']) { 116 | var id = socket.id; 117 | socket['closed'] = true; 118 | delete this.sockets[id]; 119 | delete this.msgQueues[id]; 120 | } 121 | } 122 | 123 | var cloneError = function(origin) { 124 | // copy the stack infos for Error instance json result is empty 125 | var res = { 126 | msg: origin.msg, 127 | stack: origin.stack 128 | }; 129 | return res; 130 | }; 131 | 132 | var processMsg = function(socket, acceptor, pkg) { 133 | var tracer = null; 134 | if (this.rpcDebugLog) { 135 | tracer = new Tracer(acceptor.rpcLogger, acceptor.rpcDebugLog, pkg.remote, pkg.source, pkg.msg, pkg.traceId, pkg.seqId); 136 | tracer.info('server', __filename, 'processMsg', 'mqtt-acceptor receive message and try to process message'); 137 | } 138 | acceptor.cb(tracer, pkg.msg, function() { 139 | // var args = Array.prototype.slice.call(arguments, 0); 140 | var len = arguments.length; 141 | var args = new Array(len); 142 | for (var i = 0; i < len; i++) { 143 | args[i] = arguments[i]; 144 | } 145 | 146 | var errorArg = args[0]; // first callback argument can be error object, the others are message 147 | if (errorArg && errorArg instanceof Error) { 148 | args[0] = cloneError(errorArg); 149 | } 150 | 151 | var resp; 152 | if (tracer && tracer.isEnabled) { 153 | resp = { 154 | traceId: tracer.id, 155 | seqId: tracer.seq, 156 | source: tracer.source, 157 | id: pkg.id, 158 | resp: args 159 | }; 160 | } else { 161 | resp = { 162 | id: pkg.id, 163 | resp: args 164 | }; 165 | } 166 | if (acceptor.bufferMsg) { 167 | enqueue(socket, acceptor, resp); 168 | } else { 169 | doSend(socket, resp); 170 | } 171 | }); 172 | }; 173 | 174 | var processMsgs = function(socket, acceptor, pkgs) { 175 | for (var i = 0, l = pkgs.length; i < l; i++) { 176 | processMsg(socket, acceptor, pkgs[i]); 177 | } 178 | }; 179 | 180 | var enqueue = function(socket, acceptor, msg) { 181 | var id = socket.id; 182 | var queue = acceptor.msgQueues[id]; 183 | if (!queue) { 184 | queue = acceptor.msgQueues[id] = []; 185 | } 186 | queue.push(msg); 187 | }; 188 | 189 | var flush = function(acceptor) { 190 | var sockets = acceptor.sockets, 191 | queues = acceptor.msgQueues, 192 | queue, socket; 193 | for (var socketId in queues) { 194 | socket = sockets[socketId]; 195 | if (!socket) { 196 | // clear pending messages if the socket not exist any more 197 | delete queues[socketId]; 198 | continue; 199 | } 200 | queue = queues[socketId]; 201 | if (!queue.length) { 202 | continue; 203 | } 204 | doSend(socket, queue); 205 | queues[socketId] = []; 206 | } 207 | }; 208 | 209 | var doSend = function(socket, msg) { 210 | socket.publish({ 211 | topic: 'rpc', 212 | payload: JSON.stringify(msg) 213 | }); 214 | } 215 | 216 | /** 217 | * create acceptor 218 | * 219 | * @param opts init params 220 | * @param cb(tracer, msg, cb) callback function that would be invoked when new message arrives 221 | */ 222 | module.exports.create = function(opts, cb) { 223 | return new Acceptor(opts || {}, cb); 224 | }; -------------------------------------------------------------------------------- /lib/rpc-server/acceptors/mqtt2-acceptor.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('pomelo-rpc', 'mqtt2-acceptor'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var Constant = require('../../util/constants'); 4 | var Tracer = require('../../util/tracer'); 5 | var utils = require('../../util/utils'); 6 | var Coder = require('../../util/coder'); 7 | var MqttCon = require('mqtt-connection'); 8 | var util = require('util'); 9 | var net = require('net'); 10 | 11 | var curId = 1; 12 | 13 | var Acceptor = function(opts, cb) { 14 | EventEmitter.call(this); 15 | this.interval = opts.interval; // flush interval in ms 16 | this.bufferMsg = opts.bufferMsg; 17 | this.rpcLogger = opts.rpcLogger; 18 | this.rpcDebugLog = opts.rpcDebugLog; 19 | this.services = opts.services; 20 | this._interval = null; // interval object 21 | this.sockets = {}; 22 | this.msgQueues = {}; 23 | this.servicesMap = {}; 24 | this.cb = cb; 25 | }; 26 | 27 | util.inherits(Acceptor, EventEmitter); 28 | 29 | var pro = Acceptor.prototype; 30 | 31 | pro.listen = function(port) { 32 | //check status 33 | if (!!this.inited) { 34 | this.cb(new Error('already inited.')); 35 | return; 36 | } 37 | this.inited = true; 38 | 39 | var self = this; 40 | 41 | this.server = new net.Server(); 42 | this.server.listen(port); 43 | 44 | this.server.on('error', function(err) { 45 | logger.error('rpc server is error: %j', err.stack); 46 | self.emit('error', err); 47 | }); 48 | 49 | this.server.on('connection', function(stream) { 50 | var socket = MqttCon(stream); 51 | socket['id'] = curId++; 52 | 53 | socket.on('connect', function(pkg) { 54 | console.log('connected'); 55 | sendHandshake(socket, self); 56 | }); 57 | 58 | socket.on('publish', function(pkg) { 59 | pkg = Coder.decodeServer(pkg.payload, self.servicesMap); 60 | try { 61 | processMsg(socket, self, pkg); 62 | } catch (err) { 63 | var resp = Coder.encodeServer(pkg.id, [cloneError(err)]); 64 | // doSend(socket, resp); 65 | logger.error('process rpc message error %s', err.stack); 66 | } 67 | }); 68 | 69 | socket.on('pingreq', function() { 70 | socket.pingresp(); 71 | }); 72 | 73 | socket.on('error', function() { 74 | self.onSocketClose(socket); 75 | }); 76 | 77 | socket.on('close', function() { 78 | self.onSocketClose(socket); 79 | }); 80 | 81 | self.sockets[socket.id] = socket; 82 | 83 | socket.on('disconnect', function(reason) { 84 | self.onSocketClose(socket); 85 | }); 86 | }); 87 | 88 | if (this.bufferMsg) { 89 | this._interval = setInterval(function() { 90 | flush(self); 91 | }, this.interval); 92 | } 93 | }; 94 | 95 | pro.close = function() { 96 | if (this.closed) { 97 | return; 98 | } 99 | this.closed = true; 100 | if (this._interval) { 101 | clearInterval(this._interval); 102 | this._interval = null; 103 | } 104 | this.server.close(); 105 | this.emit('closed'); 106 | }; 107 | 108 | pro.onSocketClose = function(socket) { 109 | if (!socket['closed']) { 110 | var id = socket.id; 111 | socket['closed'] = true; 112 | delete this.sockets[id]; 113 | delete this.msgQueues[id]; 114 | } 115 | } 116 | 117 | var cloneError = function(origin) { 118 | // copy the stack infos for Error instance json result is empty 119 | var res = { 120 | msg: origin.msg, 121 | stack: origin.stack 122 | }; 123 | return res; 124 | }; 125 | 126 | var processMsg = function(socket, acceptor, pkg) { 127 | var tracer = null; 128 | if (this.rpcDebugLog) { 129 | tracer = new Tracer(acceptor.rpcLogger, acceptor.rpcDebugLog, pkg.remote, pkg.source, pkg.msg, pkg.traceId, pkg.seqId); 130 | tracer.info('server', __filename, 'processMsg', 'mqtt-acceptor receive message and try to process message'); 131 | } 132 | acceptor.cb(tracer, pkg.msg, function() { 133 | // var args = Array.prototype.slice.call(arguments, 0); 134 | var len = arguments.length; 135 | var args = new Array(len); 136 | for (var i = 0; i < len; i++) { 137 | args[i] = arguments[i]; 138 | } 139 | 140 | var errorArg = args[0]; // first callback argument can be error object, the others are message 141 | if (errorArg && errorArg instanceof Error) { 142 | args[0] = cloneError(errorArg); 143 | } 144 | 145 | var resp; 146 | if (tracer && tracer.isEnabled) { 147 | resp = { 148 | traceId: tracer.id, 149 | seqId: tracer.seq, 150 | source: tracer.source, 151 | id: pkg.id, 152 | resp: args 153 | }; 154 | } else { 155 | resp = Coder.encodeServer(pkg.id, args); 156 | // resp = { 157 | // id: pkg.id, 158 | // resp: args 159 | // }; 160 | } 161 | if (acceptor.bufferMsg) { 162 | enqueue(socket, acceptor, resp); 163 | } else { 164 | doSend(socket, resp); 165 | } 166 | }); 167 | }; 168 | 169 | var processMsgs = function(socket, acceptor, pkgs) { 170 | for (var i = 0, l = pkgs.length; i < l; i++) { 171 | processMsg(socket, acceptor, pkgs[i]); 172 | } 173 | }; 174 | 175 | var enqueue = function(socket, acceptor, msg) { 176 | var id = socket.id; 177 | var queue = acceptor.msgQueues[id]; 178 | if (!queue) { 179 | queue = acceptor.msgQueues[id] = []; 180 | } 181 | queue.push(msg); 182 | }; 183 | 184 | var flush = function(acceptor) { 185 | var sockets = acceptor.sockets, 186 | queues = acceptor.msgQueues, 187 | queue, socket; 188 | for (var socketId in queues) { 189 | socket = sockets[socketId]; 190 | if (!socket) { 191 | // clear pending messages if the socket not exist any more 192 | delete queues[socketId]; 193 | continue; 194 | } 195 | queue = queues[socketId]; 196 | if (!queue.length) { 197 | continue; 198 | } 199 | doSend(socket, queue); 200 | queues[socketId] = []; 201 | } 202 | }; 203 | 204 | var doSend = function(socket, msg) { 205 | socket.publish({ 206 | topic: Constant['TOPIC_RPC'], 207 | payload: msg 208 | // payload: JSON.stringify(msg) 209 | }); 210 | } 211 | 212 | var doSendHandshake = function(socket, msg) { 213 | socket.publish({ 214 | topic: Constant['TOPIC_HANDSHAKE'], 215 | payload: msg 216 | // payload: JSON.stringify(msg) 217 | }); 218 | } 219 | 220 | var sendHandshake = function(socket, acceptor) { 221 | var servicesMap = utils.genServicesMap(acceptor.services); 222 | acceptor.servicesMap = servicesMap; 223 | doSendHandshake(socket, JSON.stringify(servicesMap)); 224 | } 225 | 226 | /** 227 | * create acceptor 228 | * 229 | * @param opts init params 230 | * @param cb(tracer, msg, cb) callback function that would be invoked when new message arrives 231 | */ 232 | module.exports.create = function(opts, cb) { 233 | return new Acceptor(opts || {}, cb); 234 | }; -------------------------------------------------------------------------------- /lib/rpc-server/acceptors/tcp-acceptor.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var Tracer = require('../../util/tracer'); 3 | var utils = require('../../util/utils'); 4 | var Composer = require('stream-pkg'); 5 | var util = require('util'); 6 | var net = require('net'); 7 | 8 | var Acceptor = function(opts, cb) { 9 | EventEmitter.call(this); 10 | opts = opts || {}; 11 | this.bufferMsg = opts.bufferMsg; 12 | this.interval = opts.interval; // flush interval in ms 13 | this.pkgSize = opts.pkgSize; 14 | this._interval = null; // interval object 15 | this.server = null; 16 | this.sockets = {}; 17 | this.msgQueues = {}; 18 | this.cb = cb; 19 | }; 20 | 21 | util.inherits(Acceptor, EventEmitter); 22 | 23 | var pro = Acceptor.prototype; 24 | 25 | pro.listen = function(port) { 26 | //check status 27 | if (!!this.inited) { 28 | utils.invokeCallback(this.cb, new Error('already inited.')); 29 | return; 30 | } 31 | this.inited = true; 32 | 33 | var self = this; 34 | 35 | this.server = net.createServer(); 36 | this.server.listen(port); 37 | 38 | this.server.on('error', function(err) { 39 | self.emit('error', err, this); 40 | }); 41 | 42 | this.server.on('connection', function(socket) { 43 | self.sockets[socket.id] = socket; 44 | socket.composer = new Composer({ 45 | maxLength: self.pkgSize 46 | }); 47 | 48 | socket.on('data', function(data) { 49 | socket.composer.feed(data); 50 | }); 51 | 52 | socket.composer.on('data', function(data) { 53 | var pkg = JSON.parse(data.toString()); 54 | if (pkg instanceof Array) { 55 | processMsgs(socket, self, pkg); 56 | } else { 57 | processMsg(socket, self, pkg); 58 | } 59 | }); 60 | 61 | socket.on('close', function() { 62 | delete self.sockets[socket.id]; 63 | delete self.msgQueues[socket.id]; 64 | }); 65 | }); 66 | 67 | if (this.bufferMsg) { 68 | this._interval = setInterval(function() { 69 | flush(self); 70 | }, this.interval); 71 | } 72 | }; 73 | 74 | pro.close = function() { 75 | if (!!this.closed) { 76 | return; 77 | } 78 | this.closed = true; 79 | if (this._interval) { 80 | clearInterval(this._interval); 81 | this._interval = null; 82 | } 83 | try { 84 | this.server.close(); 85 | } catch (err) { 86 | console.error('rpc server close error: %j', err.stack); 87 | } 88 | this.emit('closed'); 89 | }; 90 | 91 | var cloneError = function(origin) { 92 | // copy the stack infos for Error instance json result is empty 93 | var res = { 94 | msg: origin.msg, 95 | stack: origin.stack 96 | }; 97 | return res; 98 | }; 99 | 100 | var processMsg = function(socket, acceptor, pkg) { 101 | var tracer = new Tracer(acceptor.rpcLogger, acceptor.rpcDebugLog, pkg.remote, pkg.source, pkg.msg, pkg.traceId, pkg.seqId); 102 | tracer.info('server', __filename, 'processMsg', 'tcp-acceptor receive message and try to process message'); 103 | acceptor.cb(tracer, pkg.msg, function() { 104 | var args = Array.prototype.slice.call(arguments, 0); 105 | for (var i = 0, l = args.length; i < l; i++) { 106 | if (args[i] instanceof Error) { 107 | args[i] = cloneError(args[i]); 108 | } 109 | } 110 | var resp; 111 | if (tracer.isEnabled) { 112 | resp = { 113 | traceId: tracer.id, 114 | seqId: tracer.seq, 115 | source: tracer.source, 116 | id: pkg.id, 117 | resp: Array.prototype.slice.call(args, 0) 118 | }; 119 | } else { 120 | resp = { 121 | id: pkg.id, 122 | resp: Array.prototype.slice.call(args, 0) 123 | }; 124 | } 125 | if (acceptor.bufferMsg) { 126 | enqueue(socket, acceptor, resp); 127 | } else { 128 | socket.write(socket.composer.compose(JSON.stringify(resp))); 129 | } 130 | }); 131 | }; 132 | 133 | var processMsgs = function(socket, acceptor, pkgs) { 134 | for (var i = 0, l = pkgs.length; i < l; i++) { 135 | processMsg(socket, acceptor, pkgs[i]); 136 | } 137 | }; 138 | 139 | var enqueue = function(socket, acceptor, msg) { 140 | var queue = acceptor.msgQueues[socket.id]; 141 | if (!queue) { 142 | queue = acceptor.msgQueues[socket.id] = []; 143 | } 144 | queue.push(msg); 145 | }; 146 | 147 | var flush = function(acceptor) { 148 | var sockets = acceptor.sockets, 149 | queues = acceptor.msgQueues, 150 | queue, socket; 151 | for (var socketId in queues) { 152 | socket = sockets[socketId]; 153 | if (!socket) { 154 | // clear pending messages if the socket not exist any more 155 | delete queues[socketId]; 156 | continue; 157 | } 158 | queue = queues[socketId]; 159 | if (!queue.length) { 160 | continue; 161 | } 162 | socket.write(socket.composer.compose(JSON.stringify(queue))); 163 | queues[socketId] = []; 164 | } 165 | }; 166 | 167 | /** 168 | * create acceptor 169 | * 170 | * @param opts init params 171 | * @param cb(tracer, msg, cb) callback function that would be invoked when new message arrives 172 | */ 173 | module.exports.create = function(opts, cb) { 174 | return new Acceptor(opts || {}, cb); 175 | }; 176 | 177 | process.on('SIGINT', function() { 178 | process.exit(); 179 | }); -------------------------------------------------------------------------------- /lib/rpc-server/acceptors/ws-acceptor.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('pomelo-rpc', 'ws-acceptor'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var Tracer = require('../../util/tracer'); 4 | var utils = require('../../util/utils'); 5 | var sio = require('socket.io'); 6 | var util = require('util'); 7 | 8 | var Acceptor = function(opts, cb) { 9 | EventEmitter.call(this); 10 | this.bufferMsg = opts.bufferMsg; 11 | this.interval = opts.interval; // flush interval in ms 12 | this.rpcDebugLog = opts.rpcDebugLog; 13 | this.rpcLogger = opts.rpcLogger; 14 | this.whitelist = opts.whitelist; 15 | this._interval = null; // interval object 16 | this.sockets = {}; 17 | this.msgQueues = {}; 18 | this.cb = cb; 19 | }; 20 | 21 | util.inherits(Acceptor, EventEmitter); 22 | 23 | var pro = Acceptor.prototype; 24 | 25 | pro.listen = function(port) { 26 | //check status 27 | if (!!this.inited) { 28 | this.cb(new Error('already inited.')); 29 | return; 30 | } 31 | this.inited = true; 32 | 33 | var self = this; 34 | 35 | this.server = sio.listen(port); 36 | 37 | this.server.set('log level', 0); 38 | 39 | this.server.server.on('error', function(err) { 40 | logger.error('rpc server is error: %j', err.stack); 41 | self.emit('error', err); 42 | }); 43 | 44 | this.server.sockets.on('connection', function(socket) { 45 | self.sockets[socket.id] = socket; 46 | 47 | self.emit('connection', { 48 | id: socket.id, 49 | ip: socket.handshake.address.address 50 | }); 51 | 52 | socket.on('message', function(pkg) { 53 | try { 54 | if (pkg instanceof Array) { 55 | processMsgs(socket, self, pkg); 56 | } else { 57 | processMsg(socket, self, pkg); 58 | } 59 | } catch (e) { 60 | // socke.io would broken if uncaugth the exception 61 | logger.error('rpc server process message error: %j', e.stack); 62 | } 63 | }); 64 | 65 | socket.on('disconnect', function(reason) { 66 | delete self.sockets[socket.id]; 67 | delete self.msgQueues[socket.id]; 68 | }); 69 | }); 70 | 71 | this.on('connection', ipFilter.bind(this)); 72 | 73 | if (this.bufferMsg) { 74 | this._interval = setInterval(function() { 75 | flush(self); 76 | }, this.interval); 77 | } 78 | }; 79 | 80 | var ipFilter = function(obj) { 81 | if (typeof this.whitelist === 'function') { 82 | var self = this; 83 | self.whitelist(function(err, tmpList) { 84 | if (err) { 85 | logger.error('%j.(RPC whitelist).', err); 86 | return; 87 | } 88 | if (!Array.isArray(tmpList)) { 89 | logger.error('%j is not an array.(RPC whitelist).', tmpList); 90 | return; 91 | } 92 | if (!!obj && !!obj.ip && !!obj.id) { 93 | for (var i in tmpList) { 94 | var exp = new RegExp(tmpList[i]); 95 | if (exp.test(obj.ip)) { 96 | return; 97 | } 98 | } 99 | var sock = self.sockets[obj.id]; 100 | if (sock) { 101 | sock.disconnect('unauthorized'); 102 | logger.warn('%s is rejected(RPC whitelist).', obj.ip); 103 | } 104 | } 105 | }); 106 | } 107 | }; 108 | 109 | pro.close = function() { 110 | if (!!this.closed) { 111 | return; 112 | } 113 | this.closed = true; 114 | if (this._interval) { 115 | clearInterval(this._interval); 116 | this._interval = null; 117 | } 118 | try { 119 | this.server.server.close(); 120 | } catch (err) { 121 | logger.error('rpc server close error: %j', err.stack); 122 | } 123 | this.emit('closed'); 124 | }; 125 | 126 | var cloneError = function(origin) { 127 | // copy the stack infos for Error instance json result is empty 128 | var res = { 129 | msg: origin.msg, 130 | stack: origin.stack 131 | }; 132 | return res; 133 | }; 134 | 135 | var processMsg = function(socket, acceptor, pkg) { 136 | var tracer = null; 137 | if (this.rpcDebugLog) { 138 | tracer = new Tracer(acceptor.rpcLogger, acceptor.rpcDebugLog, pkg.remote, pkg.source, pkg.msg, pkg.traceId, pkg.seqId); 139 | tracer.info('server', __filename, 'processMsg', 'ws-acceptor receive message and try to process message'); 140 | } 141 | acceptor.cb(tracer, pkg.msg, function() { 142 | // var args = arguments; 143 | var args = Array.prototype.slice.call(arguments, 0); 144 | var errorArg = args[0]; // first callback argument can be error object, the others are message 145 | if (errorArg instanceof Error) { 146 | args[0] = cloneError(errorArg); 147 | } 148 | // for(var i=0, l=args.length; i=0.0.1", 16 | "should": ">=0.0.1" 17 | } 18 | } -------------------------------------------------------------------------------- /sample/bench_client.js: -------------------------------------------------------------------------------- 1 | var Client = require('..').client; 2 | 3 | // remote service interface path info list 4 | var records = [{ 5 | namespace: 'user', 6 | serverType: 'test', 7 | path: __dirname + '/remote/test' 8 | }]; 9 | 10 | var context = { 11 | serverId: 'test-server-1' 12 | }; 13 | 14 | // server info list 15 | var servers = [{ 16 | id: 'test-server-1', 17 | serverType: 'test', 18 | host: '127.0.0.1', 19 | port: 3333 20 | }]; 21 | 22 | // route parameter passed to route function 23 | var routeParam = null; 24 | 25 | // route context passed to route function 26 | var routeContext = servers; 27 | 28 | // route function to caculate the remote server id 29 | var routeFunc = function(routeParam, msg, routeContext, cb) { 30 | cb(null, routeContext[0].id); 31 | }; 32 | 33 | var client = Client.create({ 34 | routeContext: routeContext, 35 | router: routeFunc, 36 | context: context 37 | }); 38 | 39 | var start = null; 40 | client.start(function(err) { 41 | console.log('rpc client start ok.'); 42 | 43 | client.addProxies(records); 44 | client.addServers(servers); 45 | 46 | start = Date.now(); 47 | run(); 48 | }); 49 | 50 | var num_requests = 100000; 51 | var times = 0; 52 | var mock_data_1 = 'hello'; 53 | var mock_data_2 = 'hello'; 54 | 55 | var num_repeat = 200; // 100 200 300 400 800 56 | 57 | for (var i = 0; i < num_repeat; i++) { 58 | mock_data_2 += mock_data_1; 59 | } 60 | 61 | var mock_data_3 = { 62 | a: 'run', 63 | b: mock_data_2 + Date.now() + '_', 64 | time: Date.now() 65 | } 66 | 67 | var payload = mock_data_3; 68 | 69 | // console.log(new Buffer(payload).length / 1024 + 'k'); 70 | console.log(new Buffer(JSON.stringify(payload)).length / 1024 + 'k'); 71 | 72 | function run() { 73 | if (times > num_requests) { 74 | return; 75 | } 76 | 77 | if (times == num_requests) { 78 | var now = Date.now(); 79 | var cost = now - start; 80 | console.log('run %d num requests cost: %d ops/sec', num_requests, cost, (num_requests / (cost / 1000)).toFixed(2)); 81 | times = 0; 82 | start = now; 83 | // return; 84 | return run(); 85 | } 86 | 87 | times++; 88 | rpcRequest(payload, function() { 89 | run(); 90 | }); 91 | } 92 | 93 | function rpcRequest(param, cb) { 94 | client.proxies.user.test.service.echo(routeParam, param, 123, function(err, resp) { 95 | if (err) { 96 | console.error(err.stack); 97 | } 98 | // console.log(resp); 99 | cb(); 100 | }); 101 | } -------------------------------------------------------------------------------- /sample/bench_mqtt: -------------------------------------------------------------------------------- 1 | mqtt 2 | 0.4931640625k 3 | rpc client start ok. 4 | run 20000 num requests cost: 3715 ops/sec 5383.58 5 | run 20000 num requests cost: 3225 ops/sec 6201.55 6 | run 20000 num requests cost: 3189 ops/sec 6271.56 7 | run 20000 num requests cost: 3198 ops/sec 6253.91 8 | run 20000 num requests cost: 3377 ops/sec 5922.42 9 | run 20000 num requests cost: 3203 ops/sec 6244.15 10 | run 20000 num requests cost: 3528 ops/sec 5668.93 11 | run 20000 num requests cost: 3380 ops/sec 5917.16 12 | run 20000 num requests cost: 3574 ops/sec 5595.97 13 | run 20000 num requests cost: 3458 ops/sec 5783.69 14 | run 20000 num requests cost: 3480 ops/sec 5747.13 15 | run 20000 num requests cost: 3465 ops/sec 5772.01 16 | run 20000 num requests cost: 3619 ops/sec 5526.39 17 | run 20000 num requests cost: 3201 ops/sec 6248.05 18 | run 20000 num requests cost: 3319 ops/sec 6025.91 19 | run 20000 num requests cost: 3071 ops/sec 6512.54 20 | run 20000 num requests cost: 3237 ops/sec 6178.56 21 | run 20000 num requests cost: 3258 ops/sec 6138.74 22 | 23 | mqtt 24 | 0.9814453125k 25 | rpc client start ok. 26 | run 20000 num requests cost: 4140 ops/sec 4830.92 27 | run 20000 num requests cost: 3348 ops/sec 5973.72 28 | run 20000 num requests cost: 3450 ops/sec 5797.10 29 | run 20000 num requests cost: 3198 ops/sec 6253.91 30 | run 20000 num requests cost: 3370 ops/sec 5934.72 31 | run 20000 num requests cost: 3325 ops/sec 6015.04 32 | run 20000 num requests cost: 3364 ops/sec 5945.30 33 | run 20000 num requests cost: 3361 ops/sec 5950.61 34 | run 20000 num requests cost: 3589 ops/sec 5572.58 35 | run 20000 num requests cost: 3307 ops/sec 6047.78 36 | run 20000 num requests cost: 3279 ops/sec 6099.42 37 | run 20000 num requests cost: 3304 ops/sec 6053.27 38 | run 20000 num requests cost: 3368 ops/sec 5938.24 39 | run 20000 num requests cost: 3412 ops/sec 5861.66 40 | run 20000 num requests cost: 3252 ops/sec 6150.06 41 | run 20000 num requests cost: 3301 ops/sec 6058.77 42 | run 20000 num requests cost: 3387 ops/sec 5904.93 43 | 44 | mqtt 45 | 1.4697265625k 46 | rpc client start ok. 47 | run 20000 num requests cost: 3961 ops/sec 5049.23 48 | run 20000 num requests cost: 3451 ops/sec 5795.42 49 | run 20000 num requests cost: 3453 ops/sec 5792.06 50 | run 20000 num requests cost: 3314 ops/sec 6035.00 51 | run 20000 num requests cost: 3822 ops/sec 5232.86 52 | run 20000 num requests cost: 3406 ops/sec 5871.99 53 | run 20000 num requests cost: 3653 ops/sec 5474.95 54 | run 20000 num requests cost: 3572 ops/sec 5599.10 55 | run 20000 num requests cost: 3333 ops/sec 6000.60 56 | run 20000 num requests cost: 3389 ops/sec 5901.45 57 | run 20000 num requests cost: 3563 ops/sec 5613.25 58 | run 20000 num requests cost: 3714 ops/sec 5385.03 59 | run 20000 num requests cost: 3553 ops/sec 5629.05 60 | run 20000 num requests cost: 3642 ops/sec 5491.49 61 | run 20000 num requests cost: 3459 ops/sec 5782.02 62 | run 20000 num requests cost: 3452 ops/sec 5793.74 63 | run 20000 num requests cost: 3734 ops/sec 5356.19 64 | run 20000 num requests cost: 3794 ops/sec 5271.48 65 | run 20000 num requests cost: 3513 ops/sec 5693.14 66 | 67 | mqtt 68 | 1.9580078125k 69 | rpc client start ok. 70 | run 20000 num requests cost: 4395 ops/sec 4550.63 71 | run 20000 num requests cost: 3658 ops/sec 5467.47 72 | run 20000 num requests cost: 3851 ops/sec 5193.46 73 | run 20000 num requests cost: 3665 ops/sec 5457.03 74 | run 20000 num requests cost: 3841 ops/sec 5206.98 75 | run 20000 num requests cost: 3571 ops/sec 5600.67 76 | run 20000 num requests cost: 3903 ops/sec 5124.26 77 | run 20000 num requests cost: 4039 ops/sec 4951.72 78 | run 20000 num requests cost: 4023 ops/sec 4971.41 79 | run 20000 num requests cost: 3878 ops/sec 5157.30 80 | run 20000 num requests cost: 3763 ops/sec 5314.91 81 | run 20000 num requests cost: 3892 ops/sec 5138.75 82 | run 20000 num requests cost: 3851 ops/sec 5193.46 83 | run 20000 num requests cost: 3878 ops/sec 5157.30 84 | run 20000 num requests cost: 3880 ops/sec 5154.64 85 | run 20000 num requests cost: 3776 ops/sec 5296.61 86 | run 20000 num requests cost: 3689 ops/sec 5421.52 87 | run 20000 num requests cost: 3715 ops/sec 5383.58 88 | run 20000 num requests cost: 3923 ops/sec 5098.14 89 | run 20000 num requests cost: 3628 ops/sec 5512.68 90 | run 20000 num requests cost: 3998 ops/sec 5002.50 91 | 92 | mqtt 93 | 3.9111328125k 94 | rpc client start ok. 95 | run 20000 num requests cost: 5886 ops/sec 3397.89 96 | run 20000 num requests cost: 4748 ops/sec 4212.30 97 | run 20000 num requests cost: 4847 ops/sec 4126.26 98 | run 20000 num requests cost: 4672 ops/sec 4280.82 99 | run 20000 num requests cost: 4462 ops/sec 4482.29 100 | run 20000 num requests cost: 4438 ops/sec 4506.53 101 | run 20000 num requests cost: 4428 ops/sec 4516.71 102 | run 20000 num requests cost: 4205 ops/sec 4756.24 103 | run 20000 num requests cost: 4552 ops/sec 4393.67 104 | run 20000 num requests cost: 4614 ops/sec 4334.63 105 | run 20000 num requests cost: 4400 ops/sec 4545.45 106 | run 20000 num requests cost: 4540 ops/sec 4405.29 107 | run 20000 num requests cost: 4471 ops/sec 4473.27 108 | run 20000 num requests cost: 4674 ops/sec 4278.99 109 | run 20000 num requests cost: 4734 ops/sec 4224.76 110 | run 20000 num requests cost: 5030 ops/sec 3976.14 111 | run 20000 num requests cost: 5079 ops/sec 3937.78 112 | run 20000 num requests cost: 4413 ops/sec 4532.06 113 | run 20000 num requests cost: 4643 ops/sec 4307.56 -------------------------------------------------------------------------------- /sample/bench_server: -------------------------------------------------------------------------------- 1 | 43980 2 | 63180 -------------------------------------------------------------------------------- /sample/buffer/buffer.js: -------------------------------------------------------------------------------- 1 | var m = new Buffer('hello'); 2 | console.log('old length %d', m.length); 3 | var p = JSON.stringify(m); 4 | var q = JSON.parse(p); 5 | console.log(p); 6 | console.log('stringify length %d', new Buffer(p).length); 7 | console.log(q); 8 | var buf = new Buffer(q.data); 9 | console.log(buf.toString()) -------------------------------------------------------------------------------- /sample/client.js: -------------------------------------------------------------------------------- 1 | var Client = require('..').client; 2 | 3 | // remote service interface path info list 4 | var records = [ 5 | {namespace: 'user', serverType: 'test', path: __dirname + '/remote/test'} 6 | ]; 7 | 8 | var context = { 9 | serverId: 'test-server-1' 10 | }; 11 | 12 | // server info list 13 | var servers = [ 14 | {id: 'test-server-1', serverType: 'test', host: '127.0.0.1', port: 3333} 15 | ]; 16 | 17 | // route parameter passed to route function 18 | var routeParam = null; 19 | 20 | // route context passed to route function 21 | var routeContext = servers; 22 | 23 | // route function to caculate the remote server id 24 | var routeFunc = function(routeParam, msg, routeContext, cb) { 25 | cb(null, routeContext[0].id); 26 | }; 27 | 28 | var client = Client.create({routeContext: routeContext, router: routeFunc, context: context}); 29 | 30 | client.start(function(err) { 31 | console.log('rpc client start ok.'); 32 | 33 | client.addProxies(records); 34 | client.addServers(servers); 35 | 36 | var m = new Buffer('hello'); 37 | // n = 'bbb'; 38 | var fs = require('fs') 39 | // m = fs.readFileSync('./skill.js').toString(); 40 | m = [ 'onReloadSkill', 41 | // [ m ], 42 | [ '210108' ], 43 | { type: 'push', userOptions: {}, isPush: true } ] ; 44 | // m = ['route', [m], {}, {}]; 45 | // m = require('./test'); 46 | // m = 3.14; 47 | // m = 'aaa'; 48 | // m = 100325; 49 | // m = {a: '111', b: 'bbb', c: 'ccc'}; 50 | // m = [1, '2', {a: 'bbb'}, 3.12, m, false]; 51 | // m = false; 52 | // m = '0'; 53 | 54 | client.proxies.user.test.service.echo.toServer('test-server-1', m, 'aaa', function(err, resp, data) { 55 | // client.proxies.user.test.service.echo(routeParam, m, 'aaa', function(err, resp, data) { 56 | if(err) { 57 | console.error(err.stack); 58 | } 59 | 60 | // setTimeout(function() { 61 | console.log(resp); 62 | console.log(data); 63 | // console.log(typeof resp) 64 | // console.log(resp.toString()) 65 | // }, 1000); 66 | }); 67 | }); 68 | 69 | process.on('uncaughtException', function(err) { 70 | console.error(err); 71 | }); -------------------------------------------------------------------------------- /sample/mqtt/client.js: -------------------------------------------------------------------------------- 1 | var net = require('net'), 2 | mqttCon = require('mqtt-connection'), 3 | stream = net.createConnection(1883, 'localhost'), 4 | conn = mqttCon(stream); 5 | var start = null; 6 | 7 | conn.connect({ 8 | clientId: "test" 9 | }, function() { 10 | console.log('client connected'); 11 | start = Date.now(); 12 | run(); 13 | }); 14 | 15 | conn.on('puback', function() { 16 | run(); 17 | }); 18 | 19 | conn.on('pingresp', function() { 20 | run(); 21 | }) 22 | 23 | var num_requests = 20000; 24 | var times = 0; 25 | 26 | function run() { 27 | if (times > num_requests) { 28 | return; 29 | } 30 | 31 | if (times == num_requests) { 32 | var now = Date.now(); 33 | var cost = now - start; 34 | console.log('run %d num requests cost: %d ops/sec', num_requests, cost, (num_requests / (cost / 1000)).toFixed(2)); 35 | times = 0; 36 | start = now; 37 | return run(); 38 | } 39 | 40 | times++; 41 | 42 | var payload = "hello"; 43 | // conn.pingreq(); 44 | conn.publish({ 45 | topic: "topic", 46 | payload: payload, 47 | qos: 1, 48 | messageId: times 49 | }, function() { 50 | // run(); 51 | }) 52 | } -------------------------------------------------------------------------------- /sample/mqtt/server.js: -------------------------------------------------------------------------------- 1 | var net = require('net'), 2 | mqttCon = require('mqtt-connection'), 3 | server = new net.Server(); 4 | var num = 300; 5 | var len = num * num; 6 | var i = 1; 7 | 8 | var start = 0; 9 | server.on('connection', function(stream) { 10 | var conn = mqttCon(stream); 11 | 12 | conn.on('connect', function() { 13 | console.log('connected'); 14 | }); 15 | 16 | conn.on('publish', function(packet) { 17 | // console.log(packet); 18 | conn.puback({ 19 | messageId: packet.messageId 20 | }) 21 | }); 22 | 23 | conn.on('pingreq', function() { 24 | conn.pingresp(); 25 | }); 26 | // conn is your MQTT connection! 27 | }); 28 | 29 | server.listen(1883) 30 | console.log('server started.'); -------------------------------------------------------------------------------- /sample/remote/test/service.js: -------------------------------------------------------------------------------- 1 | // remote service 2 | 3 | module.exports = function(context) { 4 | return { 5 | echo: function(msg, data, cb) { 6 | // setTimeout(function() { 7 | // console.log(msg); 8 | // console.log(data); 9 | cb(null, msg); 10 | // cb(null, msg, 'aaa' + Date.now()); 11 | // }, 15000); 12 | } 13 | }; 14 | }; -------------------------------------------------------------------------------- /sample/server.js: -------------------------------------------------------------------------------- 1 | var Server = require('..').server; 2 | 3 | // remote service path info list 4 | var paths = [ 5 | {namespace: 'user', path: __dirname + '/remote/test'} 6 | ]; 7 | 8 | var port = 3333; 9 | 10 | var server = Server.create({paths: paths, port: port}); 11 | server.start(); 12 | console.log('rpc server started.'); 13 | 14 | process.on('uncaughtException', function(err) { 15 | console.error(err); 16 | }); -------------------------------------------------------------------------------- /sample/test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEase/pomelo-rpc/1988f1b4cb674c1feaf4eee3cf4d0cff704418f3/sample/test.js -------------------------------------------------------------------------------- /sample/thrift/client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | var thrift = require('thrift'); 21 | // var ThriftTransports = require('thrift/transport'); 22 | // var ThriftProtocols = require('thrift/protocol'); 23 | var Calculator = require('./gen-nodejs/Calculator'); 24 | var ttypes = require('./gen-nodejs/tutorial_types'); 25 | 26 | 27 | transport = thrift.TBufferedTransport() 28 | protocol = thrift.TBinaryProtocol() 29 | 30 | var connection = thrift.createConnection("localhost", 9090, { 31 | transport : transport, 32 | protocol : protocol 33 | }); 34 | 35 | connection.on('error', function(err) { 36 | assert(false, err); 37 | }); 38 | 39 | // Create a Calculator client with the connection 40 | var client = thrift.createClient(Calculator, connection); 41 | 42 | var num_requests = 20000; 43 | var times = 0; 44 | var start = Date.now(); 45 | 46 | var rpcRequest = function(msg, cb) { 47 | client.ping(function(err, response) { 48 | cb() 49 | }); 50 | } 51 | 52 | function run() { 53 | if (times > num_requests) { 54 | return; 55 | } 56 | 57 | if (times == num_requests) { 58 | var now = Date.now(); 59 | var cost = now - start; 60 | console.log('run %d num requests cost: %d ops/sec', num_requests, cost, (num_requests / (cost / 1000)).toFixed(2)); 61 | times = 0; 62 | start = now; 63 | // return; 64 | return run(); 65 | } 66 | 67 | times++; 68 | rpcRequest({}, function() { 69 | run(); 70 | }); 71 | } 72 | 73 | run() 74 | // work = new ttypes.Work(); 75 | // work.op = ttypes.Operation.DIVIDE; 76 | // work.num1 = 1; 77 | // work.num2 = 0; 78 | 79 | // client.calculate(1, work, function(err, message) { 80 | // if (err) { 81 | // console.log("InvalidOperation " + err); 82 | // } else { 83 | // console.log('Whoa? You know how to divide by zero?'); 84 | // } 85 | // }); 86 | 87 | // work.op = ttypes.Operation.SUBTRACT; 88 | // work.num1 = 15; 89 | // work.num2 = 10; 90 | 91 | // client.calculate(1, work, function(err, message) { 92 | // console.log('15-10=' + message); 93 | 94 | // client.getStruct(1, function(err, message){ 95 | // console.log('Check log: ' + message.value); 96 | 97 | // //close the connection once we're done 98 | // connection.end(); 99 | // }); 100 | // }); 101 | -------------------------------------------------------------------------------- /sample/thrift/gen-nodejs/SharedService.js: -------------------------------------------------------------------------------- 1 | // 2 | // Autogenerated by Thrift Compiler (0.9.3) 3 | // 4 | // DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | // 6 | var thrift = require('thrift'); 7 | var Thrift = thrift.Thrift; 8 | var Q = thrift.Q; 9 | 10 | 11 | var ttypes = require('./shared_types'); 12 | //HELPER FUNCTIONS AND STRUCTURES 13 | 14 | SharedService_getStruct_args = function(args) { 15 | this.key = null; 16 | if (args) { 17 | if (args.key !== undefined && args.key !== null) { 18 | this.key = args.key; 19 | } 20 | } 21 | }; 22 | SharedService_getStruct_args.prototype = {}; 23 | SharedService_getStruct_args.prototype.read = function(input) { 24 | input.readStructBegin(); 25 | while (true) 26 | { 27 | var ret = input.readFieldBegin(); 28 | var fname = ret.fname; 29 | var ftype = ret.ftype; 30 | var fid = ret.fid; 31 | if (ftype == Thrift.Type.STOP) { 32 | break; 33 | } 34 | switch (fid) 35 | { 36 | case 1: 37 | if (ftype == Thrift.Type.I32) { 38 | this.key = input.readI32(); 39 | } else { 40 | input.skip(ftype); 41 | } 42 | break; 43 | case 0: 44 | input.skip(ftype); 45 | break; 46 | default: 47 | input.skip(ftype); 48 | } 49 | input.readFieldEnd(); 50 | } 51 | input.readStructEnd(); 52 | return; 53 | }; 54 | 55 | SharedService_getStruct_args.prototype.write = function(output) { 56 | output.writeStructBegin('SharedService_getStruct_args'); 57 | if (this.key !== null && this.key !== undefined) { 58 | output.writeFieldBegin('key', Thrift.Type.I32, 1); 59 | output.writeI32(this.key); 60 | output.writeFieldEnd(); 61 | } 62 | output.writeFieldStop(); 63 | output.writeStructEnd(); 64 | return; 65 | }; 66 | 67 | SharedService_getStruct_result = function(args) { 68 | this.success = null; 69 | if (args) { 70 | if (args.success !== undefined && args.success !== null) { 71 | this.success = new ttypes.SharedStruct(args.success); 72 | } 73 | } 74 | }; 75 | SharedService_getStruct_result.prototype = {}; 76 | SharedService_getStruct_result.prototype.read = function(input) { 77 | input.readStructBegin(); 78 | while (true) 79 | { 80 | var ret = input.readFieldBegin(); 81 | var fname = ret.fname; 82 | var ftype = ret.ftype; 83 | var fid = ret.fid; 84 | if (ftype == Thrift.Type.STOP) { 85 | break; 86 | } 87 | switch (fid) 88 | { 89 | case 0: 90 | if (ftype == Thrift.Type.STRUCT) { 91 | this.success = new ttypes.SharedStruct(); 92 | this.success.read(input); 93 | } else { 94 | input.skip(ftype); 95 | } 96 | break; 97 | case 0: 98 | input.skip(ftype); 99 | break; 100 | default: 101 | input.skip(ftype); 102 | } 103 | input.readFieldEnd(); 104 | } 105 | input.readStructEnd(); 106 | return; 107 | }; 108 | 109 | SharedService_getStruct_result.prototype.write = function(output) { 110 | output.writeStructBegin('SharedService_getStruct_result'); 111 | if (this.success !== null && this.success !== undefined) { 112 | output.writeFieldBegin('success', Thrift.Type.STRUCT, 0); 113 | this.success.write(output); 114 | output.writeFieldEnd(); 115 | } 116 | output.writeFieldStop(); 117 | output.writeStructEnd(); 118 | return; 119 | }; 120 | 121 | SharedServiceClient = exports.Client = function(output, pClass) { 122 | this.output = output; 123 | this.pClass = pClass; 124 | this._seqid = 0; 125 | this._reqs = {}; 126 | }; 127 | SharedServiceClient.prototype = {}; 128 | SharedServiceClient.prototype.seqid = function() { return this._seqid; } 129 | SharedServiceClient.prototype.new_seqid = function() { return this._seqid += 1; } 130 | SharedServiceClient.prototype.getStruct = function(key, callback) { 131 | this._seqid = this.new_seqid(); 132 | if (callback === undefined) { 133 | var _defer = Q.defer(); 134 | this._reqs[this.seqid()] = function(error, result) { 135 | if (error) { 136 | _defer.reject(error); 137 | } else { 138 | _defer.resolve(result); 139 | } 140 | }; 141 | this.send_getStruct(key); 142 | return _defer.promise; 143 | } else { 144 | this._reqs[this.seqid()] = callback; 145 | this.send_getStruct(key); 146 | } 147 | }; 148 | 149 | SharedServiceClient.prototype.send_getStruct = function(key) { 150 | var output = new this.pClass(this.output); 151 | output.writeMessageBegin('getStruct', Thrift.MessageType.CALL, this.seqid()); 152 | var args = new SharedService_getStruct_args(); 153 | args.key = key; 154 | args.write(output); 155 | output.writeMessageEnd(); 156 | return this.output.flush(); 157 | }; 158 | 159 | SharedServiceClient.prototype.recv_getStruct = function(input,mtype,rseqid) { 160 | var callback = this._reqs[rseqid] || function() {}; 161 | delete this._reqs[rseqid]; 162 | if (mtype == Thrift.MessageType.EXCEPTION) { 163 | var x = new Thrift.TApplicationException(); 164 | x.read(input); 165 | input.readMessageEnd(); 166 | return callback(x); 167 | } 168 | var result = new SharedService_getStruct_result(); 169 | result.read(input); 170 | input.readMessageEnd(); 171 | 172 | if (null !== result.success) { 173 | return callback(null, result.success); 174 | } 175 | return callback('getStruct failed: unknown result'); 176 | }; 177 | SharedServiceProcessor = exports.Processor = function(handler) { 178 | this._handler = handler 179 | } 180 | SharedServiceProcessor.prototype.process = function(input, output) { 181 | var r = input.readMessageBegin(); 182 | if (this['process_' + r.fname]) { 183 | return this['process_' + r.fname].call(this, r.rseqid, input, output); 184 | } else { 185 | input.skip(Thrift.Type.STRUCT); 186 | input.readMessageEnd(); 187 | var x = new Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN_METHOD, 'Unknown function ' + r.fname); 188 | output.writeMessageBegin(r.fname, Thrift.MessageType.EXCEPTION, r.rseqid); 189 | x.write(output); 190 | output.writeMessageEnd(); 191 | output.flush(); 192 | } 193 | } 194 | 195 | SharedServiceProcessor.prototype.process_getStruct = function(seqid, input, output) { 196 | var args = new SharedService_getStruct_args(); 197 | args.read(input); 198 | input.readMessageEnd(); 199 | if (this._handler.getStruct.length === 1) { 200 | Q.fcall(this._handler.getStruct, args.key) 201 | .then(function(result) { 202 | var result = new SharedService_getStruct_result({success: result}); 203 | output.writeMessageBegin("getStruct", Thrift.MessageType.REPLY, seqid); 204 | result.write(output); 205 | output.writeMessageEnd(); 206 | output.flush(); 207 | }, function (err) { 208 | var result = new Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN, err.message); 209 | output.writeMessageBegin("getStruct", Thrift.MessageType.EXCEPTION, seqid); 210 | result.write(output); 211 | output.writeMessageEnd(); 212 | output.flush(); 213 | }); 214 | } else { 215 | this._handler.getStruct(args.key, function (err, result) { 216 | if (err == null) { 217 | var result = new SharedService_getStruct_result((err != null ? err : {success: result})); 218 | output.writeMessageBegin("getStruct", Thrift.MessageType.REPLY, seqid); 219 | } else { 220 | var result = new Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN, err.message); 221 | output.writeMessageBegin("getStruct", Thrift.MessageType.EXCEPTION, seqid); 222 | } 223 | result.write(output); 224 | output.writeMessageEnd(); 225 | output.flush(); 226 | }); 227 | } 228 | } 229 | 230 | -------------------------------------------------------------------------------- /sample/thrift/gen-nodejs/shared_types.js: -------------------------------------------------------------------------------- 1 | // 2 | // Autogenerated by Thrift Compiler (0.9.3) 3 | // 4 | // DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | // 6 | var thrift = require('thrift'); 7 | var Thrift = thrift.Thrift; 8 | var Q = thrift.Q; 9 | 10 | 11 | var ttypes = module.exports = {}; 12 | SharedStruct = module.exports.SharedStruct = function(args) { 13 | this.key = null; 14 | this.value = null; 15 | if (args) { 16 | if (args.key !== undefined && args.key !== null) { 17 | this.key = args.key; 18 | } 19 | if (args.value !== undefined && args.value !== null) { 20 | this.value = args.value; 21 | } 22 | } 23 | }; 24 | SharedStruct.prototype = {}; 25 | SharedStruct.prototype.read = function(input) { 26 | input.readStructBegin(); 27 | while (true) 28 | { 29 | var ret = input.readFieldBegin(); 30 | var fname = ret.fname; 31 | var ftype = ret.ftype; 32 | var fid = ret.fid; 33 | if (ftype == Thrift.Type.STOP) { 34 | break; 35 | } 36 | switch (fid) 37 | { 38 | case 1: 39 | if (ftype == Thrift.Type.I32) { 40 | this.key = input.readI32(); 41 | } else { 42 | input.skip(ftype); 43 | } 44 | break; 45 | case 2: 46 | if (ftype == Thrift.Type.STRING) { 47 | this.value = input.readString(); 48 | } else { 49 | input.skip(ftype); 50 | } 51 | break; 52 | default: 53 | input.skip(ftype); 54 | } 55 | input.readFieldEnd(); 56 | } 57 | input.readStructEnd(); 58 | return; 59 | }; 60 | 61 | SharedStruct.prototype.write = function(output) { 62 | output.writeStructBegin('SharedStruct'); 63 | if (this.key !== null && this.key !== undefined) { 64 | output.writeFieldBegin('key', Thrift.Type.I32, 1); 65 | output.writeI32(this.key); 66 | output.writeFieldEnd(); 67 | } 68 | if (this.value !== null && this.value !== undefined) { 69 | output.writeFieldBegin('value', Thrift.Type.STRING, 2); 70 | output.writeString(this.value); 71 | output.writeFieldEnd(); 72 | } 73 | output.writeFieldStop(); 74 | output.writeStructEnd(); 75 | return; 76 | }; 77 | 78 | -------------------------------------------------------------------------------- /sample/thrift/gen-nodejs/tutorial_types.js: -------------------------------------------------------------------------------- 1 | // 2 | // Autogenerated by Thrift Compiler (0.9.3) 3 | // 4 | // DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | // 6 | var thrift = require('thrift'); 7 | var Thrift = thrift.Thrift; 8 | var Q = thrift.Q; 9 | 10 | var shared_ttypes = require('./shared_types') 11 | 12 | 13 | var ttypes = module.exports = {}; 14 | ttypes.Operation = { 15 | 'ADD' : 1, 16 | 'SUBTRACT' : 2, 17 | 'MULTIPLY' : 3, 18 | 'DIVIDE' : 4 19 | }; 20 | Work = module.exports.Work = function(args) { 21 | this.num1 = 0; 22 | this.num2 = null; 23 | this.op = null; 24 | this.comment = null; 25 | if (args) { 26 | if (args.num1 !== undefined && args.num1 !== null) { 27 | this.num1 = args.num1; 28 | } 29 | if (args.num2 !== undefined && args.num2 !== null) { 30 | this.num2 = args.num2; 31 | } 32 | if (args.op !== undefined && args.op !== null) { 33 | this.op = args.op; 34 | } 35 | if (args.comment !== undefined && args.comment !== null) { 36 | this.comment = args.comment; 37 | } 38 | } 39 | }; 40 | Work.prototype = {}; 41 | Work.prototype.read = function(input) { 42 | input.readStructBegin(); 43 | while (true) 44 | { 45 | var ret = input.readFieldBegin(); 46 | var fname = ret.fname; 47 | var ftype = ret.ftype; 48 | var fid = ret.fid; 49 | if (ftype == Thrift.Type.STOP) { 50 | break; 51 | } 52 | switch (fid) 53 | { 54 | case 1: 55 | if (ftype == Thrift.Type.I32) { 56 | this.num1 = input.readI32(); 57 | } else { 58 | input.skip(ftype); 59 | } 60 | break; 61 | case 2: 62 | if (ftype == Thrift.Type.I32) { 63 | this.num2 = input.readI32(); 64 | } else { 65 | input.skip(ftype); 66 | } 67 | break; 68 | case 3: 69 | if (ftype == Thrift.Type.I32) { 70 | this.op = input.readI32(); 71 | } else { 72 | input.skip(ftype); 73 | } 74 | break; 75 | case 4: 76 | if (ftype == Thrift.Type.STRING) { 77 | this.comment = input.readString(); 78 | } else { 79 | input.skip(ftype); 80 | } 81 | break; 82 | default: 83 | input.skip(ftype); 84 | } 85 | input.readFieldEnd(); 86 | } 87 | input.readStructEnd(); 88 | return; 89 | }; 90 | 91 | Work.prototype.write = function(output) { 92 | output.writeStructBegin('Work'); 93 | if (this.num1 !== null && this.num1 !== undefined) { 94 | output.writeFieldBegin('num1', Thrift.Type.I32, 1); 95 | output.writeI32(this.num1); 96 | output.writeFieldEnd(); 97 | } 98 | if (this.num2 !== null && this.num2 !== undefined) { 99 | output.writeFieldBegin('num2', Thrift.Type.I32, 2); 100 | output.writeI32(this.num2); 101 | output.writeFieldEnd(); 102 | } 103 | if (this.op !== null && this.op !== undefined) { 104 | output.writeFieldBegin('op', Thrift.Type.I32, 3); 105 | output.writeI32(this.op); 106 | output.writeFieldEnd(); 107 | } 108 | if (this.comment !== null && this.comment !== undefined) { 109 | output.writeFieldBegin('comment', Thrift.Type.STRING, 4); 110 | output.writeString(this.comment); 111 | output.writeFieldEnd(); 112 | } 113 | output.writeFieldStop(); 114 | output.writeStructEnd(); 115 | return; 116 | }; 117 | 118 | InvalidOperation = module.exports.InvalidOperation = function(args) { 119 | Thrift.TException.call(this, "InvalidOperation") 120 | this.name = "InvalidOperation" 121 | this.whatOp = null; 122 | this.why = null; 123 | if (args) { 124 | if (args.whatOp !== undefined && args.whatOp !== null) { 125 | this.whatOp = args.whatOp; 126 | } 127 | if (args.why !== undefined && args.why !== null) { 128 | this.why = args.why; 129 | } 130 | } 131 | }; 132 | Thrift.inherits(InvalidOperation, Thrift.TException); 133 | InvalidOperation.prototype.name = 'InvalidOperation'; 134 | InvalidOperation.prototype.read = function(input) { 135 | input.readStructBegin(); 136 | while (true) 137 | { 138 | var ret = input.readFieldBegin(); 139 | var fname = ret.fname; 140 | var ftype = ret.ftype; 141 | var fid = ret.fid; 142 | if (ftype == Thrift.Type.STOP) { 143 | break; 144 | } 145 | switch (fid) 146 | { 147 | case 1: 148 | if (ftype == Thrift.Type.I32) { 149 | this.whatOp = input.readI32(); 150 | } else { 151 | input.skip(ftype); 152 | } 153 | break; 154 | case 2: 155 | if (ftype == Thrift.Type.STRING) { 156 | this.why = input.readString(); 157 | } else { 158 | input.skip(ftype); 159 | } 160 | break; 161 | default: 162 | input.skip(ftype); 163 | } 164 | input.readFieldEnd(); 165 | } 166 | input.readStructEnd(); 167 | return; 168 | }; 169 | 170 | InvalidOperation.prototype.write = function(output) { 171 | output.writeStructBegin('InvalidOperation'); 172 | if (this.whatOp !== null && this.whatOp !== undefined) { 173 | output.writeFieldBegin('whatOp', Thrift.Type.I32, 1); 174 | output.writeI32(this.whatOp); 175 | output.writeFieldEnd(); 176 | } 177 | if (this.why !== null && this.why !== undefined) { 178 | output.writeFieldBegin('why', Thrift.Type.STRING, 2); 179 | output.writeString(this.why); 180 | output.writeFieldEnd(); 181 | } 182 | output.writeFieldStop(); 183 | output.writeStructEnd(); 184 | return; 185 | }; 186 | 187 | ttypes.INT32CONSTANT = 9853; 188 | ttypes.MAPCONSTANT = { 189 | 'hello' : 'world', 190 | 'goodnight' : 'moon' 191 | }; 192 | -------------------------------------------------------------------------------- /sample/thrift/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | var thrift = require("thrift"); 21 | var Calculator = require("./gen-nodejs/Calculator"); 22 | var ttypes = require("./gen-nodejs/tutorial_types"); 23 | var SharedStruct = require("./gen-nodejs/shared_types").SharedStruct; 24 | 25 | var data = {}; 26 | 27 | var server = thrift.createServer(Calculator, { 28 | ping: function(result) { 29 | // console.log("ping()"); 30 | result(); 31 | }, 32 | 33 | add: function(n1, n2, result) { 34 | console.log("add(", n1, ",", n2, ")"); 35 | result(null, n1 + n2); 36 | }, 37 | 38 | calculate: function(logid, work, result) { 39 | console.log("calculate(", logid, ",", work, ")"); 40 | 41 | var val = 0; 42 | if (work.op == ttypes.Operation.ADD) { 43 | val = work.num1 + work.num2; 44 | } else if (work.op === ttypes.Operation.SUBTRACT) { 45 | val = work.num1 - work.num2; 46 | } else if (work.op === ttypes.Operation.MULTIPLY) { 47 | val = work.num1 * work.num2; 48 | } else if (work.op === ttypes.Operation.DIVIDE) { 49 | if (work.num2 === 0) { 50 | var x = new ttypes.InvalidOperation(); 51 | x.whatOp = work.op; 52 | x.why = 'Cannot divide by 0'; 53 | result(x); 54 | return; 55 | } 56 | val = work.num1 / work.num2; 57 | } else { 58 | var x = new ttypes.InvalidOperation(); 59 | x.whatOp = work.op; 60 | x.why = 'Invalid operation'; 61 | result(x); 62 | return; 63 | } 64 | 65 | var entry = new SharedStruct(); 66 | entry.key = logid; 67 | entry.value = ""+val; 68 | data[logid] = entry; 69 | 70 | result(null, val); 71 | }, 72 | 73 | getStruct: function(key, result) { 74 | console.log("getStruct(", key, ")"); 75 | result(null, data[key]); 76 | }, 77 | 78 | zip: function() { 79 | console.log("zip()"); 80 | result(null); 81 | } 82 | 83 | }); 84 | 85 | server.listen(9090); 86 | -------------------------------------------------------------------------------- /sample/ws/client.js: -------------------------------------------------------------------------------- 1 | var WebSocket = require('ws'); 2 | var ws = new WebSocket('ws://localhost:3331'); 3 | 4 | ws.on('open', function open() { 5 | start = Date.now(); 6 | run(); 7 | }); 8 | 9 | ws.on('message', function(data, flags) { 10 | // flags.binary will be set if a binary data is received. 11 | // flags.masked will be set if the data was masked. 12 | run(); 13 | }); 14 | 15 | var num_requests = 20000; 16 | var start = null; 17 | var times = 0; 18 | 19 | function run() { 20 | if (times > num_requests) { 21 | return; 22 | } 23 | 24 | if (times == num_requests) { 25 | var now = Date.now(); 26 | var cost = now - start; 27 | console.log('run %d num requests cost: %d ops/sec', num_requests, cost, (num_requests / (cost / 1000)).toFixed(2)); 28 | times = 0; 29 | start = now; 30 | return run(); 31 | } 32 | 33 | times++; 34 | 35 | var payload = "hello"; 36 | ws.send(payload); 37 | } -------------------------------------------------------------------------------- /sample/ws/server.js: -------------------------------------------------------------------------------- 1 | var WebSocketServer = require('ws').Server, 2 | wss = new WebSocketServer({ 3 | port: 3331 4 | }); 5 | 6 | wss.on('connection', function connection(ws) { 7 | ws.on('message', function incoming(message) { 8 | ws.send(message); 9 | }); 10 | 11 | }); -------------------------------------------------------------------------------- /sample/zlib/bench.js: -------------------------------------------------------------------------------- 1 | var zlibjs = require('browserify-zlib'); 2 | 3 | var num = 20000; 4 | var start = null; 5 | 6 | var message = { 7 | key: 'hello' 8 | } 9 | 10 | start = Date.now(); 11 | 12 | function run() { 13 | for (var i = 0; i < num; i++) { 14 | zlibjs.gunzipSync(zlibjs.gzipSync(JSON.stringify(message))); 15 | } 16 | 17 | var now = Date.now(); 18 | var cost = now - start; 19 | console.log('run %d num requests cost: %d ops/sec', num, cost, (num / (cost / 1000)).toFixed(2)); 20 | run(); 21 | } 22 | 23 | run(); -------------------------------------------------------------------------------- /sample/zmq/client.js: -------------------------------------------------------------------------------- 1 | var zmq = require('zmq'); 2 | var socket = zmq.socket('dealer'); 3 | socket.identity = 'test'; 4 | socket.connect('tcp://localhost:3331'); 5 | 6 | run(); 7 | 8 | socket.on('message', function() { 9 | run(); 10 | }) 11 | 12 | var num_requests = 20000; 13 | var start = Date.now(); 14 | var times = 0; 15 | 16 | function run() { 17 | if (times > num_requests) { 18 | return; 19 | } 20 | 21 | if (times == num_requests) { 22 | var now = Date.now(); 23 | var cost = now - start; 24 | console.log('run %d num requests cost: %d ops/sec', num_requests, cost, (num_requests / (cost / 1000)).toFixed(2)); 25 | times = 0; 26 | start = now; 27 | return run(); 28 | } 29 | 30 | times++; 31 | 32 | var payload = "hello"; 33 | socket.send(payload); 34 | } -------------------------------------------------------------------------------- /sample/zmq/server.js: -------------------------------------------------------------------------------- 1 | var zmq = require('zmq'); 2 | var socket = zmq.socket('router'); 3 | 4 | socket.bind('tcp://*:3331', function(err) { 5 | socket.on('message', function(clientId, pkg) { 6 | console.log(clientId); 7 | console.log(pkg) 8 | socket.send(pkg); 9 | }); 10 | }); -------------------------------------------------------------------------------- /test/mock-remote/area/addOneRemote.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mock remote service 3 | */ 4 | module.exports = function(app) { 5 | return { 6 | doService: function(value, cb) { 7 | cb(null, value + 1); 8 | }, 9 | 10 | doAddTwo: function(value, cb) { 11 | cb(null, value + 2); 12 | } 13 | }; 14 | }; -------------------------------------------------------------------------------- /test/mock-remote/area/addThreeRemote.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mock remote service 3 | */ 4 | module.exports = function(app) { 5 | return { 6 | doService: function(value, cb) { 7 | cb(null, value + 3); 8 | } 9 | }; 10 | }; -------------------------------------------------------------------------------- /test/mock-remote/area/whoAmIRemote.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mock remote service 3 | */ 4 | module.exports = function(app) { 5 | return { 6 | doService: function(cb) { 7 | cb(null, app.id); 8 | } 9 | }; 10 | }; -------------------------------------------------------------------------------- /test/mock-remote/connector/addTwoService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mock remote service 3 | */ 4 | module.exports = function(app) { 5 | return { 6 | doService: function(value, cb) { 7 | cb(null, value + 2); 8 | }, 9 | name: 'addTwoRemote' 10 | }; 11 | }; -------------------------------------------------------------------------------- /test/mock-remote/connector/whoAmIRemote.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mock remote service 3 | */ 4 | module.exports = function(app) { 5 | return { 6 | doService: function(cb) { 7 | cb(null, app.id); 8 | } 9 | }; 10 | }; -------------------------------------------------------------------------------- /test/rpc-client/client.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var Server = require('../../').server; 3 | var Client = require('../../').client; 4 | 5 | var WAIT_TIME = 100; 6 | 7 | // proxy records 8 | var records = [ 9 | {namespace: 'user', serverType: 'area', path: __dirname + '../../mock-remote/area'}, 10 | {namespace: 'sys', serverType: 'connector', path: __dirname + '../../mock-remote/connector'} 11 | ]; 12 | 13 | // server info list 14 | var serverList = [ 15 | {id: 'area-server-1', type: "area", host: '127.0.0.1', port: 3333}, 16 | {id: 'connector-server-1', type: "connector", host: '127.0.0.1', port: 4444}, 17 | {id: 'connector-server-2', type: "connector", host: '127.0.0.1', port: 5555}, 18 | ]; 19 | 20 | // rpc description message 21 | var msg = { 22 | namespace: 'user', 23 | serverType: 'area', 24 | service: 'whoAmIRemote', 25 | method: 'doService', 26 | args: [] 27 | }; 28 | 29 | describe('client', function() { 30 | var gateways = []; 31 | 32 | before(function(done) { 33 | gateways = []; 34 | //start remote servers 35 | var item, opts, gateway; 36 | for(var i=0, l=serverList.length; i