├── .gitignore ├── .jshintrc ├── .travis.yml ├── AUTHORS ├── History.md ├── LICENSE ├── README.md ├── bin └── pomelo ├── coverage └── blanket.js ├── gruntfile.js ├── index.js ├── lib ├── application.js ├── common │ ├── manager │ │ ├── appManager.js │ │ └── taskManager.js │ ├── remote │ │ ├── backend │ │ │ └── msgRemote.js │ │ └── frontend │ │ │ ├── channelRemote.js │ │ │ └── sessionRemote.js │ └── service │ │ ├── backendSessionService.js │ │ ├── channelService.js │ │ ├── connectionService.js │ │ ├── filterService.js │ │ ├── handlerService.js │ │ └── sessionService.js ├── components │ ├── backendSession.js │ ├── channel.js │ ├── connection.js │ ├── connector.js │ ├── dictionary.js │ ├── master.js │ ├── monitor.js │ ├── protobuf.js │ ├── proxy.js │ ├── pushScheduler.js │ ├── remote.js │ ├── server.js │ └── session.js ├── connectors │ ├── commands │ │ ├── handshake.js │ │ ├── heartbeat.js │ │ └── kick.js │ ├── common │ │ ├── coder.js │ │ └── handler.js │ ├── hybrid │ │ ├── switcher.js │ │ ├── tcpprocessor.js │ │ ├── tcpsocket.js │ │ └── wsprocessor.js │ ├── hybridconnector.js │ ├── hybridsocket.js │ ├── mqtt │ │ ├── generate.js │ │ ├── mqttadaptor.js │ │ └── protocol.js │ ├── mqttconnector.js │ ├── mqttsocket.js │ ├── sioconnector.js │ ├── siosocket.js │ ├── udpconnector.js │ └── udpsocket.js ├── filters │ ├── handler │ │ ├── serial.js │ │ ├── time.js │ │ ├── timeout.js │ │ └── toobusy.js │ └── rpc │ │ ├── rpcLog.js │ │ └── toobusy.js ├── index.js ├── master │ ├── master.js │ ├── starter.js │ └── watchdog.js ├── modules │ ├── console.js │ ├── masterwatcher.js │ └── monitorwatcher.js ├── monitor │ └── monitor.js ├── pomelo.js ├── pushSchedulers │ ├── buffer.js │ └── direct.js ├── server │ └── server.js └── util │ ├── appUtil.js │ ├── constants.js │ ├── countDownLatch.js │ ├── events.js │ ├── log.js │ ├── moduleUtil.js │ ├── pathUtil.js │ └── utils.js ├── package.json ├── template ├── game-server │ ├── app.js │ ├── app.js.mqtt │ ├── app.js.sio │ ├── app.js.sio.wss │ ├── app.js.udp │ ├── app.js.wss │ ├── app │ │ └── servers │ │ │ └── connector │ │ │ └── handler │ │ │ └── entryHandler.js │ ├── config │ │ ├── adminServer.json │ │ ├── adminUser.json │ │ ├── clientProtos.json │ │ ├── dictionary.json │ │ ├── log4js.json │ │ ├── master.json │ │ ├── serverProtos.json │ │ └── servers.json │ └── package.json ├── npm-install.bat ├── npm-install.sh ├── shared │ ├── server.crt │ └── server.key └── web-server │ ├── app.js │ ├── app.js.https │ ├── bin │ ├── component.bat │ └── component.sh │ ├── package.json │ └── public │ ├── css │ └── base.css │ ├── image │ ├── logo.png │ └── sp.png │ ├── index.html │ ├── index.html.sio │ └── js │ └── lib │ ├── build │ ├── build.js │ └── build.js.wss │ ├── component.json │ ├── local │ └── boot │ │ ├── component.json │ │ └── index.js │ ├── pomeloclient.js │ ├── pomeloclient.js.wss │ └── socket.io.js └── test ├── application.js ├── config ├── log4js.json ├── master.json └── servers.json ├── filters ├── handler │ ├── serial.js │ ├── time.js │ ├── timeout.js │ └── toobusy.js └── rpc │ ├── rpcLog.js │ └── toobusy.js ├── logs └── tmp ├── manager ├── mockChannelManager.js └── taskManager.js ├── mock-base ├── .gitignore └── app │ ├── .gitignore │ └── servers │ ├── .file-start-with-dot │ ├── .folder-start-with-dot │ └── .gitignore │ ├── .gitignore │ ├── area │ └── .gitignore │ ├── connector │ ├── .gitignore │ ├── handler │ │ └── .gitignore │ └── remote │ │ └── .gitignore │ └── other-file ├── mock-plugin ├── components │ └── mockPlugin.js └── events │ └── mockEvent.js ├── modules └── console.js ├── pomelo.js ├── remote └── channelRemote.js ├── service ├── channel.js ├── channelService.js ├── connectionService.js ├── filterService.js ├── handlerService.js └── sessionService.js └── util ├── countDownLatch.js ├── pathUtil.js └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | */node-log.log 3 | logs/*.log 4 | *.log 5 | !.gitignore 6 | node_modules/* 7 | node_modules_back/* 8 | .project 9 | .settings/ 10 | **/*.svn 11 | *.svn 12 | *.swp 13 | *.sublime-project 14 | *.sublime-workspace 15 | lib/doc/ 16 | lib-cov/ 17 | coverage.html 18 | .DS_Store 19 | .idea/* -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "camelcase": true, 4 | "eqeqeq": true, 5 | "undef": true, 6 | "unused": true, 7 | "curly": true, 8 | "newcap": true, 9 | "nonew": true, 10 | "trailing": true, 11 | "smarttabs": true, 12 | "noarg": true 13 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10.15.0" 4 | before_script: 5 | - npm install -g grunt-cli 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | * Charlie Crane 2 | * Chang chang 3 | * piaohai 4 | * py8765 5 | * Demon 6 | * numbcoder 7 | * halfblood 8 | * fantasyni 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012-2014 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Pomelo -- a fast, scalable game server framework for node.js 2 | 3 | Pomelo is a fast, scalable game server framework for [node.js](http://nodejs.org). 4 | It provides the basic development framework and many related components, including libraries and tools. 5 | Pomelo is also suitable for real-time web applications; its distributed architecture makes pomelo scale better than other real-time web frameworks. 6 | 7 | [![Build Status](https://travis-ci.org/NetEase/pomelo.svg?branch=master)](https://travis-ci.org/NetEase/pomelo) 8 | 9 | * Homepage: 10 | * Mailing list: 11 | * Documentation: 12 | * Wiki: 13 | * Issues: 14 | * Tags: game, nodejs 15 | 16 | 17 | ## Features 18 | 19 | ### Complete support of game server and realtime application server architecture 20 | 21 | * Multiple-player game: mobile, social, web, MMO rpg(middle size) 22 | * Realtime application: chat, message push, etc. 23 | 24 | ### Fast, scalable 25 | 26 | * Distributed (multi-process) architecture, can be easily scale up 27 | * Flexible server extension 28 | * Full performance optimization and test 29 | 30 | ### Easy 31 | 32 | * Simple API: request, response, broadcast, etc. 33 | * Lightweight: high development efficiency based on node.js 34 | * Convention over configuration: almost zero config 35 | 36 | ### Powerful 37 | 38 | * Many clients support, including javascript, flash, android, iOS, cocos2d-x, C 39 | * Many libraries and tools, including command line tool, admin tool, performance test tool, AI, path finding etc. 40 | * Good reference materials: full docs, many examples and [an open-source MMO RPG demo](https://github.com/NetEase/pomelo/wiki/Introduction-to--Lord-of-Pomelo) 41 | 42 | ### Extensible 43 | 44 | * Support plugin architecture, easy to add new features through plugins. We also provide many plugins like online status, master high availability. 45 | * Custom features, users can define their own network protocol, custom components very easy. 46 | 47 | ## Why should I use pomelo? 48 | Fast, scalable, real-time game server development is not an easy job, and a good container or framework can reduce its complexity. 49 | Unfortunately, unlike web, finding a game server framework solution is difficult, especially an open source solution. Pomelo fills this gap, providing a full solution for building game server frameworks. 50 | Pomelo has the following advantages: 51 | * The architecture is scalable. It uses a multi-process, single thread runtime architecture, which has been proven in the industry and is especially suited to the node.js thread model. 52 | * Easy to use, the development model is quite similar to web, using convention over configuration, with almost zero config. The [API](http://pomelo.netease.com/api.html) is also easy to use. 53 | * The framework is extensible. Based on the node.js micro module principle, the core of pomelo is small. All of the components, libraries and tools are individual npm modules, and anyone can create their own module to extend the framework. 54 | * The reference materials and documentation are quite complete. In addition to the documentation, we also provide [an open-source MMO RPG demo](https://github.com/NetEase/pomelo/wiki/Introduction-to--Lord-of-Pomelo) (HTML5 client), which is a far better reference material than any book. 55 | 56 | ## How can I develop with pomelo? 57 | With the following references, you can quickly familiarize yourself with the pomelo development process: 58 | * [Pomelo documents](https://github.com/NetEase/pomelo/wiki) 59 | * [Getting started](https://github.com/NetEase/pomelo/wiki/Welcome-to-Pomelo) 60 | * [Tutorial](https://github.com/NetEase/pomelo/wiki/Preface) 61 | 62 | 63 | ## Contributors 64 | * NetEase, Inc. (@NetEase) 65 | * Peter Johnson(@missinglink) 66 | * Aaron Yoshitake 67 | * @D-Deo 68 | * Eduard Gotwig 69 | * Eric Muyser(@stokegames) 70 | * @GeforceLee 71 | * Harold Jiang(@jzsues) 72 | * @ETiV 73 | * [kaisatec](https://github.com/kaisatec) 74 | * [roytan883](https://github.com/roytan883) 75 | * [wuxian](https://github.com/wuxian) 76 | * [zxc122333](https://github.com/zxc122333) 77 | * [newebug](https://github.com/newebug) 78 | * [jiangzhuo](https://github.com/jiangzhuo) 79 | * [youxiachai](https://github.com/youxiachai) 80 | * [qiankanglai](https://github.com/qiankanglai) 81 | * [xieren58](https://github.com/xieren58) 82 | * [prim](https://github.com/prim) 83 | * [Akaleth](https://github.com/Akaleth) 84 | * [pipi32167](https://github.com/pipi32167) 85 | * [ljhsai](https://github.com/ljhsai) 86 | * [zhanghaojie](https://github.com/zhanghaojie) 87 | * [airandfingers](https://github.com/airandfingers) 88 | 89 | ## License 90 | 91 | (The MIT License) 92 | 93 | Copyright (c) 2012-2017 NetEase, Inc. and other contributors 94 | 95 | Permission is hereby granted, free of charge, to any person obtaining 96 | a copy of this software and associated documentation files (the 97 | 'Software'), to deal in the Software without restriction, including 98 | without limitation the rights to use, copy, modify, merge, publish, 99 | distribute, sublicense, and/or sell copies of the Software, and to 100 | permit persons to whom the Software is furnished to do so, subject to 101 | the following conditions: 102 | 103 | The above copyright notice and this permission notice shall be 104 | included in all copies or substantial portions of the Software. 105 | 106 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 107 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 108 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 109 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 110 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 111 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 112 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 113 | 114 | -------------------------------------------------------------------------------- /coverage/blanket.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var srcDir = path.join(__dirname, '..', 'lib'); 3 | 4 | require('blanket')({ 5 | pattern: srcDir 6 | }); -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | 5 | grunt.loadNpmTasks('grunt-mocha-test'); 6 | grunt.loadNpmTasks('grunt-contrib-clean'); 7 | grunt.loadNpmTasks('grunt-contrib-jshint'); 8 | 9 | var src = ['test/manager/taskManager.js', 'test/filters/*.js', 10 | 'test/remote/*.js', 'test/service/*.js', 'test/modules/*.js', 'test/util/*.js', 'test/*.js']; 11 | 12 | // Project configuration. 13 | grunt.initConfig({ 14 | mochaTest: { 15 | test: { 16 | options: { 17 | reporter: 'spec', 18 | timeout: 5000, 19 | require: 'coverage/blanket' 20 | }, 21 | src: src 22 | }, 23 | coverage: { 24 | options: { 25 | reporter: 'html-cov', 26 | quiet: true, 27 | captureFile: 'coverage.html' 28 | }, 29 | src: src 30 | } 31 | }, 32 | clean: { 33 | "coverage.html" : { 34 | src: ['coverage.html'] 35 | } 36 | }, 37 | jshint: { 38 | all: ['lib/*'] 39 | } 40 | }); 41 | 42 | // Default task. 43 | grunt.registerTask('default', ['clean', 'mochaTest', 'jshint']); 44 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/pomelo'); -------------------------------------------------------------------------------- /lib/common/manager/appManager.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | var utils = require('../../util/utils'); 3 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 4 | var transactionLogger = require('pomelo-logger').getLogger('transaction-log', __filename); 5 | var transactionErrorLogger = require('pomelo-logger').getLogger('transaction-error-log', __filename); 6 | 7 | var manager = module.exports; 8 | 9 | manager.transaction = function(name, conditions, handlers, retry) { 10 | if(!retry) { 11 | retry = 1; 12 | } 13 | if(typeof name !== 'string') { 14 | logger.error('transaction name is error format, name: %s.', name); 15 | return; 16 | } 17 | if(typeof conditions !== 'object' || typeof handlers !== 'object') { 18 | logger.error('transaction conditions parameter is error format, conditions: %j, handlers: %j.', conditions, handlers); 19 | return; 20 | } 21 | 22 | var cmethods=[] ,dmethods=[], cnames=[], dnames=[]; 23 | for(var key in conditions) { 24 | if(typeof key !== 'string' || typeof conditions[key] !== 'function') { 25 | logger.error('transaction conditions parameter is error format, condition name: %s, condition function: %j.', key, conditions[key]); 26 | return; 27 | } 28 | cnames.push(key); 29 | cmethods.push(conditions[key]); 30 | } 31 | 32 | var i = 0; 33 | // execute conditions 34 | async.forEachSeries(cmethods, function(method, cb) { 35 | method(cb); 36 | transactionLogger.info('[%s]:[%s] condition is executed.', name, cnames[i]); 37 | i++; 38 | }, function(err) { 39 | if(err) { 40 | process.nextTick(function() { 41 | transactionLogger.error('[%s]:[%s] condition is executed with err: %j.', name, cnames[--i], err.stack); 42 | var log = { 43 | name: name, 44 | method: cnames[i], 45 | time: Date.now(), 46 | type: 'condition', 47 | description: err.stack 48 | }; 49 | transactionErrorLogger.error(JSON.stringify(log)); 50 | }); 51 | return; 52 | } else { 53 | // execute handlers 54 | process.nextTick(function() { 55 | for(var key in handlers) { 56 | if(typeof key !== 'string' || typeof handlers[key] !== 'function') { 57 | logger.error('transcation handlers parameter is error format, handler name: %s, handler function: %j.', key, handlers[key]); 58 | return; 59 | } 60 | dnames.push(key); 61 | dmethods.push(handlers[key]); 62 | } 63 | 64 | var flag = true; 65 | var times = retry; 66 | 67 | // do retry if failed util retry times 68 | async.whilst( 69 | function() { 70 | return retry > 0 && flag; 71 | }, 72 | function(callback) { 73 | var j = 0; 74 | retry--; 75 | async.forEachSeries(dmethods, function(method, cb) { 76 | method(cb); 77 | transactionLogger.info('[%s]:[%s] handler is executed.', name, dnames[j]); 78 | j++; 79 | }, function(err) { 80 | if(err) { 81 | process.nextTick(function() { 82 | transactionLogger.error('[%s]:[%s]:[%s] handler is executed with err: %j.', name, dnames[--j], times-retry, err.stack); 83 | var log = { 84 | name: name, 85 | method: dnames[j], 86 | retry: times-retry, 87 | time: Date.now(), 88 | type: 'handler', 89 | description: err.stack 90 | }; 91 | transactionErrorLogger.error(JSON.stringify(log)); 92 | utils.invokeCallback(callback); 93 | }); 94 | return; 95 | } 96 | flag = false; 97 | utils.invokeCallback(callback); 98 | process.nextTick(function() { 99 | transactionLogger.info('[%s] all conditions and handlers are executed successfully.', name); 100 | }); 101 | }); 102 | }, 103 | function(err) { 104 | if(err) { 105 | logger.error('transaction process is executed with error: %j', err); 106 | } 107 | // callback will not pass error 108 | } 109 | ); 110 | }); 111 | } 112 | }); 113 | }; -------------------------------------------------------------------------------- /lib/common/manager/taskManager.js: -------------------------------------------------------------------------------- 1 | var sequeue = require('seq-queue'); 2 | 3 | var manager = module.exports; 4 | 5 | var queues = {}; 6 | 7 | manager.timeout = 3000; 8 | 9 | /** 10 | * Add tasks into task group. Create the task group if it dose not exist. 11 | * 12 | * @param {String} key task key 13 | * @param {Function} fn task callback 14 | * @param {Function} ontimeout task timeout callback 15 | * @param {Number} timeout timeout for task 16 | */ 17 | manager.addTask = function(key, fn, ontimeout, timeout) { 18 | var queue = queues[key]; 19 | if(!queue) { 20 | queue = sequeue.createQueue(manager.timeout); 21 | queues[key] = queue; 22 | } 23 | 24 | return queue.push(fn, ontimeout, timeout); 25 | }; 26 | 27 | /** 28 | * Destroy task group 29 | * 30 | * @param {String} key task key 31 | * @param {Boolean} force whether close task group directly 32 | */ 33 | manager.closeQueue = function(key, force) { 34 | if(!queues[key]) { 35 | // ignore illeagle key 36 | return; 37 | } 38 | 39 | queues[key].close(force); 40 | delete queues[key]; 41 | }; 42 | -------------------------------------------------------------------------------- /lib/common/remote/backend/msgRemote.js: -------------------------------------------------------------------------------- 1 | var utils = require('../../../util/utils'); 2 | var logger = require('pomelo-logger').getLogger('forward-log', __filename); 3 | /** 4 | * Remote service for backend servers. 5 | * Receive and handle request message forwarded from frontend server. 6 | */ 7 | module.exports = function(app) { 8 | return new Remote(app); 9 | }; 10 | 11 | var Remote = function(app) { 12 | this.app = app; 13 | }; 14 | 15 | /** 16 | * Forward message from frontend server to other server's handlers 17 | * 18 | * @param msg {Object} request message 19 | * @param session {Object} session object for current request 20 | * @param cb {Function} callback function 21 | */ 22 | Remote.prototype.forwardMessage = function(msg, session, cb) { 23 | var server = this.app.components.__server__; 24 | var sessionService = this.app.components.__backendSession__; 25 | 26 | if(!server) { 27 | logger.error('server component not enable on %s', this.app.serverId); 28 | utils.invokeCallback(cb, new Error('server component not enable')); 29 | return; 30 | } 31 | 32 | if(!sessionService) { 33 | logger.error('backend session component not enable on %s', this.app.serverId); 34 | utils.invokeCallback(cb, new Error('backend sesssion component not enable')); 35 | return; 36 | } 37 | 38 | // generate backend session for current request 39 | var backendSession = sessionService.create(session); 40 | 41 | // handle the request 42 | 43 | logger.debug('backend server [%s] handle message: %j', this.app.serverId, msg); 44 | 45 | server.handle(msg, backendSession, function(err, resp, opts) { 46 | // cb && cb(err, resp, opts); 47 | utils.invokeCallback(cb, err, resp, opts); 48 | }); 49 | }; 50 | 51 | Remote.prototype.forwardMessage2 = function(route, body, aesPassword, compressGzip, session, cb) { 52 | var server = this.app.components.__server__; 53 | var sessionService = this.app.components.__backendSession__; 54 | 55 | if(!server) { 56 | logger.error('server component not enable on %s', this.app.serverId); 57 | utils.invokeCallback(cb, new Error('server component not enable')); 58 | return; 59 | } 60 | 61 | if(!sessionService) { 62 | logger.error('backend session component not enable on %s', this.app.serverId); 63 | utils.invokeCallback(cb, new Error('backend sesssion component not enable')); 64 | return; 65 | } 66 | 67 | // generate backend session for current request 68 | var backendSession = sessionService.create(session); 69 | 70 | // handle the request 71 | 72 | // logger.debug('backend server [%s] handle message: %j', this.app.serverId, msg); 73 | 74 | var dmsg = { 75 | route: route, 76 | body: body, 77 | compressGzip: compressGzip 78 | } 79 | 80 | var socket = { 81 | aesPassword: aesPassword 82 | } 83 | 84 | var connector = this.app.components.__connector__.connector; 85 | connector.runDecode(dmsg, socket, function(err, msg) { 86 | if(err) { 87 | return cb(err); 88 | } 89 | 90 | server.handle(msg, backendSession, function(err, resp, opts) { 91 | utils.invokeCallback(cb, err, resp, opts); 92 | }); 93 | }); 94 | }; -------------------------------------------------------------------------------- /lib/common/remote/frontend/channelRemote.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remote channel service for frontend server. 3 | * Receive push request from backend servers and push it to clients. 4 | */ 5 | var utils = require('../../../util/utils'); 6 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 7 | 8 | module.exports = function(app) { 9 | return new Remote(app); 10 | }; 11 | 12 | var Remote = function(app) { 13 | this.app = app; 14 | }; 15 | 16 | /** 17 | * Push message to client by uids. 18 | * 19 | * @param {String} route route string of message 20 | * @param {Object} msg message 21 | * @param {Array} uids user ids that would receive the message 22 | * @param {Object} opts push options 23 | * @param {Function} cb callback function 24 | */ 25 | Remote.prototype.pushMessage = function(route, msg, uids, opts, cb) { 26 | if(!msg){ 27 | logger.error('Can not send empty message! route : %j, compressed msg : %j', 28 | route, msg); 29 | utils.invokeCallback(cb, new Error('can not send empty message.')); 30 | return; 31 | } 32 | 33 | var connector = this.app.components.__connector__; 34 | 35 | var sessionService = this.app.get('sessionService'); 36 | var fails = [], sids = [], sessions, j, k; 37 | for(var i=0, l=uids.length; i= self.befores.length) { 50 | cb(err, resp, opts); 51 | return; 52 | } 53 | 54 | var handler = self.befores[index++]; 55 | if(typeof handler === 'function') { 56 | handler(msg, session, next); 57 | } else if(typeof handler.before === 'function') { 58 | handler.before(msg, session, next); 59 | } else { 60 | logger.error('meet invalid before filter, handler or handler.before should be function.'); 61 | next(new Error('invalid before filter.')); 62 | } 63 | }; //end of next 64 | 65 | next(); 66 | }; 67 | 68 | /** 69 | * Do after filter chain. 70 | * Give server a chance to do clean up jobs after request responsed. 71 | * After filter can not change the request flow before. 72 | * After filter should call the next callback to let the request pass to next after filter. 73 | * 74 | * @param err {Object} error object 75 | * @param session {Object} session object for current request 76 | * @param {Object} resp response object send to client 77 | * @param cb {Function} cb(err) callback function to invoke next chain node 78 | */ 79 | Service.prototype.afterFilter = function(err, msg, session, resp, cb) { 80 | var index = 0, self = this; 81 | function next(err) { 82 | //if done 83 | if(index >= self.afters.length) { 84 | cb(err); 85 | return; 86 | } 87 | 88 | var handler = self.afters[index++]; 89 | if(typeof handler === 'function') { 90 | handler(err, msg, session, resp, next); 91 | } else if(typeof handler.after === 'function') { 92 | handler.after(err, msg, session, resp, next); 93 | } else { 94 | logger.error('meet invalid after filter, handler or handler.after should be function.'); 95 | next(new Error('invalid after filter.')); 96 | } 97 | } //end of next 98 | 99 | next(err); 100 | }; 101 | -------------------------------------------------------------------------------- /lib/common/service/handlerService.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var utils = require('../../util/utils'); 3 | var Loader = require('pomelo-loader'); 4 | var pathUtil = require('../../util/pathUtil'); 5 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 6 | var forwardLogger = require('pomelo-logger').getLogger('forward-log', __filename); 7 | /** 8 | * Handler service. 9 | * Dispatch request to the relactive handler. 10 | * 11 | * @param {Object} app current application context 12 | */ 13 | var Service = function(app, opts) { 14 | this.app = app; 15 | this.handlerMap = {}; 16 | if(!!opts.reloadHandlers) { 17 | watchHandlers(app, this.handlerMap); 18 | } 19 | 20 | this.enableForwardLog = opts.enableForwardLog || false; 21 | }; 22 | 23 | module.exports = Service; 24 | 25 | Service.prototype.name = 'handler'; 26 | 27 | /** 28 | * Handler the request. 29 | */ 30 | Service.prototype.handle = function(routeRecord, msg, session, cb) { 31 | // the request should be processed by current server 32 | var handler = this.getHandler(routeRecord); 33 | if(!handler) { 34 | logger.error('[handleManager]: fail to find handler for %j', msg.__route__); 35 | utils.invokeCallback(cb, new Error('fail to find handler for ' + msg.__route__)); 36 | return; 37 | } 38 | var start = Date.now(); 39 | var self = this; 40 | 41 | var callback = function(err, resp, opts) { 42 | if(self.enableForwardLog) { 43 | var log = { 44 | route : msg.__route__, 45 | args : msg, 46 | time : utils.format(new Date(start)), 47 | timeUsed : new Date() - start 48 | }; 49 | forwardLogger.info(JSON.stringify(log)); 50 | } 51 | 52 | // resp = getResp(arguments); 53 | utils.invokeCallback(cb, err, resp, opts); 54 | } 55 | 56 | var method = routeRecord.method; 57 | 58 | if(!Array.isArray(msg)) { 59 | handler[method](msg, session, callback); 60 | } else { 61 | msg.push(session); 62 | msg.push(callback); 63 | handler[method].apply(handler, msg); 64 | } 65 | return; 66 | }; 67 | 68 | /** 69 | * Get handler instance by routeRecord. 70 | * 71 | * @param {Object} handlers handler map 72 | * @param {Object} routeRecord route record parsed from route string 73 | * @return {Object} handler instance if any matchs or null for match fail 74 | */ 75 | Service.prototype.getHandler = function(routeRecord) { 76 | var serverType = routeRecord.serverType; 77 | if(!this.handlerMap[serverType]) { 78 | loadHandlers(this.app, serverType, this.handlerMap); 79 | } 80 | var handlers = this.handlerMap[serverType] || {}; 81 | var handler = handlers[routeRecord.handler]; 82 | if(!handler) { 83 | logger.warn('could not find handler for routeRecord: %j', routeRecord); 84 | return null; 85 | } 86 | if(typeof handler[routeRecord.method] !== 'function') { 87 | logger.warn('could not find the method %s in handler: %s', routeRecord.method, routeRecord.handler); 88 | return null; 89 | } 90 | return handler; 91 | }; 92 | 93 | /** 94 | * Load handlers from current application 95 | */ 96 | var loadHandlers = function(app, serverType, handlerMap) { 97 | var p = pathUtil.getHandlerPath(app.getBase(), serverType); 98 | if(p) { 99 | handlerMap[serverType] = Loader.load(p, app); 100 | } 101 | }; 102 | 103 | var watchHandlers = function(app, handlerMap) { 104 | var p = pathUtil.getHandlerPath(app.getBase(), app.serverType); 105 | if (!!p){ 106 | fs.watch(p, function(event, name) { 107 | if(event === 'change') { 108 | handlerMap[app.serverType] = Loader.load(p, app); 109 | } 110 | }); 111 | } 112 | }; 113 | 114 | var getResp = function(args) { 115 | var len = args.length; 116 | if(len == 1) { 117 | return []; 118 | } 119 | 120 | if(len == 2) { 121 | return [args[1]]; 122 | } 123 | 124 | if(len == 3) { 125 | return [args[1], args[2]]; 126 | } 127 | 128 | if(len == 4) { 129 | return [args[1], args[2], args[3]]; 130 | } 131 | 132 | var r = new Array(len); 133 | for (var i = 1; i < len; i++) { 134 | r[i] = args[i]; 135 | } 136 | 137 | return r; 138 | } -------------------------------------------------------------------------------- /lib/components/backendSession.js: -------------------------------------------------------------------------------- 1 | var BackendSessionService = require('../common/service/backendSessionService'); 2 | 3 | module.exports = function(app) { 4 | var service = new BackendSessionService(app); 5 | service.name = '__backendSession__'; 6 | // export backend session service to the application context. 7 | app.set('backendSessionService', service, true); 8 | 9 | // for compatibility as `LocalSession` is renamed to `BackendSession` 10 | app.set('localSessionService', service, true); 11 | 12 | return service; 13 | }; 14 | -------------------------------------------------------------------------------- /lib/components/channel.js: -------------------------------------------------------------------------------- 1 | var ChannelService = require('../common/service/channelService'); 2 | 3 | module.exports = function(app, opts) { 4 | var service = new ChannelService(app, opts); 5 | app.set('channelService', service, true); 6 | service.name = '__channel__'; 7 | return service; 8 | }; -------------------------------------------------------------------------------- /lib/components/connection.js: -------------------------------------------------------------------------------- 1 | var ConnectionService = require('../common/service/connectionService'); 2 | 3 | /** 4 | * Connection component for statistics connection status of frontend servers 5 | */ 6 | module.exports = function(app) { 7 | return new Component(app); 8 | }; 9 | 10 | var Component = function(app) { 11 | this.app = app; 12 | this.service = new ConnectionService(app); 13 | 14 | // proxy the service methods except the lifecycle interfaces of component 15 | var method, self = this; 16 | 17 | var getFun = function(m) { 18 | return (function() { 19 | return function() { 20 | return self.service[m].apply(self.service, arguments); 21 | }; 22 | })(); 23 | }; 24 | 25 | for(var m in this.service) { 26 | if(m !== 'start' && m !== 'stop') { 27 | method = this.service[m]; 28 | if(typeof method === 'function') { 29 | this[m] = getFun(m); 30 | } 31 | } 32 | } 33 | }; 34 | 35 | Component.prototype.name = '__connection__'; 36 | -------------------------------------------------------------------------------- /lib/components/dictionary.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var utils = require('../util/utils'); 4 | var Loader = require('pomelo-loader'); 5 | var pathUtil = require('../util/pathUtil'); 6 | var crypto = require('crypto'); 7 | 8 | module.exports = function(app, opts) { 9 | return new Component(app, opts); 10 | }; 11 | 12 | var Component = function(app, opts) { 13 | this.app = app; 14 | this.dict = {}; 15 | this.abbrs = {}; 16 | this.userDicPath = null; 17 | this.version = ""; 18 | 19 | //Set user dictionary 20 | var p = path.join(app.getBase(), '/config/dictionary.json'); 21 | if(!!opts && !!opts.dict) { 22 | p = opts.dict; 23 | } 24 | if(fs.existsSync(p)) { 25 | this.userDicPath = p; 26 | } 27 | }; 28 | 29 | var pro = Component.prototype; 30 | 31 | pro.name = '__dictionary__'; 32 | 33 | pro.start = function(cb) { 34 | var servers = this.app.get('servers'); 35 | var routes = []; 36 | 37 | //Load all the handler files 38 | for(var serverType in servers) { 39 | var p = pathUtil.getHandlerPath(this.app.getBase(), serverType); 40 | if(!p) { 41 | continue; 42 | } 43 | 44 | var handlers = Loader.load(p, this.app); 45 | 46 | for(var name in handlers) { 47 | var handler = handlers[name]; 48 | for(var key in handler) { 49 | if(typeof(handler[key]) === 'function') { 50 | routes.push(serverType + '.' + name + '.' + key); 51 | } 52 | } 53 | } 54 | } 55 | 56 | //Sort the route to make sure all the routers abbr are the same in all the servers 57 | routes.sort(); 58 | var abbr; 59 | var i; 60 | for(i = 0; i < routes.length; i++) { 61 | abbr = i + 1; 62 | this.abbrs[abbr] = routes[i]; 63 | this.dict[routes[i]] = abbr; 64 | } 65 | 66 | //Load user dictionary 67 | if(!!this.userDicPath) { 68 | var userDic = require(this.userDicPath); 69 | 70 | abbr = routes.length + 1; 71 | for(i = 0; i < userDic.length; i++) { 72 | var route = userDic[i]; 73 | 74 | this.abbrs[abbr] = route; 75 | this.dict[route] = abbr; 76 | abbr++; 77 | } 78 | } 79 | 80 | this.version = crypto.createHash('md5').update(JSON.stringify(this.dict)).digest('base64'); 81 | 82 | utils.invokeCallback(cb); 83 | }; 84 | 85 | pro.getDict = function() { 86 | return this.dict; 87 | }; 88 | 89 | pro.getAbbrs = function() { 90 | return this.abbrs; 91 | }; 92 | 93 | pro.getVersion = function() { 94 | return this.version; 95 | }; 96 | -------------------------------------------------------------------------------- /lib/components/master.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Component for master. 3 | */ 4 | var Master = require('../master/master'); 5 | 6 | /** 7 | * Component factory function 8 | * 9 | * @param {Object} app current application context 10 | * @return {Object} component instances 11 | */ 12 | module.exports = function (app, opts) { 13 | return new Component(app, opts); 14 | }; 15 | 16 | /** 17 | * Master component class 18 | * 19 | * @param {Object} app current application context 20 | */ 21 | var Component = function (app, opts) { 22 | this.master = new Master(app, opts); 23 | }; 24 | 25 | var pro = Component.prototype; 26 | 27 | pro.name = '__master__'; 28 | 29 | /** 30 | * Component lifecycle function 31 | * 32 | * @param {Function} cb 33 | * @return {Void} 34 | */ 35 | pro.start = function (cb) { 36 | this.master.start(cb); 37 | }; 38 | 39 | /** 40 | * Component lifecycle function 41 | * 42 | * @param {Boolean} force whether stop the component immediately 43 | * @param {Function} cb 44 | * @return {Void} 45 | */ 46 | pro.stop = function (force, cb) { 47 | this.master.stop(cb); 48 | }; 49 | -------------------------------------------------------------------------------- /lib/components/monitor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Component for monitor. 3 | * Load and start monitor client. 4 | */ 5 | var Monitor = require('../monitor/monitor'); 6 | 7 | 8 | 9 | /** 10 | * Component factory function 11 | * 12 | * @param {Object} app current application context 13 | * @return {Object} component instances 14 | */ 15 | module.exports = function(app, opts) { 16 | return new Component(app, opts); 17 | }; 18 | 19 | var Component = function(app, opts) { 20 | this.monitor = new Monitor(app, opts); 21 | }; 22 | 23 | var pro = Component.prototype; 24 | 25 | pro.name = '__monitor__'; 26 | 27 | pro.start = function(cb) { 28 | this.monitor.start(cb); 29 | }; 30 | 31 | pro.stop = function(force, cb) { 32 | this.monitor.stop(cb); 33 | }; 34 | 35 | pro.reconnect = function(masterInfo) { 36 | this.monitor.reconnect(masterInfo); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/components/protobuf.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var protobuf = require('pomelo-protobuf'); 4 | var Constants = require('../util/constants'); 5 | var crypto = require('crypto'); 6 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 7 | 8 | module.exports = function(app, opts) { 9 | return new Component(app, opts); 10 | }; 11 | 12 | var Component = function(app, opts) { 13 | this.app = app; 14 | opts = opts || {}; 15 | this.watchers = {}; 16 | this.serverProtos = {}; 17 | this.clientProtos = {}; 18 | this.version = ""; 19 | 20 | var env = app.get(Constants.RESERVED.ENV); 21 | var originServerPath = path.join(app.getBase(), Constants.FILEPATH.SERVER_PROTOS); 22 | var presentServerPath = path.join(Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.SERVER_PROTOS)); 23 | var originClientPath = path.join(app.getBase(), Constants.FILEPATH.CLIENT_PROTOS); 24 | var presentClientPath = path.join(Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.CLIENT_PROTOS)); 25 | 26 | this.serverProtosPath = opts.serverProtos || (fs.existsSync(originServerPath) ? Constants.FILEPATH.SERVER_PROTOS : presentServerPath); 27 | this.clientProtosPath = opts.clientProtos || (fs.existsSync(originClientPath) ? Constants.FILEPATH.CLIENT_PROTOS : presentClientPath); 28 | 29 | this.setProtos(Constants.RESERVED.SERVER, path.join(app.getBase(), this.serverProtosPath)); 30 | this.setProtos(Constants.RESERVED.CLIENT, path.join(app.getBase(), this.clientProtosPath)); 31 | 32 | protobuf.init({encoderProtos:this.serverProtos, decoderProtos:this.clientProtos}); 33 | }; 34 | 35 | var pro = Component.prototype; 36 | 37 | pro.name = '__protobuf__'; 38 | 39 | pro.encode = function(key, msg) { 40 | return protobuf.encode(key, msg); 41 | }; 42 | 43 | pro.encode2Bytes = function(key, msg) { 44 | return protobuf.encode2Bytes(key, msg); 45 | }; 46 | 47 | pro.decode = function(key, msg) { 48 | return protobuf.decode(key, msg); 49 | }; 50 | 51 | pro.getProtos = function() { 52 | return { 53 | server : this.serverProtos, 54 | client : this.clientProtos, 55 | version : this.version 56 | }; 57 | }; 58 | 59 | pro.getVersion = function() { 60 | return this.version; 61 | }; 62 | 63 | pro.setProtos = function(type, path) { 64 | if(!fs.existsSync(path)) { 65 | return; 66 | } 67 | 68 | if(type === Constants.RESERVED.SERVER) { 69 | this.serverProtos = protobuf.parse(require(path)); 70 | } 71 | 72 | if(type === Constants.RESERVED.CLIENT) { 73 | this.clientProtos = protobuf.parse(require(path)); 74 | } 75 | 76 | var protoStr = JSON.stringify(this.clientProtos) + JSON.stringify(this.serverProtos); 77 | this.version = crypto.createHash('md5').update(protoStr).digest('base64'); 78 | 79 | //Watch file 80 | var watcher = fs.watch(path, this.onUpdate.bind(this, type, path)); 81 | if (this.watchers[type]) { 82 | this.watchers[type].close(); 83 | } 84 | this.watchers[type] = watcher; 85 | }; 86 | 87 | pro.onUpdate = function(type, path, event) { 88 | if(event !== 'change') { 89 | return; 90 | } 91 | 92 | var self = this; 93 | fs.readFile(path, 'utf8' ,function(err, data) { 94 | try { 95 | var protos = protobuf.parse(JSON.parse(data)); 96 | if(type === Constants.RESERVED.SERVER) { 97 | protobuf.setEncoderProtos(protos); 98 | self.serverProtos = protos; 99 | } else { 100 | protobuf.setDecoderProtos(protos); 101 | self.clientProtos = protos; 102 | } 103 | 104 | var protoStr = JSON.stringify(self.clientProtos) + JSON.stringify(self.serverProtos); 105 | self.version = crypto.createHash('md5').update(protoStr).digest('base64'); 106 | logger.info('change proto file , type : %j, path : %j, version : %j', type, path, self.version); 107 | } catch(e) { 108 | logger.warn("change proto file error! path : %j", path); 109 | logger.warn(e); 110 | } 111 | }); 112 | }; 113 | 114 | pro.stop = function(force, cb) { 115 | for (var type in this.watchers) { 116 | this.watchers[type].close(); 117 | } 118 | this.watchers = {}; 119 | process.nextTick(cb); 120 | }; 121 | -------------------------------------------------------------------------------- /lib/components/pushScheduler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Scheduler component to schedule message sending. 3 | */ 4 | 5 | var DefaultScheduler = require('../pushSchedulers/direct'); 6 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 7 | 8 | module.exports = function(app, opts) { 9 | return new PushScheduler(app, opts); 10 | }; 11 | 12 | var PushScheduler = function(app, opts) { 13 | this.app = app; 14 | opts = opts || {}; 15 | this.scheduler = getScheduler(this, app, opts); 16 | }; 17 | 18 | PushScheduler.prototype.name = '__pushScheduler__'; 19 | 20 | /** 21 | * Component lifecycle callback 22 | * 23 | * @param {Function} cb 24 | * @return {Void} 25 | */ 26 | PushScheduler.prototype.afterStart = function(cb) { 27 | if(this.isSelectable) { 28 | for (var k in this.scheduler) { 29 | var sch = this.scheduler[k]; 30 | if(typeof sch.start === 'function') { 31 | sch.start(); 32 | } 33 | } 34 | process.nextTick(cb); 35 | } else if(typeof this.scheduler.start === 'function') { 36 | this.scheduler.start(cb); 37 | } else { 38 | process.nextTick(cb); 39 | } 40 | }; 41 | 42 | /** 43 | * Component lifecycle callback 44 | * 45 | * @param {Function} cb 46 | * @return {Void} 47 | */ 48 | PushScheduler.prototype.stop = function(force, cb) { 49 | if(this.isSelectable) { 50 | for (var k in this.scheduler) { 51 | var sch = this.scheduler[k]; 52 | if(typeof sch.stop === 'function') { 53 | sch.stop(); 54 | } 55 | } 56 | process.nextTick(cb); 57 | } else if(typeof this.scheduler.stop === 'function') { 58 | this.scheduler.stop(cb); 59 | } else { 60 | process.nextTick(cb); 61 | } 62 | }; 63 | 64 | /** 65 | * Schedule how the message to send. 66 | * 67 | * @param {Number} reqId request id 68 | * @param {String} route route string of the message 69 | * @param {Object} msg message content after encoded 70 | * @param {Array} recvs array of receiver's session id 71 | * @param {Object} opts options 72 | * @param {Function} cb 73 | */ 74 | 75 | PushScheduler.prototype.schedule = function(reqId, route, msg, recvs, opts, cb) { 76 | var self = this; 77 | if(self.isSelectable) { 78 | if(typeof self.selector === 'function') { 79 | self.selector(reqId, route, msg, recvs, opts, function(id) { 80 | if(self.scheduler[id] && typeof self.scheduler[id].schedule === 'function') { 81 | self.scheduler[id].schedule(reqId, route, msg, recvs, opts, cb); 82 | } else { 83 | logger.error('invalid pushScheduler id, id: %j', id); 84 | } 85 | }); 86 | } else { 87 | logger.error('the selector for pushScheduler is not a function, selector: %j', self.selector); 88 | } 89 | } else { 90 | if (typeof self.scheduler.schedule === 'function') { 91 | self.scheduler.schedule(reqId, route, msg, recvs, opts, cb); 92 | } else { 93 | logger.error('the scheduler does not have a schedule function, scheduler: %j', self.scheduler); 94 | } 95 | } 96 | }; 97 | 98 | var getScheduler = function(pushSchedulerComp, app, opts) { 99 | var scheduler = opts.scheduler || DefaultScheduler; 100 | if(typeof scheduler === 'function') { 101 | return scheduler(app, opts); 102 | } 103 | 104 | if(Array.isArray(scheduler)) { 105 | var res = {}; 106 | scheduler.forEach(function(sch) { 107 | if(typeof sch.scheduler === 'function') { 108 | res[sch.id] = sch.scheduler(app, sch.options); 109 | } else { 110 | res[sch.id] = sch.scheduler; 111 | } 112 | }); 113 | pushSchedulerComp.isSelectable = true; 114 | pushSchedulerComp.selector = opts.selector; 115 | return res; 116 | } 117 | 118 | return scheduler; 119 | }; 120 | -------------------------------------------------------------------------------- /lib/components/remote.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Component for remote service. 3 | * Load remote service and add to global context. 4 | */ 5 | var fs = require('fs'); 6 | var pathUtil = require('../util/pathUtil'); 7 | var RemoteServer = require('pomelo-rpc').server; 8 | 9 | /** 10 | * Remote component factory function 11 | * 12 | * @param {Object} app current application context 13 | * @param {Object} opts construct parameters 14 | * opts.acceptorFactory {Object}: acceptorFactory.create(opts, cb) 15 | * @return {Object} remote component instances 16 | */ 17 | module.exports = function(app, opts) { 18 | opts = opts || {}; 19 | 20 | // cacheMsg is deprecated, just for compatibility here. 21 | opts.bufferMsg = opts.bufferMsg || opts.cacheMsg || false; 22 | opts.interval = opts.interval || 30; 23 | if(app.enabled('rpcDebugLog')) { 24 | opts.rpcDebugLog = true; 25 | opts.rpcLogger = require('pomelo-logger').getLogger('rpc-debug', __filename); 26 | } 27 | return new Component(app, opts); 28 | }; 29 | 30 | /** 31 | * Remote component class 32 | * 33 | * @param {Object} app current application context 34 | * @param {Object} opts construct parameters 35 | */ 36 | var Component = function(app, opts) { 37 | this.app = app; 38 | this.opts = opts; 39 | }; 40 | 41 | var pro = Component.prototype; 42 | 43 | pro.name = '__remote__'; 44 | 45 | /** 46 | * Remote component lifecycle function 47 | * 48 | * @param {Function} cb 49 | * @return {Void} 50 | */ 51 | pro.start = function(cb) { 52 | this.opts.port = this.app.getCurServer().port; 53 | this.remote = genRemote(this.app, this.opts); 54 | this.remote.start(); 55 | process.nextTick(cb); 56 | }; 57 | 58 | /** 59 | * Remote component lifecycle function 60 | * 61 | * @param {Boolean} force whether stop the component immediately 62 | * @param {Function} cb 63 | * @return {Void} 64 | */ 65 | pro.stop = function(force, cb) { 66 | this.remote.stop(force); 67 | process.nextTick(cb); 68 | }; 69 | 70 | /** 71 | * Get remote paths from application 72 | * 73 | * @param {Object} app current application context 74 | * @return {Array} paths 75 | * 76 | */ 77 | var getRemotePaths = function(app) { 78 | var paths = []; 79 | 80 | var role; 81 | // master server should not come here 82 | if(app.isFrontend()) { 83 | role = 'frontend'; 84 | } else { 85 | role = 'backend'; 86 | } 87 | 88 | var sysPath = pathUtil.getSysRemotePath(role), serverType = app.getServerType(); 89 | if(fs.existsSync(sysPath)) { 90 | paths.push(pathUtil.remotePathRecord('sys', serverType, sysPath)); 91 | } 92 | var userPath = pathUtil.getUserRemotePath(app.getBase(), serverType); 93 | if(fs.existsSync(userPath)) { 94 | paths.push(pathUtil.remotePathRecord('user', serverType, userPath)); 95 | } 96 | 97 | return paths; 98 | }; 99 | 100 | /** 101 | * Generate remote server instance 102 | * 103 | * @param {Object} app current application context 104 | * @param {Object} opts contructor parameters for rpc Server 105 | * @return {Object} remote server instance 106 | */ 107 | var genRemote = function(app, opts) { 108 | opts.paths = getRemotePaths(app); 109 | opts.context = app; 110 | if(!!opts.rpcServer) { 111 | return opts.rpcServer.create(opts); 112 | } else { 113 | return RemoteServer.create(opts); 114 | } 115 | }; 116 | -------------------------------------------------------------------------------- /lib/components/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Component for server starup. 3 | */ 4 | var Server = require('../server/server'); 5 | 6 | /** 7 | * Component factory function 8 | * 9 | * @param {Object} app current application context 10 | * @return {Object} component instance 11 | */ 12 | module.exports = function(app, opts) { 13 | return new Component(app, opts); 14 | }; 15 | 16 | /** 17 | * Server component class 18 | * 19 | * @param {Object} app current application context 20 | */ 21 | var Component = function(app, opts) { 22 | this.server = Server.create(app, opts); 23 | }; 24 | 25 | var pro = Component.prototype; 26 | 27 | pro.name = '__server__'; 28 | 29 | /** 30 | * Component lifecycle callback 31 | * 32 | * @param {Function} cb 33 | * @return {Void} 34 | */ 35 | pro.start = function(cb) { 36 | this.server.start(); 37 | process.nextTick(cb); 38 | }; 39 | 40 | /** 41 | * Component lifecycle callback 42 | * 43 | * @param {Function} cb 44 | * @return {Void} 45 | */ 46 | pro.afterStart = function(cb) { 47 | this.server.afterStart(); 48 | process.nextTick(cb); 49 | }; 50 | 51 | /** 52 | * Component lifecycle function 53 | * 54 | * @param {Boolean} force whether stop the component immediately 55 | * @param {Function} cb 56 | * @return {Void} 57 | */ 58 | pro.stop = function(force, cb) { 59 | this.server.stop(); 60 | process.nextTick(cb); 61 | }; 62 | 63 | /** 64 | * Proxy server handle 65 | */ 66 | pro.handle = function(msg, session, cb) { 67 | this.server.handle(msg, session, cb); 68 | }; 69 | 70 | /** 71 | * Proxy server global handle 72 | */ 73 | pro.globalHandle = function(msg, session, cb) { 74 | this.server.globalHandle(msg, session, cb); 75 | }; -------------------------------------------------------------------------------- /lib/components/session.js: -------------------------------------------------------------------------------- 1 | var SessionService = require('../common/service/sessionService'); 2 | 3 | module.exports = function(app, opts) { 4 | var cmp = new Component(app, opts); 5 | app.set('sessionService', cmp, true); 6 | return cmp; 7 | }; 8 | 9 | /** 10 | * Session component. Manage sessions. 11 | * 12 | * @param {Object} app current application context 13 | * @param {Object} opts attach parameters 14 | */ 15 | var Component = function(app, opts) { 16 | opts = opts || {}; 17 | this.app = app; 18 | this.service = new SessionService(opts); 19 | 20 | var getFun = function(m) { 21 | return (function() { 22 | return function() { 23 | return self.service[m].apply(self.service, arguments); 24 | }; 25 | })(); 26 | }; 27 | // proxy the service methods except the lifecycle interfaces of component 28 | var method, self = this; 29 | for(var m in this.service) { 30 | if(m !== 'start' && m !== 'stop') { 31 | method = this.service[m]; 32 | if(typeof method === 'function') { 33 | this[m] = getFun(m); 34 | } 35 | } 36 | } 37 | }; 38 | 39 | Component.prototype.name = '__session__'; 40 | -------------------------------------------------------------------------------- /lib/connectors/commands/handshake.js: -------------------------------------------------------------------------------- 1 | var pomelo = require('../../pomelo'); 2 | var Package = require('pomelo-protocol').Package; 3 | 4 | var CODE_OK = 200; 5 | var CODE_USE_ERROR = 500; 6 | var CODE_OLD_CLIENT = 501; 7 | 8 | /** 9 | * Process the handshake request. 10 | * 11 | * @param {Object} opts option parameters 12 | * opts.handshake(msg, cb(err, resp)) handshake callback. msg is the handshake message from client. 13 | * opts.hearbeat heartbeat interval (level?) 14 | * opts.version required client level 15 | */ 16 | var Command = function(opts) { 17 | opts = opts || {}; 18 | this.userHandshake = opts.handshake; 19 | 20 | if(opts.heartbeat) { 21 | this.heartbeatSec = opts.heartbeat; 22 | this.heartbeat = opts.heartbeat * 1000; 23 | } 24 | 25 | this.checkClient = opts.checkClient; 26 | 27 | this.useDict = opts.useDict; 28 | this.useProtobuf = opts.useProtobuf; 29 | this.useCrypto = opts.useCrypto; 30 | }; 31 | 32 | module.exports = Command; 33 | 34 | Command.prototype.handle = function(socket, msg) { 35 | if(!msg.sys) { 36 | processError(socket, CODE_USE_ERROR); 37 | return; 38 | } 39 | 40 | if(typeof this.checkClient === 'function') { 41 | if(!msg || !msg.sys || !this.checkClient(msg.sys.type, msg.sys.version)) { 42 | processError(socket, CODE_OLD_CLIENT); 43 | return; 44 | } 45 | } 46 | 47 | var opts = { 48 | heartbeat : setupHeartbeat(this) 49 | }; 50 | 51 | if(this.useDict) { 52 | var dictVersion = pomelo.app.components.__dictionary__.getVersion(); 53 | if(!msg.sys.dictVersion || msg.sys.dictVersion !== dictVersion){ 54 | 55 | // may be deprecated in future 56 | opts.dict = pomelo.app.components.__dictionary__.getDict(); 57 | 58 | opts.routeToCode = pomelo.app.components.__dictionary__.getDict(); 59 | opts.codeToRoute = pomelo.app.components.__dictionary__.getAbbrs(); 60 | opts.dictVersion = dictVersion; 61 | } 62 | opts.useDict = true; 63 | } 64 | 65 | if(this.useProtobuf) { 66 | var protoVersion = pomelo.app.components.__protobuf__.getVersion(); 67 | if(!msg.sys.protoVersion || msg.sys.protoVersion !== protoVersion){ 68 | opts.protos = pomelo.app.components.__protobuf__.getProtos(); 69 | } 70 | opts.useProto = true; 71 | } 72 | 73 | if(!!pomelo.app.components.__decodeIO__protobuf__) { 74 | if(!!this.useProtobuf) { 75 | throw new Error('protobuf can not be both used in the same project.'); 76 | } 77 | var version = pomelo.app.components.__decodeIO__protobuf__.getVersion(); 78 | if(!msg.sys.protoVersion || msg.sys.protoVersion < version) { 79 | opts.protos = pomelo.app.components.__decodeIO__protobuf__.getProtos(); 80 | } 81 | opts.useProto = true; 82 | } 83 | 84 | if(this.useCrypto) { 85 | pomelo.app.components.__connector__.setPubKey(socket.id, msg.sys.rsa); 86 | } 87 | 88 | if(typeof this.userHandshake === 'function') { 89 | this.userHandshake(msg, function(err, resp) { 90 | if(err) { 91 | process.nextTick(function() { 92 | processError(socket, CODE_USE_ERROR); 93 | }); 94 | return; 95 | } 96 | process.nextTick(function() { 97 | response(socket, opts, resp); 98 | }); 99 | }, socket); 100 | return; 101 | } 102 | 103 | process.nextTick(function() { 104 | response(socket, opts); 105 | }); 106 | }; 107 | 108 | var setupHeartbeat = function(self) { 109 | return self.heartbeatSec; 110 | }; 111 | 112 | var response = function(socket, sys, resp) { 113 | var res = { 114 | code: CODE_OK, 115 | sys: sys 116 | }; 117 | if(resp) { 118 | res.user = resp; 119 | } 120 | socket.handshakeResponse(Package.encode(Package.TYPE_HANDSHAKE, new Buffer(JSON.stringify(res)))); 121 | }; 122 | 123 | var processError = function(socket, code) { 124 | var res = { 125 | code: code 126 | }; 127 | socket.sendForce(Package.encode(Package.TYPE_HANDSHAKE, new Buffer(JSON.stringify(res)))); 128 | process.nextTick(function() { 129 | socket.disconnect(); 130 | }); 131 | }; 132 | -------------------------------------------------------------------------------- /lib/connectors/commands/heartbeat.js: -------------------------------------------------------------------------------- 1 | var Package = require('pomelo-protocol').Package; 2 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 3 | 4 | /** 5 | * Process heartbeat request. 6 | * 7 | * @param {Object} opts option request 8 | * opts.heartbeat heartbeat interval 9 | */ 10 | var Command = function(opts) { 11 | opts = opts || {}; 12 | this.heartbeat = null; 13 | this.timeout = null; 14 | this.disconnectOnTimeout = opts.disconnectOnTimeout; 15 | 16 | if(opts.heartbeat) { 17 | this.heartbeat = opts.heartbeat * 1000; // heartbeat interval 18 | this.timeout = opts.timeout * 1000 || this.heartbeat * 2; // max heartbeat message timeout 19 | this.disconnectOnTimeout = true; 20 | } 21 | 22 | this.timeouts = {}; 23 | this.clients = {}; 24 | }; 25 | 26 | module.exports = Command; 27 | 28 | Command.prototype.handle = function(socket) { 29 | if(!this.heartbeat) { 30 | // no heartbeat setting 31 | return; 32 | } 33 | 34 | var self = this; 35 | 36 | if(!this.clients[socket.id]) { 37 | // clear timers when socket disconnect or error 38 | this.clients[socket.id] = 1; 39 | socket.once('disconnect', clearTimers.bind(null, this, socket.id)); 40 | socket.once('error', clearTimers.bind(null, this, socket.id)); 41 | } 42 | 43 | // clear timeout timer 44 | if(self.disconnectOnTimeout) { 45 | this.clear(socket.id); 46 | } 47 | 48 | socket.sendRaw(Package.encode(Package.TYPE_HEARTBEAT)); 49 | 50 | if(self.disconnectOnTimeout) { 51 | self.timeouts[socket.id] = setTimeout(function() { 52 | logger.info('client %j heartbeat timeout.', socket.id); 53 | socket.disconnect(); 54 | }, self.timeout); 55 | } 56 | }; 57 | 58 | Command.prototype.clear = function(id) { 59 | var tid = this.timeouts[id]; 60 | if(tid) { 61 | clearTimeout(tid); 62 | delete this.timeouts[id]; 63 | } 64 | }; 65 | 66 | var clearTimers = function(self, id) { 67 | delete self.clients[id]; 68 | var tid = self.timeouts[id]; 69 | if(tid) { 70 | clearTimeout(tid); 71 | delete self.timeouts[id]; 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /lib/connectors/commands/kick.js: -------------------------------------------------------------------------------- 1 | var Package = require('pomelo-protocol').Package; 2 | 3 | module.exports.handle = function(socket, reason) { 4 | // websocket close code 1000 would emit when client close the connection 5 | if(typeof reason === 'string') { 6 | var res = { 7 | reason: reason 8 | }; 9 | socket.sendRaw(Package.encode(Package.TYPE_KICK, new Buffer(JSON.stringify(res)))); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /lib/connectors/common/coder.js: -------------------------------------------------------------------------------- 1 | var Message = require('pomelo-protocol').Message; 2 | var Constants = require('../../util/constants'); 3 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 4 | 5 | var encode = function(reqId, route, msg) { 6 | if(!!reqId) { 7 | return composeResponse(this, reqId, route, msg); 8 | } else { 9 | return composePush(this, route, msg); 10 | } 11 | }; 12 | 13 | var decode = function(msg) { 14 | msg = Message.decode(msg.body); 15 | var route = msg.route; 16 | 17 | // decode use dictionary 18 | if(!!msg.compressRoute) { 19 | if(!!this.connector.useDict) { 20 | var abbrs = this.dictionary.getAbbrs(); 21 | if(!abbrs[route]) { 22 | logger.error('dictionary error! no abbrs for route : %s', route); 23 | return null; 24 | } 25 | route = msg.route = abbrs[route]; 26 | } else { 27 | logger.error('fail to uncompress route code for msg: %j, server not enable dictionary.', msg); 28 | return null; 29 | } 30 | } 31 | 32 | // decode use protobuf 33 | if(!!this.protobuf && !!this.protobuf.getProtos().client[route]) { 34 | msg.body = this.protobuf.decode(route, msg.body); 35 | } else if(!!this.decodeIO_protobuf && !!this.decodeIO_protobuf.check(Constants.RESERVED.CLIENT, route)) { 36 | msg.body = this.decodeIO_protobuf.decode(route, msg.body); 37 | } else { 38 | try { 39 | msg.body = JSON.parse(msg.body.toString('utf8')); 40 | } catch (ex) { 41 | msg.body = {}; 42 | } 43 | } 44 | 45 | return msg; 46 | }; 47 | 48 | var composeResponse = function(server, msgId, route, msgBody) { 49 | if(!msgId || !route || !msgBody) { 50 | return null; 51 | } 52 | msgBody = encodeBody(server, route, msgBody); 53 | return Message.encode(msgId, Message.TYPE_RESPONSE, 0, null, msgBody); 54 | }; 55 | 56 | var composePush = function(server, route, msgBody) { 57 | if(!route || !msgBody){ 58 | return null; 59 | } 60 | msgBody = encodeBody(server, route, msgBody); 61 | // encode use dictionary 62 | var compressRoute = 0; 63 | if(!!server.dictionary) { 64 | var dict = server.dictionary.getDict(); 65 | if(!!server.connector.useDict && !!dict[route]) { 66 | route = dict[route]; 67 | compressRoute = 1; 68 | } 69 | } 70 | return Message.encode(0, Message.TYPE_PUSH, compressRoute, route, msgBody); 71 | }; 72 | 73 | var encodeBody = function(server, route, msgBody) { 74 | // encode use protobuf 75 | if(!!server.protobuf && !!server.protobuf.getProtos().server[route]) { 76 | msgBody = server.protobuf.encode(route, msgBody); 77 | } else if(!!server.decodeIO_protobuf && !!server.decodeIO_protobuf.check(Constants.RESERVED.SERVER, route)) { 78 | msgBody = server.decodeIO_protobuf.encode(route, msgBody); 79 | } else { 80 | msgBody = new Buffer(JSON.stringify(msgBody), 'utf8'); 81 | } 82 | return msgBody; 83 | }; 84 | 85 | module.exports = { 86 | encode: encode, 87 | decode: decode 88 | }; -------------------------------------------------------------------------------- /lib/connectors/common/handler.js: -------------------------------------------------------------------------------- 1 | var protocol = require('pomelo-protocol'); 2 | var Package = protocol.Package; 3 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 4 | 5 | var handlers = {}; 6 | 7 | var ST_INITED = 0; 8 | var ST_WAIT_ACK = 1; 9 | var ST_WORKING = 2; 10 | var ST_CLOSED = 3; 11 | 12 | var handleHandshake = function(socket, pkg) { 13 | if(socket.state !== ST_INITED) { 14 | return; 15 | } 16 | try { 17 | socket.emit('handshake', JSON.parse(protocol.strdecode(pkg.body))); 18 | } catch (ex) { 19 | socket.emit('handshake', {}); 20 | } 21 | }; 22 | 23 | var handleHandshakeAck = function(socket, pkg) { 24 | if(socket.state !== ST_WAIT_ACK) { 25 | return; 26 | } 27 | socket.state = ST_WORKING; 28 | socket.emit('heartbeat'); 29 | }; 30 | 31 | var handleHeartbeat = function(socket, pkg) { 32 | if(socket.state !== ST_WORKING) { 33 | return; 34 | } 35 | socket.emit('heartbeat'); 36 | }; 37 | 38 | var handleData = function(socket, pkg) { 39 | if(socket.state !== ST_WORKING) { 40 | return; 41 | } 42 | socket.emit('message', pkg); 43 | }; 44 | 45 | handlers[Package.TYPE_HANDSHAKE] = handleHandshake; 46 | handlers[Package.TYPE_HANDSHAKE_ACK] = handleHandshakeAck; 47 | handlers[Package.TYPE_HEARTBEAT] = handleHeartbeat; 48 | handlers[Package.TYPE_DATA] = handleData; 49 | 50 | var handle = function(socket, pkg) { 51 | var handler = handlers[pkg.type]; 52 | if(!!handler) { 53 | handler(socket, pkg); 54 | } else { 55 | logger.error('could not find handle invalid data package.'); 56 | socket.disconnect(); 57 | } 58 | }; 59 | 60 | module.exports = handle; 61 | -------------------------------------------------------------------------------- /lib/connectors/hybrid/switcher.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var WSProcessor = require('./wsprocessor'); 4 | var TCPProcessor = require('./tcpprocessor'); 5 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 6 | 7 | var HTTP_METHODS = [ 8 | 'GET', 'POST', 'DELETE', 'PUT', 'HEAD' 9 | ]; 10 | 11 | var ST_STARTED = 1; 12 | var ST_CLOSED = 2; 13 | 14 | var DEFAULT_TIMEOUT = 90; 15 | 16 | /** 17 | * Switcher for tcp and websocket protocol 18 | * 19 | * @param {Object} server tcp server instance from node.js net module 20 | */ 21 | var Switcher = function(server, opts) { 22 | EventEmitter.call(this); 23 | this.server = server; 24 | this.wsprocessor = new WSProcessor(); 25 | this.tcpprocessor = new TCPProcessor(opts.closeMethod); 26 | this.id = 1; 27 | this.timeout = (opts.timeout || DEFAULT_TIMEOUT) * 1000; 28 | this.setNoDelay = opts.setNoDelay; 29 | 30 | if (!opts.ssl) { 31 | this.server.on('connection', this.newSocket.bind(this)); 32 | } else { 33 | this.server.on('secureConnection', this.newSocket.bind(this)); 34 | this.server.on('clientError', function(e, tlsSo) { 35 | logger.warn('an ssl error occured before handshake established: ', e); 36 | tlsSo.destroy(); 37 | }); 38 | } 39 | 40 | this.wsprocessor.on('connection', this.emit.bind(this, 'connection')); 41 | this.tcpprocessor.on('connection', this.emit.bind(this, 'connection')); 42 | 43 | this.state = ST_STARTED; 44 | }; 45 | util.inherits(Switcher, EventEmitter); 46 | 47 | module.exports = Switcher; 48 | 49 | Switcher.prototype.newSocket = function(socket) { 50 | if(this.state !== ST_STARTED) { 51 | return; 52 | } 53 | 54 | socket.setTimeout(this.timeout, function() { 55 | logger.warn('connection is timeout without communication, the remote ip is %s && port is %s', 56 | socket.remoteAddress, socket.remotePort); 57 | socket.destroy(); 58 | }); 59 | 60 | var self = this; 61 | 62 | socket.once('data', function(data) { 63 | // FIXME: handle incomplete HTTP method 64 | if(isHttp(data)) { 65 | processHttp(self, self.wsprocessor, socket, data); 66 | } else { 67 | if(!!self.setNoDelay) { 68 | socket.setNoDelay(true); 69 | } 70 | processTcp(self, self.tcpprocessor, socket, data); 71 | } 72 | }); 73 | }; 74 | 75 | Switcher.prototype.close = function() { 76 | if(this.state !== ST_STARTED) { 77 | return; 78 | } 79 | 80 | this.state = ST_CLOSED; 81 | this.wsprocessor.close(); 82 | this.tcpprocessor.close(); 83 | }; 84 | 85 | var isHttp = function(data) { 86 | var head = data.toString('utf8', 0, 4); 87 | 88 | for(var i=0, l=HTTP_METHODS.length; i 2) return null; 28 | if (typeof id !== 'number' || id < 0 || id > 0xFFFF) return null; 29 | 30 | /* Generate header */ 31 | packet.header = protocol.codes.publish << protocol.CMD_SHIFT | dup | qos << protocol.QOS_SHIFT | retain; 32 | 33 | /* Topic name */ 34 | packet.payload = packet.payload.concat(gen_string(topic)); 35 | 36 | /* Message ID */ 37 | if (qos > 0) packet.payload = packet.payload.concat(gen_number(id)); 38 | 39 | 40 | var buf = new Buffer([packet.header] 41 | .concat(gen_length(packet.payload.length + payload.length)) 42 | .concat(packet.payload)); 43 | 44 | return Buffer.concat([buf, payload]); 45 | }; 46 | 47 | /* Requires length be a number > 0 */ 48 | var gen_length = function(length) { 49 | if(typeof length !== "number") return null; 50 | if(length < 0) return null; 51 | 52 | var len = []; 53 | var digit = 0; 54 | 55 | do { 56 | digit = length % 128 | 0; 57 | length = length / 128 | 0; 58 | if (length > 0) { 59 | digit = digit | 0x80; 60 | } 61 | len.push(digit); 62 | } while (length > 0); 63 | 64 | return len; 65 | }; 66 | 67 | var gen_string = function(str, without_length) { /* based on code in (from http://farhadi.ir/downloads/utf8.js) */ 68 | if(arguments.length < 2) without_length = false; 69 | if(typeof str !== "string") return null; 70 | if(typeof without_length !== "boolean") return null; 71 | 72 | var string = []; 73 | var length = 0; 74 | for(var i = 0; i < str.length; i++) { 75 | var code = str.charCodeAt(i); 76 | if (code < 128) { 77 | string.push(code); ++length; 78 | 79 | } else if (code < 2048) { 80 | string.push(192 + ((code >> 6 ) )); ++length; 81 | string.push(128 + ((code ) & 63)); ++length; 82 | } else if (code < 65536) { 83 | string.push(224 + ((code >> 12) )); ++length; 84 | string.push(128 + ((code >> 6 ) & 63)); ++length; 85 | string.push(128 + ((code ) & 63)); ++length; 86 | } else if (code < 2097152) { 87 | string.push(240 + ((code >> 18) )); ++length; 88 | string.push(128 + ((code >> 12) & 63)); ++length; 89 | string.push(128 + ((code >> 6 ) & 63)); ++length; 90 | string.push(128 + ((code ) & 63)); ++length; 91 | } else { 92 | throw new Error("Can't encode character with code " + code); 93 | } 94 | } 95 | return without_length ? string : gen_number(length).concat(string); 96 | }; 97 | 98 | var gen_number = function(num) { 99 | var number = [num >> 8, num & 0x00FF]; 100 | return number; 101 | }; 102 | 103 | var randint = function() { return Math.floor(Math.random() * 0xFFFF); }; -------------------------------------------------------------------------------- /lib/connectors/mqtt/mqttadaptor.js: -------------------------------------------------------------------------------- 1 | var Adaptor = function(opts) { 2 | opts = opts || {}; 3 | this.subReqs = {}; 4 | this.publishRoute = opts.publishRoute; 5 | this.subscribeRoute = opts.subscribeRoute; 6 | }; 7 | 8 | module.exports = Adaptor; 9 | 10 | Adaptor.prototype.onPublish = function(client, packet) { 11 | var route = this.publishRoute; 12 | 13 | if(!route) { 14 | throw new Error('unspecified publish route.'); 15 | } 16 | 17 | var payload = packet.payload; 18 | if(payload instanceof Buffer) { 19 | payload = payload.toString('utf8'); 20 | } 21 | 22 | var req = { 23 | id: packet.messageId, 24 | route: route, 25 | body: packet 26 | }; 27 | 28 | client.emit('message', req); 29 | 30 | if(packet.qos === 1) { 31 | client.socket.puback({messageId: packet.messageId}); 32 | } 33 | }; 34 | 35 | Adaptor.prototype.onSubscribe = function(client, packet) { 36 | var route = this.subscribeRoute; 37 | 38 | if(!route) { 39 | throw new Error('unspecified subscribe route.'); 40 | } 41 | 42 | var req = { 43 | id: packet.messageId, 44 | route: route, 45 | body: { 46 | subscriptions: packet.subscriptions 47 | } 48 | }; 49 | 50 | this.subReqs[packet.messageId] = packet; 51 | 52 | client.emit('message', req); 53 | }; 54 | 55 | Adaptor.prototype.onPubAck = function(client, packet) { 56 | var req = { 57 | id: packet.messageId, 58 | route: 'connector.mqttHandler.pubAck', 59 | body: { 60 | mid: packet.messageId 61 | } 62 | }; 63 | 64 | this.subReqs[packet.messageId] = packet; 65 | 66 | client.emit('message', req); 67 | }; 68 | 69 | /** 70 | * Publish message or subscription ack. 71 | * 72 | * if packet.id exist and this.subReqs[packet.id] exist then packet is a suback. 73 | * Subscription is request/response mode. 74 | * packet.id is pass from client in packet.messageId and record in Pomelo context and attached to the subscribe response packet. 75 | * packet.body is the context that returned by subscribe next callback. 76 | * 77 | * if packet.id not exist then packet is a publish message. 78 | * 79 | * otherwise packet is a illegal packet. 80 | */ 81 | Adaptor.prototype.publish = function(client, packet) { 82 | var mid = packet.id; 83 | var subreq = this.subReqs[mid]; 84 | if(subreq) { 85 | // is suback 86 | client.socket.suback({messageId: mid, granted: packet.body}); 87 | delete this.subReqs[mid]; 88 | return; 89 | } 90 | 91 | client.socket.publish(packet.body); 92 | }; 93 | -------------------------------------------------------------------------------- /lib/connectors/mqtt/protocol.js: -------------------------------------------------------------------------------- 1 | /* Protocol - protocol constants */ 2 | 3 | /* Command code => mnemonic */ 4 | module.exports.types = { 5 | 0: 'reserved', 6 | 1: 'connect', 7 | 2: 'connack', 8 | 3: 'publish', 9 | 4: 'puback', 10 | 5: 'pubrec', 11 | 6: 'pubrel', 12 | 7: 'pubcomp', 13 | 8: 'subscribe', 14 | 9: 'suback', 15 | 10: 'unsubscribe', 16 | 11: 'unsuback', 17 | 12: 'pingreq', 18 | 13: 'pingresp', 19 | 14: 'disconnect', 20 | 15: 'reserved' 21 | }; 22 | 23 | /* Mnemonic => Command code */ 24 | module.exports.codes = {}; 25 | for(var k in module.exports.types) { 26 | var v = module.exports.types[k]; 27 | module.exports.codes[v] = k; 28 | } 29 | 30 | /* Header */ 31 | module.exports.CMD_SHIFT = 4; 32 | module.exports.CMD_MASK = 0xF0; 33 | module.exports.DUP_MASK = 0x08; 34 | module.exports.QOS_MASK = 0x03; 35 | module.exports.QOS_SHIFT = 1; 36 | module.exports.RETAIN_MASK = 0x01; 37 | 38 | /* Length */ 39 | module.exports.LENGTH_MASK = 0x7F; 40 | module.exports.LENGTH_FIN_MASK = 0x80; 41 | 42 | /* Connect */ 43 | module.exports.USERNAME_MASK = 0x80; 44 | module.exports.PASSWORD_MASK = 0x40; 45 | module.exports.WILL_RETAIN_MASK = 0x20; 46 | module.exports.WILL_QOS_MASK = 0x18; 47 | module.exports.WILL_QOS_SHIFT = 3; 48 | module.exports.WILL_FLAG_MASK = 0x04; 49 | module.exports.CLEAN_SESSION_MASK = 0x02; 50 | -------------------------------------------------------------------------------- /lib/connectors/mqttconnector.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var mqtt = require('mqtt'); 4 | var constants = require('../util/constants'); 5 | var MQTTSocket = require('./mqttsocket'); 6 | var Adaptor = require('./mqtt/mqttadaptor'); 7 | var generate = require('./mqtt/generate'); 8 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 9 | 10 | var curId = 1; 11 | /** 12 | * Connector that manager low level connection and protocol bewteen server and client. 13 | * Develper can provide their own connector to switch the low level prototol, such as tcp or probuf. 14 | */ 15 | var Connector = function(port, host, opts) { 16 | if (!(this instanceof Connector)) { 17 | return new Connector(port, host, opts); 18 | } 19 | 20 | EventEmitter.call(this); 21 | this.port = port; 22 | this.host = host; 23 | this.opts = opts || {}; 24 | 25 | this.adaptor = new Adaptor(this.opts); 26 | }; 27 | util.inherits(Connector, EventEmitter); 28 | 29 | module.exports = Connector; 30 | /** 31 | * Start connector to listen the specified port 32 | */ 33 | Connector.prototype.start = function(cb) { 34 | var self = this; 35 | this.mqttServer = mqtt.createServer(); 36 | this.mqttServer.on('client', function(client) { 37 | client.on('error', function(err) { 38 | client.stream.destroy(); 39 | }); 40 | 41 | client.on('close', function() { 42 | client.stream.destroy(); 43 | }); 44 | 45 | client.on('disconnect', function(packet) { 46 | client.stream.destroy(); 47 | }); 48 | 49 | if(self.opts.disconnectOnTimeout) { 50 | var timeout = self.opts.timeout * 1000 || constants.TIME.DEFAULT_MQTT_HEARTBEAT_TIMEOUT; 51 | client.stream.setTimeout(timeout,function() { 52 | client.emit('close'); 53 | }); 54 | } 55 | 56 | client.on('connect', function(packet) { 57 | client.connack({returnCode: 0}); 58 | var mqttsocket = new MQTTSocket(curId++, client, self.adaptor); 59 | self.emit('connection', mqttsocket); 60 | }); 61 | }); 62 | 63 | this.mqttServer.listen(this.port); 64 | 65 | process.nextTick(cb); 66 | }; 67 | 68 | Connector.prototype.stop = function() { 69 | this.mqttServer.close(); 70 | process.exit(0); 71 | }; 72 | 73 | var composeResponse = function(msgId, route, msgBody) { 74 | return { 75 | id: msgId, 76 | body: msgBody 77 | }; 78 | }; 79 | 80 | var composePush = function(route, msgBody) { 81 | var msg = generate.publish(msgBody); 82 | if(!msg) { 83 | logger.error('invalid mqtt publish message: %j', msgBody); 84 | } 85 | 86 | return msg; 87 | }; 88 | 89 | Connector.prototype.encode = function(reqId, route, msgBody) { 90 | if (!!reqId) { 91 | return composeResponse(reqId, route, msgBody); 92 | } else { 93 | return composePush(route, msgBody); 94 | } 95 | }; 96 | 97 | Connector.prototype.close = function() { 98 | this.mqttServer.close(); 99 | }; -------------------------------------------------------------------------------- /lib/connectors/mqttsocket.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var EventEmitter = require('events').EventEmitter; 3 | 4 | var ST_INITED = 1; 5 | var ST_CLOSED = 2; 6 | 7 | /** 8 | * Socket class that wraps socket and websocket to provide unified interface for up level. 9 | */ 10 | var Socket = function(id, socket, adaptor) { 11 | EventEmitter.call(this); 12 | this.id = id; 13 | this.socket = socket; 14 | this.remoteAddress = { 15 | ip: socket.stream.remoteAddress, 16 | port: socket.stream.remotePort 17 | }; 18 | this.adaptor = adaptor; 19 | 20 | var self = this; 21 | 22 | socket.on('close', this.emit.bind(this, 'disconnect')); 23 | socket.on('error', this.emit.bind(this, 'disconnect')); 24 | socket.on('disconnect', this.emit.bind(this, 'disconnect')); 25 | 26 | socket.on('pingreq', function(packet) { 27 | socket.pingresp(); 28 | }); 29 | 30 | socket.on('subscribe', this.adaptor.onSubscribe.bind(this.adaptor, this)); 31 | 32 | socket.on('publish', this.adaptor.onPublish.bind(this.adaptor, this)); 33 | 34 | this.state = ST_INITED; 35 | 36 | // TODO: any other events? 37 | }; 38 | 39 | util.inherits(Socket, EventEmitter); 40 | 41 | module.exports = Socket; 42 | 43 | Socket.prototype.send = function(msg) { 44 | if(this.state !== ST_INITED) { 45 | return; 46 | } 47 | if(msg instanceof Buffer) { 48 | // if encoded, send directly 49 | this.socket.stream.write(msg); 50 | } else { 51 | this.adaptor.publish(this, msg); 52 | } 53 | }; 54 | 55 | Socket.prototype.sendBatch = function(msgs) { 56 | for(var i = 0, l = msgs.length; i 0) { 136 | res <<= 8; 137 | } 138 | res |= str.charCodeAt(offset + i) & 0xff; 139 | } 140 | 141 | return res; 142 | }; -------------------------------------------------------------------------------- /lib/connectors/siosocket.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var EventEmitter = require('events').EventEmitter; 3 | 4 | var ST_INITED = 0; 5 | var ST_CLOSED = 1; 6 | 7 | /** 8 | * Socket class that wraps socket.io socket to provide unified interface for up level. 9 | */ 10 | var Socket = function(id, socket) { 11 | EventEmitter.call(this); 12 | this.id = id; 13 | this.socket = socket; 14 | this.remoteAddress = { 15 | ip: socket.handshake.address.address, 16 | port: socket.handshake.address.port 17 | }; 18 | 19 | var self = this; 20 | 21 | socket.on('disconnect', this.emit.bind(this, 'disconnect')); 22 | 23 | socket.on('error', this.emit.bind(this, 'error')); 24 | 25 | socket.on('message', function(msg) { 26 | self.emit('message', msg); 27 | }); 28 | 29 | this.state = ST_INITED; 30 | 31 | // TODO: any other events? 32 | }; 33 | 34 | util.inherits(Socket, EventEmitter); 35 | 36 | module.exports = Socket; 37 | 38 | Socket.prototype.send = function(msg) { 39 | if(this.state !== ST_INITED) { 40 | return; 41 | } 42 | if(typeof msg !== 'string') { 43 | msg = JSON.stringify(msg); 44 | } 45 | this.socket.send(msg); 46 | }; 47 | 48 | Socket.prototype.disconnect = function() { 49 | if(this.state === ST_CLOSED) { 50 | return; 51 | } 52 | 53 | this.state = ST_CLOSED; 54 | this.socket.disconnect(); 55 | }; 56 | 57 | Socket.prototype.sendBatch = function(msgs) { 58 | this.send(encodeBatch(msgs)); 59 | }; 60 | 61 | /** 62 | * Encode batch msg to client 63 | */ 64 | var encodeBatch = function(msgs){ 65 | var res = '[', msg; 66 | for(var i=0, l=msgs.length; i 0) { 68 | res += ','; 69 | } 70 | msg = msgs[i]; 71 | if(typeof msg === 'string') { 72 | res += msg; 73 | } else { 74 | res += JSON.stringify(msg); 75 | } 76 | } 77 | res += ']'; 78 | return res; 79 | }; 80 | -------------------------------------------------------------------------------- /lib/connectors/udpconnector.js: -------------------------------------------------------------------------------- 1 | var net = require('net'); 2 | var util = require('util'); 3 | var dgram = require("dgram"); 4 | var utils = require('../util/utils'); 5 | var Constants = require('../util/constants'); 6 | var UdpSocket = require('./udpsocket'); 7 | var Kick = require('./commands/kick'); 8 | var Handshake = require('./commands/handshake'); 9 | var Heartbeat = require('./commands/heartbeat'); 10 | var protocol = require('pomelo-protocol'); 11 | var Package = protocol.Package; 12 | var Message = protocol.Message; 13 | var coder = require('./common/coder'); 14 | var EventEmitter = require('events').EventEmitter; 15 | 16 | var curId = 1; 17 | 18 | var Connector = function(port, host, opts) { 19 | if (!(this instanceof Connector)) { 20 | return new Connector(port, host, opts); 21 | } 22 | 23 | EventEmitter.call(this); 24 | this.opts = opts || {}; 25 | this.type = opts.udpType || 'udp4'; 26 | this.handshake = new Handshake(opts); 27 | if(!opts.heartbeat) { 28 | opts.heartbeat = Constants.TIME.DEFAULT_UDP_HEARTBEAT_TIME; 29 | opts.timeout = Constants.TIME.DEFAULT_UDP_HEARTBEAT_TIMEOUT; 30 | } 31 | this.heartbeat = new Heartbeat(utils.extends(opts, {disconnectOnTimeout: true})); 32 | this.clients = {}; 33 | this.host = host; 34 | this.port = port; 35 | }; 36 | 37 | util.inherits(Connector, EventEmitter); 38 | 39 | module.exports = Connector; 40 | 41 | Connector.prototype.start = function(cb) { 42 | var self = this; 43 | this.tcpServer = net.createServer(); 44 | this.socket = dgram.createSocket(this.type, function(msg, peer) { 45 | var key = genKey(peer); 46 | if(!self.clients[key]) { 47 | var udpsocket = new UdpSocket(curId++, self.socket, peer); 48 | self.clients[key] = udpsocket; 49 | 50 | udpsocket.on('handshake', 51 | self.handshake.handle.bind(self.handshake, udpsocket)); 52 | 53 | udpsocket.on('heartbeat', 54 | self.heartbeat.handle.bind(self.heartbeat, udpsocket)); 55 | 56 | udpsocket.on('disconnect', 57 | self.heartbeat.clear.bind(self.heartbeat, udpsocket.id)); 58 | 59 | udpsocket.on('disconnect', function() { 60 | delete self.clients[genKey(udpsocket.peer)]; 61 | }); 62 | 63 | udpsocket.on('closing', Kick.handle.bind(null, udpsocket)); 64 | 65 | self.emit('connection', udpsocket); 66 | } 67 | }); 68 | 69 | this.socket.on('message', function(data, peer) { 70 | var socket = self.clients[genKey(peer)]; 71 | if(!!socket) { 72 | socket.emit('package', data); 73 | } 74 | }); 75 | 76 | this.socket.on('error', function(err) { 77 | logger.error('udp socket encounters with error: %j', err.stack); 78 | return; 79 | }); 80 | 81 | this.socket.bind(this.port, this.host); 82 | this.tcpServer.listen(this.port); 83 | process.nextTick(cb); 84 | }; 85 | 86 | Connector.decode = Connector.prototype.decode = coder.decode; 87 | 88 | Connector.encode = Connector.prototype.encode = coder.encode; 89 | 90 | Connector.prototype.stop = function(force, cb) { 91 | this.socket.close(); 92 | process.nextTick(cb); 93 | }; 94 | 95 | var genKey = function(peer) { 96 | return peer.address + ":" + peer.port; 97 | }; -------------------------------------------------------------------------------- /lib/connectors/udpsocket.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var handler = require('./common/handler'); 3 | var protocol = require('pomelo-protocol'); 4 | var Package = protocol.Package; 5 | var EventEmitter = require('events').EventEmitter; 6 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 7 | 8 | var ST_INITED = 0; 9 | var ST_WAIT_ACK = 1; 10 | var ST_WORKING = 2; 11 | var ST_CLOSED = 3; 12 | 13 | var Socket = function(id, socket, peer) { 14 | EventEmitter.call(this); 15 | 16 | this.id = id; 17 | this.socket = socket; 18 | this.peer = peer; 19 | this.host = peer.address; 20 | this.port = peer.port; 21 | this.remoteAddress = { 22 | ip: this.host, 23 | port: this.port 24 | }; 25 | 26 | var self = this; 27 | this.on('package', function(pkg) { 28 | if(!!pkg) { 29 | pkg = Package.decode(pkg); 30 | handler(self, pkg); 31 | } 32 | }); 33 | 34 | this.state = ST_INITED; 35 | }; 36 | 37 | util.inherits(Socket, EventEmitter); 38 | 39 | module.exports = Socket; 40 | 41 | /** 42 | * Send byte data package to client. 43 | * 44 | * @param {Buffer} msg byte data 45 | */ 46 | Socket.prototype.send = function(msg) { 47 | if(this.state !== ST_WORKING) { 48 | return; 49 | } 50 | if(msg instanceof String) { 51 | msg = new Buffer(msg); 52 | } else if(!(msg instanceof Buffer)) { 53 | msg = new Buffer(JSON.stringify(msg)); 54 | } 55 | this.sendRaw(Package.encode(Package.TYPE_DATA, msg)); 56 | }; 57 | 58 | Socket.prototype.sendRaw = function(msg) { 59 | this.socket.send(msg, 0, msg.length, this.port, this.host, function(err, bytes) { 60 | if(!!err) { 61 | logger.error('send msg to remote with err: %j', err.stack); 62 | return; 63 | } 64 | }); 65 | }; 66 | 67 | Socket.prototype.sendForce = function(msg) { 68 | if(this.state === ST_CLOSED) { 69 | return; 70 | } 71 | this.sendRaw(msg); 72 | }; 73 | 74 | Socket.prototype.handshakeResponse = function(resp) { 75 | if(this.state !== ST_INITED) { 76 | return; 77 | } 78 | this.sendRaw(resp); 79 | this.state = ST_WAIT_ACK; 80 | }; 81 | 82 | Socket.prototype.sendBatch = function(msgs) { 83 | if(this.state !== ST_WORKING) { 84 | return; 85 | } 86 | var rs = []; 87 | for(var i=0; i this.maxSize) { 25 | logger.warn('timeout filter is out of range, current size is %s, max size is %s', count, this.maxSize); 26 | next(); 27 | return; 28 | } 29 | this.curId++; 30 | this.timeouts[this.curId] = setTimeout(function() { 31 | logger.error('request %j timeout.', msg.__route__); 32 | }, this.timeout); 33 | session.__timeout__ = this.curId; 34 | next(); 35 | }; 36 | 37 | Filter.prototype.after = function(err, msg, session, resp, next) { 38 | var timeout = this.timeouts[session.__timeout__]; 39 | if(timeout) { 40 | clearTimeout(timeout); 41 | delete this.timeouts[session.__timeout__]; 42 | } 43 | next(err); 44 | }; 45 | -------------------------------------------------------------------------------- /lib/filters/handler/toobusy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Filter for toobusy. 3 | * if the process is toobusy, just skip the new request 4 | */ 5 | var conLogger = require('pomelo-logger').getLogger('con-log', __filename); 6 | var toobusy = null; 7 | var DEFAULT_MAXLAG = 70; 8 | 9 | 10 | module.exports = function(maxLag) { 11 | return new Filter(maxLag || DEFAULT_MAXLAG); 12 | }; 13 | 14 | var Filter = function(maxLag) { 15 | try { 16 | toobusy = require('toobusy'); 17 | } catch(e) { 18 | } 19 | if(!!toobusy) { 20 | toobusy.maxLag(maxLag); 21 | } 22 | }; 23 | 24 | Filter.prototype.before = function(msg, session, next) { 25 | if (!!toobusy && toobusy()) { 26 | conLogger.warn('[toobusy] reject request msg: ' + msg); 27 | var err = new Error('Server toobusy!'); 28 | err.code = 500; 29 | next(err); 30 | } else { 31 | next(); 32 | } 33 | }; -------------------------------------------------------------------------------- /lib/filters/rpc/rpcLog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Filter for rpc log. 3 | * Record used time for remote process call. 4 | */ 5 | var rpcLogger = require('pomelo-logger').getLogger('rpc-log', __filename); 6 | var utils = require('../../util/utils'); 7 | 8 | module.exports = function() { 9 | return new Filter(); 10 | }; 11 | 12 | var Filter = function () { 13 | }; 14 | 15 | Filter.prototype.name = 'rpcLog'; 16 | 17 | /** 18 | * Before filter for rpc 19 | */ 20 | 21 | Filter.prototype.before = function(serverId, msg, opts, next) { 22 | opts = opts||{}; 23 | opts.__start_time__ = Date.now(); 24 | next(); 25 | }; 26 | 27 | /** 28 | * After filter for rpc 29 | */ 30 | Filter.prototype.after = function(serverId, msg, opts, next) { 31 | if(!!opts && !!opts.__start_time__) { 32 | var start = opts.__start_time__; 33 | var end = Date.now(); 34 | var timeUsed = end - start; 35 | var log = { 36 | route: msg.service, 37 | args: msg.args, 38 | time: utils.format(new Date(start)), 39 | timeUsed: timeUsed 40 | }; 41 | rpcLogger.info(JSON.stringify(log)); 42 | } 43 | next(); 44 | }; 45 | -------------------------------------------------------------------------------- /lib/filters/rpc/toobusy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Filter for rpc log. 3 | * Reject rpc request when toobusy 4 | */ 5 | var rpcLogger = require('pomelo-logger').getLogger('rpc-log', __filename); 6 | var toobusy = null; 7 | 8 | var DEFAULT_MAXLAG = 70; 9 | 10 | module.exports = function(maxLag) { 11 | return new Filter(maxLag || DEFAULT_MAXLAG); 12 | }; 13 | 14 | var Filter = function(maxLag) { 15 | try { 16 | toobusy = require('toobusy'); 17 | } catch(e) { 18 | } 19 | if(!!toobusy) { 20 | toobusy.maxLag(maxLag); 21 | } 22 | }; 23 | 24 | Filter.prototype.name = 'toobusy'; 25 | 26 | /** 27 | * Before filter for rpc 28 | */ 29 | Filter.prototype.before = function(serverId, msg, opts, next) { 30 | opts = opts||{}; 31 | if (!!toobusy && toobusy()) { 32 | rpcLogger.warn('Server too busy for rpc request, serverId:' + serverId + ' msg: ' + msg); 33 | var err = new Error('Backend server ' + serverId + ' is too busy now!'); 34 | err.code = 500; 35 | next(err); 36 | } else { 37 | next(); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./pomelo'); -------------------------------------------------------------------------------- /lib/master/master.js: -------------------------------------------------------------------------------- 1 | var starter = require('./starter'); 2 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 3 | var crashLogger = require('pomelo-logger').getLogger('crash-log', __filename); 4 | var adminLogger = require('pomelo-logger').getLogger('admin-log', __filename); 5 | var admin = require('pomelo-admin'); 6 | var util = require('util'); 7 | var utils = require('../util/utils'); 8 | var moduleUtil = require('../util/moduleUtil'); 9 | var Constants = require('../util/constants'); 10 | 11 | var Server = function(app, opts) { 12 | this.app = app; 13 | this.masterInfo = app.getMaster(); 14 | this.registered = {}; 15 | this.modules = []; 16 | opts = opts || {}; 17 | 18 | opts.port = this.masterInfo.port; 19 | opts.env = this.app.get(Constants.RESERVED.ENV); 20 | this.closeWatcher = opts.closeWatcher; 21 | this.masterConsole = admin.createMasterConsole(opts); 22 | }; 23 | 24 | module.exports = Server; 25 | 26 | Server.prototype.start = function(cb) { 27 | moduleUtil.registerDefaultModules(true, this.app, this.closeWatcher); 28 | moduleUtil.loadModules(this, this.masterConsole); 29 | 30 | var self = this; 31 | // start master console 32 | this.masterConsole.start(function(err) { 33 | if(err) { 34 | process.exit(0); 35 | } 36 | moduleUtil.startModules(self.modules, function(err) { 37 | if(err) { 38 | utils.invokeCallback(cb, err); 39 | return; 40 | } 41 | 42 | if(self.app.get(Constants.RESERVED.MODE) !== Constants.RESERVED.STAND_ALONE) { 43 | starter.runServers(self.app); 44 | } 45 | utils.invokeCallback(cb); 46 | }); 47 | }); 48 | 49 | this.masterConsole.on('error', function(err) { 50 | if(!!err) { 51 | logger.error('masterConsole encounters with error: ' + err.stack); 52 | return; 53 | } 54 | }); 55 | 56 | this.masterConsole.on('reconnect', function(info){ 57 | self.app.addServers([info]); 58 | }); 59 | 60 | // monitor servers disconnect event 61 | this.masterConsole.on('disconnect', function(id, type, info, reason) { 62 | crashLogger.info(util.format('[%s],[%s],[%s],[%s]', type, id, Date.now(), reason || 'disconnect')); 63 | var count = 0; 64 | var time = 0; 65 | var pingTimer = null; 66 | var server = self.app.getServerById(id); 67 | var stopFlags = self.app.get(Constants.RESERVED.STOP_SERVERS) || []; 68 | if(!!server && (server[Constants.RESERVED.AUTO_RESTART] === 'true' || server[Constants.RESERVED.RESTART_FORCE] === 'true') && stopFlags.indexOf(id) < 0) { 69 | var setTimer = function(time) { 70 | pingTimer = setTimeout(function() { 71 | utils.ping(server.host, function(flag) { 72 | if(flag) { 73 | handle(); 74 | } else { 75 | count++; 76 | if(count > 3) { 77 | time = Constants.TIME.TIME_WAIT_MAX_PING; 78 | } else { 79 | time = Constants.TIME.TIME_WAIT_PING * count; 80 | } 81 | setTimer(time); 82 | } 83 | }); 84 | }, time); 85 | }; 86 | setTimer(time); 87 | var handle = function() { 88 | clearTimeout(pingTimer); 89 | utils.checkPort(server, function(status) { 90 | if(status === 'error') { 91 | utils.invokeCallback(cb, new Error('Check port command executed with error.')); 92 | return; 93 | } else if(status === 'busy') { 94 | if(!!server[Constants.RESERVED.RESTART_FORCE]) { 95 | starter.kill([info.pid], [server]); 96 | } else { 97 | utils.invokeCallback(cb, new Error('Port occupied already, check your server to add.')); 98 | return; 99 | } 100 | } 101 | setTimeout(function() { 102 | starter.run(self.app, server, null); 103 | }, Constants.TIME.TIME_WAIT_STOP); 104 | }); 105 | }; 106 | } 107 | }); 108 | 109 | // monitor servers register event 110 | this.masterConsole.on('register', function(record) { 111 | starter.bindCpu(record.id, record.pid, record.host); 112 | }); 113 | 114 | this.masterConsole.on('admin-log', function(log, error) { 115 | if(error) { 116 | adminLogger.error(JSON.stringify(log)); 117 | } else { 118 | adminLogger.info(JSON.stringify(log)); 119 | } 120 | }); 121 | }; 122 | 123 | Server.prototype.stop = function(cb) { 124 | this.masterConsole.stop(); 125 | process.nextTick(cb); 126 | }; 127 | -------------------------------------------------------------------------------- /lib/master/watchdog.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 2 | var utils = require('../util/utils'); 3 | var Constants = require('../util/constants'); 4 | var countDownLatch = require('../util/countDownLatch'); 5 | var EventEmitter = require('events').EventEmitter; 6 | var util = require('util'); 7 | 8 | var Watchdog = function(app, service) { 9 | EventEmitter.call(this); 10 | 11 | this.app = app; 12 | this.service = service; 13 | this.isStarted = false; 14 | this.count = utils.size(app.getServersFromConfig()); 15 | 16 | this.servers = {}; 17 | this.listeners = {}; 18 | }; 19 | util.inherits(Watchdog, EventEmitter); 20 | 21 | module.exports = Watchdog; 22 | 23 | Watchdog.prototype.addServer = function(server) { 24 | if(!server) { 25 | return; 26 | } 27 | this.servers[server.id] = server; 28 | this.notify({action: 'addServer', server: server}); 29 | }; 30 | 31 | Watchdog.prototype.removeServer = function(id) { 32 | if(!id) { 33 | return; 34 | } 35 | this.unsubscribe(id); 36 | delete this.servers[id]; 37 | this.notify({action: 'removeServer', id: id}); 38 | }; 39 | 40 | Watchdog.prototype.reconnectServer = function(server) { 41 | var self = this; 42 | if(!server) { 43 | return; 44 | } 45 | if(!this.servers[server.id]) { 46 | this.servers[server.id] = server; 47 | } 48 | //replace server in reconnect server 49 | this.notifyById(server.id, {action: 'replaceServer', servers: self.servers}); 50 | // notify other server to add server 51 | this.notify({action: 'addServer', server: server}); 52 | // add server in listener 53 | this.subscribe(server.id); 54 | }; 55 | 56 | Watchdog.prototype.subscribe = function(id) { 57 | this.listeners[id] = 1; 58 | }; 59 | 60 | Watchdog.prototype.unsubscribe = function(id) { 61 | delete this.listeners[id]; 62 | }; 63 | 64 | Watchdog.prototype.query = function() { 65 | return this.servers; 66 | }; 67 | 68 | Watchdog.prototype.record = function(id) { 69 | if(!this.isStarted && --this.count < 0) { 70 | var usedTime = Date.now() - this.app.startTime; 71 | logger.info('all servers startup in %s ms', usedTime); 72 | this.notify({action: 'startOver'}); 73 | this.isStarted = true; 74 | } 75 | }; 76 | 77 | Watchdog.prototype.notifyById = function(id, msg) { 78 | this.service.agent.request(id, Constants.KEYWORDS.MONITOR_WATCHER, msg, function(signal) { 79 | if(signal !== Constants.SIGNAL.OK) { 80 | logger.error('master watchdog fail to notify to monitor, id: %s, msg: %j', id, msg); 81 | } else { 82 | logger.debug('master watchdog notify to monitor success, id: %s, msg: %j', id, msg); 83 | } 84 | }); 85 | }; 86 | 87 | Watchdog.prototype.notify = function(msg) { 88 | var listeners = this.listeners; 89 | var success = true; 90 | var fails = []; 91 | var timeouts = []; 92 | var requests = {}; 93 | var count = utils.size(listeners); 94 | if(count === 0) { 95 | logger.warn('master watchdog listeners is none, msg: %j', msg); 96 | return; 97 | } 98 | var latch = countDownLatch.createCountDownLatch(count, {timeout: Constants.TIME.TIME_WAIT_COUNTDOWN}, function(isTimeout) { 99 | if(!!isTimeout) { 100 | for(var key in requests) { 101 | if(!requests[key]) { 102 | timeouts.push(key); 103 | } 104 | } 105 | logger.error('master watchdog request timeout message: %j, timeouts: %j, fails: %j', msg, timeouts, fails); 106 | } 107 | if(!success) { 108 | logger.error('master watchdog request fail message: %j, fails: %j', msg, fails); 109 | } 110 | }); 111 | 112 | var moduleRequest = function(self, id) { 113 | return (function() { 114 | self.service.agent.request(id, Constants.KEYWORDS.MONITOR_WATCHER, msg, function(signal) { 115 | if(signal !== Constants.SIGNAL.OK) { 116 | fails.push(id); 117 | success = false; 118 | } 119 | requests[id] = 1; 120 | latch.done(); 121 | }); 122 | })(); 123 | }; 124 | 125 | for(var id in listeners) { 126 | requests[id] = 0; 127 | moduleRequest(this, id); 128 | } 129 | }; -------------------------------------------------------------------------------- /lib/modules/masterwatcher.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 2 | var utils = require('../util/utils'); 3 | var Constants = require('../util/constants'); 4 | var MasterWatchdog = require('../master/watchdog'); 5 | 6 | module.exports = function(opts, consoleService) { 7 | return new Module(opts, consoleService); 8 | }; 9 | 10 | module.exports.moduleId = Constants.KEYWORDS.MASTER_WATCHER; 11 | 12 | var Module = function(opts, consoleService) { 13 | this.app = opts.app; 14 | this.service = consoleService; 15 | this.id = this.app.getServerId(); 16 | 17 | this.watchdog = new MasterWatchdog(this.app, this.service); 18 | this.service.on('register', onServerAdd.bind(null, this)); 19 | this.service.on('disconnect', onServerLeave.bind(null, this)); 20 | this.service.on('reconnect', onServerReconnect.bind(null, this)); 21 | }; 22 | 23 | // ----------------- bind methods ------------------------- 24 | 25 | var onServerAdd = function(module, record) { 26 | logger.debug('masterwatcher receive add server event, with server: %j', record); 27 | if(!record || record.type === 'client' || !record.serverType) { 28 | return; 29 | } 30 | module.watchdog.addServer(record); 31 | }; 32 | 33 | var onServerReconnect = function(module, record) { 34 | logger.debug('masterwatcher receive reconnect server event, with server: %j', record); 35 | if(!record || record.type === 'client' || !record.serverType) { 36 | logger.warn('onServerReconnect receive wrong message: %j', record); 37 | return; 38 | } 39 | module.watchdog.reconnectServer(record); 40 | }; 41 | 42 | var onServerLeave = function(module, id, type) { 43 | logger.debug('masterwatcher receive remove server event, with server: %s, type: %s', id, type); 44 | if(!id) { 45 | logger.warn('onServerLeave receive server id is empty.'); 46 | return; 47 | } 48 | if(type !== 'client') { 49 | module.watchdog.removeServer(id); 50 | } 51 | }; 52 | 53 | // ----------------- module methods ------------------------- 54 | 55 | Module.prototype.start = function(cb) { 56 | utils.invokeCallback(cb); 57 | }; 58 | 59 | Module.prototype.masterHandler = function(agent, msg, cb) { 60 | if(!msg) { 61 | logger.warn('masterwatcher receive empty message.'); 62 | return; 63 | } 64 | var func = masterMethods[msg.action]; 65 | if(!func) { 66 | logger.info('masterwatcher unknown action: %j', msg.action); 67 | return; 68 | } 69 | func(this, agent, msg, cb); 70 | }; 71 | 72 | // ----------------- monitor request methods ------------------------- 73 | 74 | var subscribe = function(module, agent, msg, cb) { 75 | if(!msg) { 76 | utils.invokeCallback(cb, new Error('masterwatcher subscribe empty message.')); 77 | return; 78 | } 79 | 80 | module.watchdog.subscribe(msg.id); 81 | utils.invokeCallback(cb, null, module.watchdog.query()); 82 | }; 83 | 84 | var unsubscribe = function(module, agent, msg, cb) { 85 | if(!msg) { 86 | utils.invokeCallback(cb, new Error('masterwatcher unsubscribe empty message.')); 87 | return; 88 | } 89 | module.watchdog.unsubscribe(msg.id); 90 | utils.invokeCallback(cb); 91 | }; 92 | 93 | var query = function(module, agent, msg, cb) { 94 | utils.invokeCallback(cb, null, module.watchdog.query()); 95 | }; 96 | 97 | var record = function(module, agent, msg) { 98 | if(!msg) { 99 | utils.invokeCallback(cb, new Error('masterwatcher record empty message.')); 100 | return; 101 | } 102 | module.watchdog.record(msg.id); 103 | }; 104 | 105 | var masterMethods = { 106 | 'subscribe': subscribe, 107 | 'unsubscribe': unsubscribe, 108 | 'query': query, 109 | 'record': record 110 | }; -------------------------------------------------------------------------------- /lib/modules/monitorwatcher.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 2 | var utils = require('../util/utils'); 3 | var events = require('../util/events'); 4 | var Constants = require('../util/constants'); 5 | var util = require('util'); 6 | 7 | module.exports = function(opts, consoleService) { 8 | return new Module(opts, consoleService); 9 | }; 10 | 11 | module.exports.moduleId = Constants.KEYWORDS.MONITOR_WATCHER; 12 | 13 | var Module = function(opts, consoleService) { 14 | this.app = opts.app; 15 | this.service = consoleService; 16 | this.id = this.app.getServerId(); 17 | 18 | this.app.event.on(events.START_SERVER, finishStart.bind(null, this)); 19 | }; 20 | 21 | Module.prototype.start = function(cb) { 22 | subscribeRequest(this, this.service.agent, this.id, cb); 23 | }; 24 | 25 | Module.prototype.monitorHandler = function(agent, msg, cb) { 26 | if(!msg || !msg.action) { 27 | return; 28 | } 29 | var func = monitorMethods[msg.action]; 30 | if(!func) { 31 | logger.info('monitorwatcher unknown action: %j', msg.action); 32 | return; 33 | } 34 | func(this, agent, msg, cb); 35 | }; 36 | 37 | // ----------------- monitor start method ------------------------- 38 | 39 | var subscribeRequest = function(self, agent, id, cb) { 40 | var msg = {action: 'subscribe', id: id}; 41 | agent.request(Constants.KEYWORDS.MASTER_WATCHER, msg, function(err, servers) { 42 | if(err) { 43 | logger.error('subscribeRequest request to master with error: %j', err.stack); 44 | utils.invokeCallback(cb, err); 45 | } 46 | var res = []; 47 | for(var id in servers) { 48 | res.push(servers[id]); 49 | } 50 | addServers(self, res); 51 | utils.invokeCallback(cb); 52 | }); 53 | }; 54 | 55 | // ----------------- monitor request methods ------------------------- 56 | 57 | var addServer = function(self, agent, msg, cb) { 58 | logger.debug('[%s] receive addServer signal: %j', self.app.serverId, msg); 59 | if(!msg || !msg.server) { 60 | logger.warn('monitorwatcher addServer receive empty message: %j', msg); 61 | utils.invokeCallback(cb, Constants.SIGNAL.FAIL); 62 | return; 63 | } 64 | addServers(self, [msg.server]); 65 | utils.invokeCallback(cb, Constants.SIGNAL.OK); 66 | }; 67 | 68 | var removeServer = function(self, agent, msg, cb) { 69 | logger.debug('%s receive removeServer signal: %j', self.app.serverId, msg); 70 | if(!msg || !msg.id) { 71 | logger.warn('monitorwatcher removeServer receive empty message: %j', msg); 72 | utils.invokeCallback(cb, Constants.SIGNAL.FAIL); 73 | return; 74 | } 75 | removeServers(self, [msg.id]); 76 | utils.invokeCallback(cb, Constants.SIGNAL.OK); 77 | }; 78 | 79 | var replaceServer = function(self, agent, msg, cb) { 80 | logger.debug('%s receive replaceServer signal: %j', self.app.serverId, msg); 81 | if(!msg || !msg.servers) { 82 | logger.warn('monitorwatcher replaceServer receive empty message: %j', msg); 83 | utils.invokeCallback(cb, Constants.SIGNAL.FAIL); 84 | return; 85 | } 86 | replaceServers(self, msg.servers); 87 | utils.invokeCallback(cb, Constants.SIGNAL.OK); 88 | }; 89 | 90 | var startOver = function(self, agent, msg, cb) { 91 | var fun = self.app.lifecycleCbs[Constants.LIFECYCLE.AFTER_STARTALL]; 92 | if(!!fun) { 93 | fun.call(null, self.app); 94 | } 95 | self.app.event.emit(events.START_ALL); 96 | utils.invokeCallback(cb, Constants.SIGNAL.OK); 97 | }; 98 | 99 | // ----------------- common methods ------------------------- 100 | 101 | var addServers = function(self, servers) { 102 | if(!servers || !servers.length) { 103 | return; 104 | } 105 | self.app.addServers(servers); 106 | }; 107 | 108 | var removeServers = function(self, ids) { 109 | if(!ids || !ids.length) { 110 | return; 111 | } 112 | self.app.removeServers(ids); 113 | }; 114 | 115 | var replaceServers = function(self, servers) { 116 | self.app.replaceServers(servers); 117 | }; 118 | 119 | // ----------------- bind methods ------------------------- 120 | 121 | var finishStart = function(self, id) { 122 | var msg = {action: 'record', id: id}; 123 | self.service.agent.notify(Constants.KEYWORDS.MASTER_WATCHER, msg); 124 | }; 125 | 126 | var monitorMethods = { 127 | 'addServer': addServer, 128 | 'removeServer': removeServer, 129 | 'replaceServer': replaceServer, 130 | 'startOver': startOver 131 | }; 132 | -------------------------------------------------------------------------------- /lib/monitor/monitor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Component for monitor. 3 | * Load and start monitor client. 4 | */ 5 | var logger = require('pomelo-logger').getLogger('pomelo', __filename); 6 | var admin = require('pomelo-admin'); 7 | var moduleUtil = require('../util/moduleUtil'); 8 | var utils = require('../util/utils'); 9 | var Constants = require('../util/constants'); 10 | 11 | var Monitor = function(app, opts) { 12 | opts = opts || {}; 13 | this.app = app; 14 | this.serverInfo = app.getCurServer(); 15 | this.masterInfo = app.getMaster(); 16 | this.modules = []; 17 | this.closeWatcher = opts.closeWatcher; 18 | 19 | this.monitorConsole = admin.createMonitorConsole({ 20 | id: this.serverInfo.id, 21 | type: this.app.getServerType(), 22 | host: this.masterInfo.host, 23 | port: this.masterInfo.port, 24 | info: this.serverInfo, 25 | env: this.app.get(Constants.RESERVED.ENV), 26 | authServer: app.get('adminAuthServerMonitor') // auth server function 27 | }); 28 | }; 29 | 30 | module.exports = Monitor; 31 | 32 | Monitor.prototype.start = function(cb) { 33 | moduleUtil.registerDefaultModules(false, this.app, this.closeWatcher); 34 | this.startConsole(cb); 35 | }; 36 | 37 | Monitor.prototype.startConsole = function(cb) { 38 | moduleUtil.loadModules(this, this.monitorConsole); 39 | 40 | var self = this; 41 | this.monitorConsole.start(function(err) { 42 | if (err) { 43 | utils.invokeCallback(cb, err); 44 | return; 45 | } 46 | moduleUtil.startModules(self.modules, function(err) { 47 | utils.invokeCallback(cb, err); 48 | return; 49 | }); 50 | }); 51 | 52 | this.monitorConsole.on('error', function(err) { 53 | if(!!err) { 54 | logger.error('monitorConsole encounters with error: %j', err.stack); 55 | return; 56 | } 57 | }); 58 | }; 59 | 60 | Monitor.prototype.stop = function(cb) { 61 | this.monitorConsole.stop(); 62 | this.modules = []; 63 | process.nextTick(function() { 64 | utils.invokeCallback(cb); 65 | }); 66 | }; 67 | 68 | // monitor reconnect to master 69 | Monitor.prototype.reconnect = function(masterInfo) { 70 | var self = this; 71 | this.stop(function() { 72 | self.monitorConsole = admin.createMonitorConsole({ 73 | id: self.serverInfo.id, 74 | type: self.app.getServerType(), 75 | host: masterInfo.host, 76 | port: masterInfo.port, 77 | info: self.serverInfo, 78 | env: self.app.get(Constants.RESERVED.ENV) 79 | }); 80 | self.startConsole(function() { 81 | logger.info('restart modules for server : %j finish.', self.app.serverId); 82 | }); 83 | }); 84 | }; -------------------------------------------------------------------------------- /lib/pomelo.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Pomelo 3 | * Copyright(c) 2012 xiechengchao 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies. 9 | */ 10 | var fs = require('fs'); 11 | var path = require('path'); 12 | var application = require('./application'); 13 | var Package = require('../package'); 14 | 15 | /** 16 | * Expose `createApplication()`. 17 | * 18 | * @module 19 | */ 20 | 21 | var Pomelo = module.exports = {}; 22 | 23 | /** 24 | * Framework version. 25 | */ 26 | 27 | Pomelo.version = Package.version; 28 | 29 | /** 30 | * Event definitions that would be emitted by app.event 31 | */ 32 | Pomelo.events = require('./util/events'); 33 | 34 | /** 35 | * auto loaded components 36 | */ 37 | Pomelo.components = {}; 38 | 39 | /** 40 | * auto loaded filters 41 | */ 42 | Pomelo.filters = {}; 43 | 44 | /** 45 | * auto loaded rpc filters 46 | */ 47 | Pomelo.rpcFilters = {}; 48 | 49 | /** 50 | * connectors 51 | */ 52 | Pomelo.connectors = {}; 53 | Pomelo.connectors.__defineGetter__('sioconnector', load.bind(null, './connectors/sioconnector')); 54 | Pomelo.connectors.__defineGetter__('hybridconnector', load.bind(null, './connectors/hybridconnector')); 55 | Pomelo.connectors.__defineGetter__('udpconnector', load.bind(null, './connectors/udpconnector')); 56 | Pomelo.connectors.__defineGetter__('mqttconnector', load.bind(null, './connectors/mqttconnector')); 57 | 58 | /** 59 | * pushSchedulers 60 | */ 61 | Pomelo.pushSchedulers = {}; 62 | Pomelo.pushSchedulers.__defineGetter__('direct', load.bind(null, './pushSchedulers/direct')); 63 | Pomelo.pushSchedulers.__defineGetter__('buffer', load.bind(null, './pushSchedulers/buffer')); 64 | 65 | var self = this; 66 | 67 | /** 68 | * Create an pomelo application. 69 | * 70 | * @return {Application} 71 | * @memberOf Pomelo 72 | * @api public 73 | */ 74 | Pomelo.createApp = function (opts) { 75 | var app = application; 76 | app.init(opts); 77 | self.app = app; 78 | return app; 79 | }; 80 | 81 | /** 82 | * Get application 83 | */ 84 | Object.defineProperty(Pomelo, 'app', { 85 | get:function () { 86 | return self.app; 87 | } 88 | }); 89 | 90 | /** 91 | * Auto-load bundled components with getters. 92 | */ 93 | fs.readdirSync(__dirname + '/components').forEach(function (filename) { 94 | if (!/\.js$/.test(filename)) { 95 | return; 96 | } 97 | var name = path.basename(filename, '.js'); 98 | var _load = load.bind(null, './components/', name); 99 | 100 | Pomelo.components.__defineGetter__(name, _load); 101 | Pomelo.__defineGetter__(name, _load); 102 | }); 103 | 104 | fs.readdirSync(__dirname + '/filters/handler').forEach(function (filename) { 105 | if (!/\.js$/.test(filename)) { 106 | return; 107 | } 108 | var name = path.basename(filename, '.js'); 109 | var _load = load.bind(null, './filters/handler/', name); 110 | 111 | Pomelo.filters.__defineGetter__(name, _load); 112 | Pomelo.__defineGetter__(name, _load); 113 | }); 114 | 115 | fs.readdirSync(__dirname + '/filters/rpc').forEach(function (filename) { 116 | if (!/\.js$/.test(filename)) { 117 | return; 118 | } 119 | var name = path.basename(filename, '.js'); 120 | var _load = load.bind(null, './filters/rpc/', name); 121 | 122 | Pomelo.rpcFilters.__defineGetter__(name, _load); 123 | }); 124 | 125 | function load(path, name) { 126 | if (name) { 127 | return require(path + name); 128 | } 129 | return require(path); 130 | } 131 | -------------------------------------------------------------------------------- /lib/pushSchedulers/buffer.js: -------------------------------------------------------------------------------- 1 | var utils = require('../util/utils'); 2 | var DEFAULT_FLUSH_INTERVAL = 20; 3 | 4 | var Service = function(app, opts) { 5 | if (!(this instanceof Service)) { 6 | return new Service(app, opts); 7 | } 8 | 9 | opts = opts || {}; 10 | this.app = app; 11 | this.flushInterval = opts.flushInterval || DEFAULT_FLUSH_INTERVAL; 12 | this.sessions = {}; // sid -> msg queue 13 | this.tid = null; 14 | }; 15 | 16 | module.exports = Service; 17 | 18 | Service.prototype.start = function(cb) { 19 | this.tid = setInterval(flush.bind(null, this), this.flushInterval); 20 | process.nextTick(function() { 21 | utils.invokeCallback(cb); 22 | }); 23 | }; 24 | 25 | Service.prototype.stop = function(force, cb) { 26 | if(this.tid) { 27 | clearInterval(this.tid); 28 | this.tid = null; 29 | } 30 | process.nextTick(function() { 31 | utils.invokeCallback(cb); 32 | }); 33 | }; 34 | 35 | Service.prototype.schedule = function(reqId, route, msg, recvs, opts, cb) { 36 | opts = opts || {}; 37 | if(opts.type === 'broadcast') { 38 | doBroadcast(this, msg, opts.userOptions); 39 | } else { 40 | doBatchPush(this, msg, recvs); 41 | } 42 | 43 | process.nextTick(function() { 44 | utils.invokeCallback(cb); 45 | }); 46 | }; 47 | 48 | var doBroadcast = function(self, msg, opts) { 49 | var channelService = self.app.get('channelService'); 50 | var sessionService = self.app.get('sessionService'); 51 | 52 | if(opts.binded) { 53 | sessionService.forEachBindedSession(function(session) { 54 | if(channelService.broadcastFilter && 55 | !channelService.broadcastFilter(session, msg, opts.filterParam)) { 56 | return; 57 | } 58 | 59 | enqueue(self, session, msg); 60 | }); 61 | } else { 62 | sessionService.forEachSession(function(session) { 63 | if(channelService.broadcastFilter && 64 | !channelService.broadcastFilter(session, msg, opts.filterParam)) { 65 | return; 66 | } 67 | 68 | enqueue(self, session, msg); 69 | }); 70 | } 71 | }; 72 | 73 | var doBatchPush = function(self, msg, recvs) { 74 | var sessionService = self.app.get('sessionService'); 75 | var session; 76 | for(var i=0, l=recvs.length; i= modules.length) { 84 | utils.invokeCallback(cb, err); 85 | return; 86 | } 87 | 88 | var module = modules[index]; 89 | if(module && typeof module.start === 'function') { 90 | module.start(function(err) { 91 | startModule(err, modules, index + 1, cb); 92 | }); 93 | } else { 94 | startModule(err, modules, index + 1, cb); 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /lib/util/pathUtil.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var Constants = require('./constants'); 4 | var exp = module.exports; 5 | 6 | /** 7 | * Get system remote service path 8 | * 9 | * @param {String} role server role: frontend, backend 10 | * @return {String} path string if the path exist else null 11 | */ 12 | exp.getSysRemotePath = function(role) { 13 | var p = path.join(__dirname, '/../common/remote/', role); 14 | return fs.existsSync(p) ? p : null; 15 | }; 16 | 17 | /** 18 | * Get user remote service path 19 | * 20 | * @param {String} appBase application base path 21 | * @param {String} serverType server type 22 | * @return {String} path string if the path exist else null 23 | */ 24 | exp.getUserRemotePath = function(appBase, serverType) { 25 | var p = path.join(appBase, '/app/servers/', serverType, Constants.DIR.REMOTE); 26 | return fs.existsSync(p) ? p : null; 27 | }; 28 | 29 | /** 30 | * Get user remote cron path 31 | * 32 | * @param {String} appBase application base path 33 | * @param {String} serverType server type 34 | * @return {String} path string if the path exist else null 35 | */ 36 | exp.getCronPath = function(appBase, serverType) { 37 | var p = path.join(appBase, '/app/servers/', serverType, Constants.DIR.CRON); 38 | return fs.existsSync(p) ? p : null; 39 | }; 40 | 41 | /** 42 | * List all the subdirectory names of user remote directory 43 | * which hold the codes for all the server types. 44 | * 45 | * @param {String} appBase application base path 46 | * @return {Array} all the subdiretory name under servers/ 47 | */ 48 | exp.listUserRemoteDir = function(appBase) { 49 | var base = path.join(appBase, '/app/servers/'); 50 | var files = fs.readdirSync(base); 51 | return files.filter(function(fn) { 52 | if(fn.charAt(0) === '.') { 53 | return false; 54 | } 55 | 56 | return fs.statSync(path.join(base, fn)).isDirectory(); 57 | }); 58 | }; 59 | 60 | /** 61 | * Compose remote path record 62 | * 63 | * @param {String} namespace remote path namespace, such as: 'sys', 'user' 64 | * @param {String} serverType 65 | * @param {String} path remote service source path 66 | * @return {Object} remote path record 67 | */ 68 | exp.remotePathRecord = function(namespace, serverType, path) { 69 | return {namespace: namespace, serverType: serverType, path: path}; 70 | }; 71 | 72 | /** 73 | * Get handler path 74 | * 75 | * @param {String} appBase application base path 76 | * @param {String} serverType server type 77 | * @return {String} path string if the path exist else null 78 | */ 79 | exp.getHandlerPath = function(appBase, serverType) { 80 | var p = path.join(appBase, '/app/servers/', serverType, Constants.DIR.HANDLER); 81 | return fs.existsSync(p) ? p : null; 82 | }; 83 | 84 | /** 85 | * Get admin script root path. 86 | * 87 | * @param {String} appBase application base path 88 | * @return {String} script path string 89 | */ 90 | exp.getScriptPath = function(appBase) { 91 | return path.join(appBase, Constants.DIR.SCRIPT); 92 | }; 93 | 94 | /** 95 | * Get logs path. 96 | * 97 | * @param {String} appBase application base path 98 | * @return {String} logs path string 99 | */ 100 | exp.getLogPath = function(appBase) { 101 | return path.join(appBase, Constants.DIR.LOG); 102 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pomelo", 3 | "version": "2.2.7", 4 | "homepage": "https://github.com/NetEase/pomelo", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/NetEase/pomelo.git" 8 | }, 9 | "scripts": { 10 | "test": "grunt" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/NetEase/pomelo/issues" 14 | }, 15 | "licenses": [{ 16 | "type": "MIT", 17 | "url": "https://github.com/NetEase/pomelo#license" 18 | }], 19 | "keywords": [ 20 | "pomelo", 21 | "framework", 22 | "game", 23 | "web", 24 | "realtime", 25 | "server" 26 | ], 27 | "dependencies": { 28 | "socket.io": "1.7.2", 29 | "async": "0.2.5", 30 | "seq-queue": "0.0.5", 31 | "crc": "0.2.0", 32 | "cliff": "0.1.8", 33 | "mkdirp": "0.3.3", 34 | "pomelo-loader": "0.0.6", 35 | "pomelo-rpc": "1.0.7", 36 | "pomelo-protocol": "0.1.6", 37 | "pomelo-admin": "1.0.1", 38 | "pomelo-logger": "0.1.7", 39 | "pomelo-scheduler": "0.3.9", 40 | "ws": "1.1.1", 41 | "pomelo-protobuf": "0.4.0", 42 | "node-bignumber": "1.2.1", 43 | "commander": "2.0.0", 44 | "mqtt": "0.3.9" 45 | }, 46 | "bin": { 47 | "pomelo": "./bin/pomelo" 48 | }, 49 | "devDependencies": { 50 | "should": "3.3.1", 51 | "mocha": ">=0.0.1", 52 | "muk": ">=0.0.1", 53 | "grunt": "~0.4.2", 54 | "grunt-mocha-test": "0.8.x", 55 | "grunt-contrib-clean": "0.5.x", 56 | "grunt-contrib-jshint": "~0.8.0", 57 | "blanket": "~1.1.6" 58 | } 59 | } -------------------------------------------------------------------------------- /template/game-server/app.js: -------------------------------------------------------------------------------- 1 | var pomelo = require('pomelo'); 2 | 3 | /** 4 | * Init app for client. 5 | */ 6 | var app = pomelo.createApp(); 7 | app.set('name', '$'); 8 | 9 | // app configuration 10 | app.configure('production|development', 'connector', function(){ 11 | app.set('connectorConfig', 12 | { 13 | connector : pomelo.connectors.hybridconnector, 14 | heartbeat : 3, 15 | useDict : true, 16 | useProtobuf : true 17 | }); 18 | }); 19 | 20 | // start app 21 | app.start(); 22 | 23 | process.on('uncaughtException', function (err) { 24 | console.error(' Caught exception: ' + err.stack); 25 | }); 26 | -------------------------------------------------------------------------------- /template/game-server/app.js.mqtt: -------------------------------------------------------------------------------- 1 | var pomelo = require('pomelo'); 2 | 3 | /** 4 | * Init app for client. 5 | */ 6 | var app = pomelo.createApp(); 7 | app.set('name', '$'); 8 | 9 | // app configuration 10 | app.configure('production|development', 'connector', function(){ 11 | app.set('connectorConfig', 12 | { 13 | connector : pomelo.connectors.mqttconnector, 14 | publishRoute: 'connector.entryHandler.publish', 15 | subscribeRoute: 'connector.entryHandler.subscribe' 16 | }); 17 | }); 18 | 19 | // start app 20 | app.start(); 21 | 22 | process.on('uncaughtException', function (err) { 23 | console.error(' Caught exception: ' + err.stack); 24 | }); -------------------------------------------------------------------------------- /template/game-server/app.js.sio: -------------------------------------------------------------------------------- 1 | var pomelo = require('pomelo'); 2 | 3 | /** 4 | * Init app for client. 5 | */ 6 | var app = pomelo.createApp(); 7 | app.set('name', '$'); 8 | 9 | // app configuration 10 | app.configure('production|development', 'connector', function(){ 11 | app.set('connectorConfig', 12 | { 13 | connector : pomelo.connectors.sioconnector, 14 | // 'websocket', 'polling-xhr', 'polling-jsonp', 'polling' 15 | transports : ['websocket', 'polling'], 16 | heartbeats : true, 17 | closeTimeout : 60 * 1000, 18 | heartbeatTimeout : 60 * 1000, 19 | heartbeatInterval : 25 * 1000 20 | }); 21 | }); 22 | 23 | // start app 24 | app.start(); 25 | 26 | process.on('uncaughtException', function (err) { 27 | console.error(' Caught exception: ' + err.stack); 28 | }); 29 | -------------------------------------------------------------------------------- /template/game-server/app.js.sio.wss: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var pomelo = require('pomelo'); 3 | 4 | /** 5 | * Init app for client. 6 | */ 7 | var app = pomelo.createApp(); 8 | app.set('name', '$'); 9 | 10 | // app configuration 11 | app.configure('production|development', function(){ 12 | app.set('connectorConfig', 13 | { 14 | connector : pomelo.connectors.sioconnector, 15 | key: fs.readFileSync('../shared/server.key'), 16 | cert: fs.readFileSync('../shared/server.crt') 17 | }); 18 | }); 19 | 20 | // start app 21 | app.start(); 22 | 23 | process.on('uncaughtException', function (err) { 24 | console.error(' Caught exception: ' + err.stack); 25 | }); -------------------------------------------------------------------------------- /template/game-server/app.js.udp: -------------------------------------------------------------------------------- 1 | var pomelo = require('pomelo'); 2 | 3 | /** 4 | * Init app for client. 5 | */ 6 | var app = pomelo.createApp(); 7 | app.set('name', '$'); 8 | 9 | // app configuration 10 | app.configure('production|development', function(){ 11 | app.set('connectorConfig', 12 | { 13 | connector : pomelo.connectors.udpconnector, 14 | heartbeat : 3 15 | }); 16 | }); 17 | 18 | // start app 19 | app.start(); 20 | 21 | process.on('uncaughtException', function (err) { 22 | console.error(' Caught exception: ' + err.stack); 23 | }); -------------------------------------------------------------------------------- /template/game-server/app.js.wss: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var pomelo = require('pomelo'); 3 | 4 | /** 5 | * Init app for client. 6 | */ 7 | var app = pomelo.createApp(); 8 | app.set('name', '$'); 9 | 10 | // app configuration 11 | app.configure('production|development', 'connector', function(){ 12 | app.set('connectorConfig', 13 | { 14 | connector : pomelo.connectors.hybridconnector, 15 | heartbeat : 3, 16 | useDict : true, 17 | useProtobuf : true, 18 | ssl: { 19 | type: 'wss', 20 | key: fs.readFileSync('../shared/server.key'), 21 | cert: fs.readFileSync('../shared/server.crt') 22 | } 23 | }); 24 | }); 25 | 26 | // start app 27 | app.start(); 28 | 29 | process.on('uncaughtException', function (err) { 30 | console.error(' Caught exception: ' + err.stack); 31 | }); -------------------------------------------------------------------------------- /template/game-server/app/servers/connector/handler/entryHandler.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app) { 2 | return new Handler(app); 3 | }; 4 | 5 | var Handler = function(app) { 6 | this.app = app; 7 | }; 8 | 9 | /** 10 | * New client entry. 11 | * 12 | * @param {Object} msg request message 13 | * @param {Object} session current session object 14 | * @param {Function} next next step callback 15 | * @return {Void} 16 | */ 17 | Handler.prototype.entry = function(msg, session, next) { 18 | next(null, {code: 200, msg: 'game server is ok.'}); 19 | }; 20 | 21 | /** 22 | * Publish route for mqtt connector. 23 | * 24 | * @param {Object} msg request message 25 | * @param {Object} session current session object 26 | * @param {Function} next next step callback 27 | * @return {Void} 28 | */ 29 | Handler.prototype.publish = function(msg, session, next) { 30 | var result = { 31 | topic: 'publish', 32 | payload: JSON.stringify({code: 200, msg: 'publish message is ok.'}) 33 | }; 34 | next(null, result); 35 | }; 36 | 37 | /** 38 | * Subscribe route for mqtt connector. 39 | * 40 | * @param {Object} msg request message 41 | * @param {Object} session current session object 42 | * @param {Function} next next step callback 43 | * @return {Void} 44 | */ 45 | Handler.prototype.subscribe = function(msg, session, next) { 46 | var result = { 47 | topic: 'subscribe', 48 | payload: JSON.stringify({code: 200, msg: 'subscribe message is ok.'}) 49 | }; 50 | next(null, result); 51 | }; 52 | -------------------------------------------------------------------------------- /template/game-server/config/adminServer.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "type": "connector", 3 | "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn" 4 | } 5 | ] -------------------------------------------------------------------------------- /template/game-server/config/adminUser.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "user-1", 4 | "username": "admin", 5 | "password": "admin", 6 | "level": 1 7 | }, 8 | 9 | { 10 | "id": "user-2", 11 | "username": "monitor", 12 | "password": "monitor", 13 | "level": 2 14 | }, 15 | 16 | { 17 | "id": "user-3", 18 | "username": "test", 19 | "password": "test", 20 | "level": 2 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /template/game-server/config/clientProtos.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /template/game-server/config/dictionary.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /template/game-server/config/log4js.json: -------------------------------------------------------------------------------- 1 | { 2 | "appenders": [ 3 | { 4 | "type": "console" 5 | }, 6 | { 7 | "type": "file", 8 | "filename": "${opts:base}/logs/con-log-${opts:serverId}.log", 9 | "pattern": "connector", 10 | "maxLogSize": 1048576, 11 | "layout": { 12 | "type": "basic" 13 | }, 14 | "backups": 5, 15 | "category": "con-log" 16 | }, 17 | { 18 | "type": "file", 19 | "filename": "${opts:base}/logs/rpc-log-${opts:serverId}.log", 20 | "maxLogSize": 1048576, 21 | "layout": { 22 | "type": "basic" 23 | }, 24 | "backups": 5, 25 | "category": "rpc-log" 26 | }, 27 | { 28 | "type": "file", 29 | "filename": "${opts:base}/logs/forward-log-${opts:serverId}.log", 30 | "maxLogSize": 1048576, 31 | "layout": { 32 | "type": "basic" 33 | }, 34 | "backups": 5, 35 | "category": "forward-log" 36 | }, 37 | { 38 | "type": "file", 39 | "filename": "${opts:base}/logs/rpc-debug-${opts:serverId}.log", 40 | "maxLogSize": 1048576, 41 | "layout": { 42 | "type": "basic" 43 | }, 44 | "backups": 5, 45 | "category": "rpc-debug" 46 | }, 47 | { 48 | "type": "file", 49 | "filename": "${opts:base}/logs/crash.log", 50 | "maxLogSize": 1048576, 51 | "layout": { 52 | "type": "basic" 53 | }, 54 | "backups": 5, 55 | "category":"crash-log" 56 | }, 57 | { 58 | "type": "file", 59 | "filename": "${opts:base}/logs/admin.log", 60 | "maxLogSize": 1048576, 61 | "layout": { 62 | "type": "basic" 63 | } 64 | ,"backups": 5, 65 | "category":"admin-log" 66 | }, 67 | { 68 | "type": "file", 69 | "filename": "${opts:base}/logs/pomelo-${opts:serverId}.log", 70 | "maxLogSize": 1048576, 71 | "layout": { 72 | "type": "basic" 73 | } 74 | ,"backups": 5, 75 | "category":"pomelo" 76 | }, 77 | { 78 | "type": "file", 79 | "filename": "${opts:base}/logs/pomelo-admin.log", 80 | "maxLogSize": 1048576, 81 | "layout": { 82 | "type": "basic" 83 | } 84 | ,"backups": 5, 85 | "category":"pomelo-admin" 86 | }, 87 | { 88 | "type": "file", 89 | "filename": "${opts:base}/logs/pomelo-rpc-${opts:serverId}.log", 90 | "maxLogSize": 1048576, 91 | "layout": { 92 | "type": "basic" 93 | } 94 | ,"backups": 5, 95 | "category":"pomelo-rpc" 96 | } 97 | ], 98 | 99 | "levels": { 100 | "rpc-log" : "ERROR", 101 | "forward-log": "ERROR" 102 | }, 103 | 104 | "replaceConsole": true, 105 | 106 | "lineDebug": false 107 | } 108 | -------------------------------------------------------------------------------- /template/game-server/config/master.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "id": "master-server-1", "host": "127.0.0.1", "port": 3005 4 | }, 5 | "production": { 6 | "id": "master-server-1", "host": "127.0.0.1", "port": 3005 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /template/game-server/config/serverProtos.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /template/game-server/config/servers.json: -------------------------------------------------------------------------------- 1 | { 2 | "development":{ 3 | "connector": [ 4 | {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientHost": "127.0.0.1", "clientPort": 3010, "frontend": true} 5 | ] 6 | }, 7 | "production":{ 8 | "connector": [ 9 | {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientHost": "127.0.0.1", "clientPort": 3010, "frontend": true} 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /template/game-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"$", 3 | "version":"0.0.1", 4 | "private":false, 5 | "dependencies":{ 6 | "pomelo":"#" 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /template/npm-install.bat: -------------------------------------------------------------------------------- 1 | ::npm-install.bat 2 | @echo off 3 | ::install web server dependencies && game server dependencies 4 | cd web-server && npm install -d && cd .. && cd game-server && npm install -d -------------------------------------------------------------------------------- /template/npm-install.sh: -------------------------------------------------------------------------------- 1 | cd ./game-server && npm install -d 2 | echo '============ game-server npm installed ============' 3 | cd .. 4 | cd ./web-server && npm install -d 5 | echo '============ web-server npm installed ============' 6 | -------------------------------------------------------------------------------- /template/shared/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICSzCCAbQCCQCQVN8rD6MylDANBgkqhkiG9w0BAQUFADBqMQswCQYDVQQGEwJD 3 | TjERMA8GA1UECAwIemhlamlhbmcxETAPBgNVBAcMCGhhbmd6aG91MRAwDgYDVQQK 4 | DAdOZXRFYXNlMQ8wDQYDVQQLDAZwb21lbG8xEjAQBgNVBAMMCWxvY2FsaG9zdDAe 5 | Fw0xNDA0MjIwNjEwMDJaFw0xNDA1MjIwNjEwMDJaMGoxCzAJBgNVBAYTAkNOMREw 6 | DwYDVQQIDAh6aGVqaWFuZzERMA8GA1UEBwwIaGFuZ3pob3UxEDAOBgNVBAoMB05l 7 | dEVhc2UxDzANBgNVBAsMBnBvbWVsbzESMBAGA1UEAwwJbG9jYWxob3N0MIGfMA0G 8 | CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMPe8oscKpTlQFZRrpbWmSO1UE+H65nq50 9 | l5+ptOVPMK3wgEj+YRyGWhBjugj9teVmLXY9ImWdZkBlvdAiQj7/S/1MxRbRtwEF 10 | GRE5ul/X1M6I+F0UyTGYA1Mo0jIlQaBDXAAyDujCWi+qlyZ28efNDUlO2KBY1H4r 11 | Xobm9hoEFQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAMIuL8KqEEtjbfL/tR2+5dQ5 12 | 958gtDtA62L7bMosl4hmuzdyWADu3IcKSaXAESLhIuIClt2Pwc14iFf9qRyB/cjY 13 | 4kLgwDGhK5EJw1kQS+Hs9NNSGxJTXUkoms3kEdRGy4hrZpTheJJNaKuv3oXrdvYQ 14 | 85yoc/P5OnJapB3huYL9 15 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /template/shared/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWwIBAAKBgQDMPe8oscKpTlQFZRrpbWmSO1UE+H65nq50l5+ptOVPMK3wgEj+ 3 | YRyGWhBjugj9teVmLXY9ImWdZkBlvdAiQj7/S/1MxRbRtwEFGRE5ul/X1M6I+F0U 4 | yTGYA1Mo0jIlQaBDXAAyDujCWi+qlyZ28efNDUlO2KBY1H4rXobm9hoEFQIDAQAB 5 | AoGAXhaeCUIyqeoynLWh+yzzOHFqzjpnrr0iIwYCgJycEqobRzLh7YXxLRdqe3al 6 | U7Oq9TI2SR2CcEs9mWEi89VOzVvfu+4zRlvJLMzNjG8ncdvzmzWR288ORq6qmYVU 7 | 3KAEz/tbNaQMLrD43hkIb9BrSIb/cnwekl3pANo9dwytU5UCQQD4V6vTyzs/ob21 8 | +fO98tFkPtoHbt43S/1kDBSUyh6WWbS1KIQgtUSr2P5Ddtl6/vD3DW+XHCAhxyfV 9 | vuDvaP/fAkEA0oomFfmlpvzYejYNKPOz2PR+M0oRFVwn7lYyNwbRtUK1JYOMHwJ/ 10 | 3gwQEgAcYEkvgRlsxX0T5vHNmoR3U3OqiwJAIWkiG9devDvVWxMqoKZ3V0ZBbPiU 11 | etoFWB1r82yR2uZssmamCAR7HaeO5aKqtapw3rv3BFxrUkAJ8u7AMlVs/wJAVnpm 12 | MGqNjyyWIoSnHSYUvk2WtKx8neBvimcfUxja9HAFBfaljGszaFpeE3a2MRp+h7GQ 13 | ywGYNikmAYzdkoqVBwJAcOm/6u863pD2xA1mSFnmm3TulAMBfCULLdcY40w9m38b 14 | D89R1ISEy//N1fWa4KTsM0GpVOowEyluc53XNRUghw== 15 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /template/web-server/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express.createServer(); 3 | 4 | app.configure(function(){ 5 | app.use(express.methodOverride()); 6 | app.use(express.bodyParser()); 7 | app.use(app.router); 8 | app.set('view engine', 'jade'); 9 | app.set('views', __dirname + '/public'); 10 | app.set('view options', {layout: false}); 11 | app.set('basepath',__dirname + '/public'); 12 | }); 13 | 14 | app.configure('development', function(){ 15 | app.use(express.static(__dirname + '/public')); 16 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 17 | }); 18 | 19 | app.configure('production', function(){ 20 | var oneYear = 31557600000; 21 | app.use(express.static(__dirname + '/public', { maxAge: oneYear })); 22 | app.use(express.errorHandler()); 23 | }); 24 | 25 | console.log("Web server has started.\nPlease log on http://127.0.0.1:3001/index.html"); 26 | 27 | app.listen(3001); 28 | -------------------------------------------------------------------------------- /template/web-server/app.js.https: -------------------------------------------------------------------------------- 1 | var https = require('https'); 2 | var express = require('express'); 3 | 4 | var fs = require('fs'); 5 | 6 | var options = { 7 | key: fs.readFileSync('../shared/server.key'), 8 | cert: fs.readFileSync('../shared/server.crt') 9 | }; 10 | 11 | var app = express(); 12 | 13 | app.configure(function(){ 14 | app.use(express.methodOverride()); 15 | app.use(express.bodyParser()); 16 | app.use(app.router); 17 | app.set('view engine', 'jade'); 18 | app.set('views', __dirname + '/public'); 19 | app.set('view options', {layout: false}); 20 | app.set('basepath',__dirname + '/public'); 21 | }); 22 | 23 | app.configure('development', function(){ 24 | app.use(express.static(__dirname + '/public')); 25 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 26 | }); 27 | 28 | app.configure('production', function(){ 29 | var oneYear = 31557600000; 30 | app.use(express.static(__dirname + '/public', { maxAge: oneYear })); 31 | app.use(express.errorHandler()); 32 | }); 33 | 34 | console.log("Web server has started.\nPlease log on http://127.0.0.1:3001/index.html"); 35 | 36 | var httpsServer = https.createServer(options, app); 37 | 38 | httpsServer.listen(3001); -------------------------------------------------------------------------------- /template/web-server/bin/component.bat: -------------------------------------------------------------------------------- 1 | cd public/js/lib && component install -f && component build -v -------------------------------------------------------------------------------- /template/web-server/bin/component.sh: -------------------------------------------------------------------------------- 1 | cd public/js/lib && component install -f && component build -v -------------------------------------------------------------------------------- /template/web-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$", 3 | "version": "0.0.1", 4 | "private": false, 5 | "dependencies": { 6 | "express": "3.4.8" 7 | } 8 | } -------------------------------------------------------------------------------- /template/web-server/public/css/base.css: -------------------------------------------------------------------------------- 1 | .g-doc { 2 | width: 1000px; 3 | margin: 0 auto; 4 | text-align: left; 5 | line-height: 18px; 6 | font-size: 12px; 7 | color: #555; 8 | font-family: arial; 9 | } 10 | 11 | .g-banner { 12 | position: relative; 13 | width: 1000px; 14 | height: 90px; 15 | margin: 0 auto; 16 | } 17 | 18 | .g-banner .logo { 19 | position: absolute; 20 | left: 20px; 21 | top: 13px; 22 | width: 153px; 23 | height: 60px; 24 | z-index: 101; 25 | } 26 | 27 | .g-banner .logo .img { 28 | width: 153px; 29 | height: 60px; 30 | background: url(../image/logo.png) no-repeat 0 0; 31 | } 32 | 33 | .g-background { 34 | height: 350px; 35 | background: url(../image/sp.png) no-repeat 326px 0; 36 | } 37 | 38 | .g-content { 39 | padding: 150px; 40 | width: 800px; 41 | margin-left: 50px; 42 | font-size: 50pt; 43 | font-style: italic; 44 | } 45 | 46 | .g-link { 47 | height: 100px; 48 | margin-top: 30px; 49 | margin-left: 300px; 50 | font-size: 15pt; 51 | } 52 | 53 | .g-button { 54 | margin-top: 10px; 55 | text-align: center; 56 | } 57 | 58 | a:link { 59 | color: #006699; 60 | text-decoration: none; 61 | } 62 | 63 | a:visited { 64 | color: #006699; 65 | text-decoration: none; 66 | } 67 | 68 | a:hover { 69 | color: #FF0000; 70 | text-decoration: underline; 71 | } 72 | 73 | a:active { 74 | color: #006699; 75 | text-decoration: underline; 76 | } 77 | -------------------------------------------------------------------------------- /template/web-server/public/image/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEase/pomelo/e1615c1305170241efd197c085b7324570dced1f/template/web-server/public/image/logo.png -------------------------------------------------------------------------------- /template/web-server/public/image/sp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEase/pomelo/e1615c1305170241efd197c085b7324570dced1f/template/web-server/public/image/sp.png -------------------------------------------------------------------------------- /template/web-server/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pomelo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 35 | 36 | 37 |
38 |
39 | 42 |
43 |
44 |
45 | Welcome to Pomelo 46 |
47 |
48 | 52 |
53 | 54 |
55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /template/web-server/public/index.html.sio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pomelo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 19 | 35 | 36 | 37 | 38 |
39 |
40 | 43 |
44 |
45 |
46 | Welcome to Pomelo 47 |
48 |
49 | 53 |
54 | 55 |
56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /template/web-server/public/js/lib/component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pomelo-client", 3 | "description": "pomelo-client", 4 | "local": [ "boot" ], 5 | "paths": [ "local"] 6 | } -------------------------------------------------------------------------------- /template/web-server/public/js/lib/local/boot/component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boot", 3 | "description": "Main app boot component", 4 | "dependencies": { 5 | "component/emitter":"master", 6 | "NetEase/pomelo-protocol": "master", 7 | "pomelonode/pomelo-protobuf": "*", 8 | "pomelonode/pomelo-jsclient-websocket": "master" 9 | }, 10 | "scripts": ["index.js"] 11 | } -------------------------------------------------------------------------------- /template/web-server/public/js/lib/local/boot/index.js: -------------------------------------------------------------------------------- 1 | var Emitter = require('emitter'); 2 | window.EventEmitter = Emitter; 3 | 4 | var protocol = require('pomelo-protocol'); 5 | window.Protocol = protocol; 6 | 7 | var protobuf = require('pomelo-protobuf'); 8 | window.protobuf = protobuf; 9 | 10 | var pomelo = require('pomelo-jsclient-websocket'); 11 | window.pomelo = pomelo; 12 | -------------------------------------------------------------------------------- /test/config/log4js.json: -------------------------------------------------------------------------------- 1 | { 2 | "appenders": [ 3 | { 4 | "type": "file", 5 | "filename": "./test/logs/node-log-${opts:serverId}.log", 6 | "fileSize": 1048576, 7 | "layout": { 8 | "type": "basic" 9 | }, 10 | "backups": 5 11 | }, 12 | { 13 | "type": "console" 14 | }, 15 | { 16 | "type": "file", 17 | "filename": "./test/logs/con-log-${opts:serverId}.log", 18 | "pattern": "connector", 19 | "maxLogSize": 1048576, 20 | "layout": { 21 | "type": "basic" 22 | }, 23 | "backups": 5, 24 | "category":"con-log" 25 | }, 26 | { 27 | "type": "file", 28 | "filename": "./test/logs/rpc-log-${opts:serverId}.log", 29 | "fileSize": 1048576, 30 | "layout": { 31 | "type": "basic" 32 | }, 33 | "backups": 5, 34 | "category":"rpc-log" 35 | }, 36 | { 37 | "type": "file", 38 | "filename": "./test/logs/forward-log-${opts:serverId}.log", 39 | "fileSize": 1048576, 40 | "layout": { 41 | "type": "basic" 42 | }, 43 | "backups": 5, 44 | "category":"forward-log" 45 | }, 46 | { 47 | "type": "file", 48 | "filename": "./test/logs/crash.log", 49 | "fileSize": 1048576, 50 | "layout": { 51 | "type": "basic" 52 | }, 53 | "backups": 5, 54 | "category":"crash-log" 55 | }, 56 | { 57 | "type": "file", 58 | "filename": "./test/logs/transaction.log", 59 | "fileSize": 1048576, 60 | "layout": { 61 | "type": "basic" 62 | }, 63 | "backups": 5, 64 | "category":"transaction-log" 65 | }, 66 | { 67 | "type": "file", 68 | "filename": "./test/logs/transaction-error.log", 69 | "fileSize": 1048576, 70 | "layout": { 71 | "type": "basic" 72 | }, 73 | "backups": 5, 74 | "category":"transaction-error-log" 75 | } 76 | ], 77 | 78 | "levels": { 79 | "rpc-log" : "ERROR", 80 | "forward-log": "ERROR" 81 | }, 82 | 83 | "replaceConsole": true 84 | } 85 | -------------------------------------------------------------------------------- /test/config/master.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "id": "master-server-1", "host": "127.0.0.1", "port": 3005 4 | }, 5 | 6 | "production": { 7 | "id": "master-server-1", "host": "172.17.2.237", "port": 3005 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/config/servers.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | }, 4 | "production": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/filters/handler/serial.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var serialFilter = require('../../../lib/filters/handler/serial'); 3 | var FilterService = require('../../../lib/common/service/filterService'); 4 | var util = require('util'); 5 | 6 | var mockSession = { 7 | key : "123" 8 | }; 9 | 10 | var WAIT_TIME = 100; 11 | describe("#serialFilter",function(){ 12 | it("should do before filter ok",function(done){ 13 | var service = new FilterService(); 14 | var filter = serialFilter(); 15 | service.before(filter); 16 | 17 | service.beforeFilter(null,mockSession,function(){ 18 | should.exist(mockSession); 19 | 20 | should.exist(mockSession.__serialTask__); 21 | done(); 22 | }); 23 | }); 24 | 25 | it("should do after filter by doing before filter ok",function(done){ 26 | var service = new FilterService(); 27 | var filter = serialFilter(); 28 | var _session ; 29 | service.before(filter); 30 | 31 | service.beforeFilter(null,mockSession,function(){ 32 | should.exist(mockSession); 33 | should.exist(mockSession.__serialTask__); 34 | _session = mockSession; 35 | }); 36 | 37 | service.after(filter); 38 | 39 | service.afterFilter(null,null,mockSession,null,function(){ 40 | should.exist(mockSession); 41 | should.strictEqual(mockSession,_session); 42 | }); 43 | 44 | setTimeout(done,WAIT_TIME); 45 | }); 46 | }); -------------------------------------------------------------------------------- /test/filters/handler/time.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var serialFilter = require('../../../lib/filters/handler/time'); 3 | var FilterService = require('../../../lib/common/service/filterService'); 4 | var util = require('util'); 5 | var mockSession = { 6 | key : "123" 7 | }; 8 | 9 | var WAIT_TIME = 100; 10 | describe("#serialFilter",function(){ 11 | it("should do before filter ok",function(done){ 12 | var service = new FilterService(); 13 | var filter = serialFilter(); 14 | service.before(filter); 15 | 16 | 17 | service.beforeFilter(null,mockSession,function(){ 18 | should.exist(mockSession); 19 | 20 | should.exist(mockSession.__startTime__); 21 | done(); 22 | }); 23 | }); 24 | 25 | it("should do after filter by doing before filter ok",function(done){ 26 | var service = new FilterService(); 27 | var filter = serialFilter(); 28 | var _session ; 29 | service.before(filter); 30 | 31 | service.beforeFilter(null,mockSession,function(){ 32 | should.exist(mockSession); 33 | should.exist(mockSession.__startTime__); 34 | _session = mockSession; 35 | }); 36 | 37 | service.after(filter); 38 | 39 | service.afterFilter(null,{route:"hello"},mockSession,null,function(){ 40 | should.exist(mockSession); 41 | should.strictEqual(mockSession,_session); 42 | }); 43 | 44 | setTimeout(done,WAIT_TIME); 45 | done(); 46 | }); 47 | }); -------------------------------------------------------------------------------- /test/filters/handler/timeout.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var timeoutFilter = require('../../../lib/filters/handler/timeout'); 3 | var FilterService = require('../../../lib/common/service/filterService'); 4 | var util = require('util'); 5 | var mockSession = { 6 | key : "123" 7 | }; 8 | 9 | var WAIT_TIME = 100; 10 | describe("#serialFilter",function(){ 11 | it("should do before filter ok",function(done){ 12 | var service = new FilterService(); 13 | var filter = timeoutFilter(); 14 | service.before(filter); 15 | 16 | service.beforeFilter(null,mockSession,function(){ 17 | should.exist(mockSession); 18 | 19 | should.exist(mockSession.__timeout__); 20 | done(); 21 | }); 22 | }); 23 | 24 | it("should do after filter by doing before filter ok",function(done){ 25 | var service = new FilterService(); 26 | var filter = timeoutFilter(); 27 | var _session ; 28 | service.before(filter); 29 | 30 | service.beforeFilter(null,mockSession,function(){ 31 | should.exist(mockSession); 32 | should.exist(mockSession.__timeout__); 33 | _session = mockSession; 34 | }); 35 | 36 | service.after(filter); 37 | 38 | service.afterFilter(null,null,mockSession,null,function(){ 39 | should.exist(mockSession); 40 | should.strictEqual(mockSession,_session); 41 | }); 42 | 43 | setTimeout(done,WAIT_TIME); 44 | done(); 45 | }); 46 | }); -------------------------------------------------------------------------------- /test/filters/handler/toobusy.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var toobusyFilter = require('../../../lib/filters/handler/toobusy'); 3 | var FilterService = require('../../../lib/common/service/filterService'); 4 | var util = require('util'); 5 | var mockSession = { 6 | key : "123" 7 | }; 8 | 9 | describe("#toobusyFilter",function(){ 10 | it("should do before filter ok",function(done){ 11 | var service = new FilterService(); 12 | var filter = toobusyFilter(); 13 | service.before(filter); 14 | 15 | service.beforeFilter(null,mockSession,function(err){ 16 | should.not.exist(err); 17 | should.exist(mockSession); 18 | done(); 19 | }); 20 | }); 21 | 22 | it("should do before filter error because of too busy",function(done){ 23 | var service = new FilterService(); 24 | var filter = toobusyFilter(); 25 | service.before(filter); 26 | 27 | var exit = false; 28 | function load() { 29 | service.beforeFilter(null,mockSession,function(err, resp){ 30 | should.exist(mockSession); 31 | console.log('err: ' + err); 32 | if (!!err) { 33 | exit = true; 34 | } 35 | }); 36 | 37 | console.log('exit: ' + exit); 38 | if (exit) { 39 | return done(); 40 | } 41 | var start = new Date(); 42 | while ((new Date() - start) < 250) { 43 | for (var i = 0; i < 1e5;) i++; 44 | } 45 | setTimeout(load, 0); 46 | } 47 | load(); 48 | 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/filters/rpc/rpcLog.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var rpcLogFilter = require('../../../lib/filters/rpc/rpcLog'); 3 | 4 | var mockData = { 5 | serverId : "connector-server-1", 6 | msg : "hello", 7 | opts : {} 8 | }; 9 | 10 | describe('#rpcLogFilter',function(){ 11 | it("should do after filter by before filter",function(done){ 12 | rpcLogFilter.before(mockData.serverId,mockData.msg,mockData.opts,function(serverId,msg,opts){ 13 | rpcLogFilter.after(serverId,msg,opts,function(serverId,msg,opts){ 14 | should.exist(opts.__start_time__); 15 | done(); 16 | }); 17 | }); 18 | }); 19 | }); -------------------------------------------------------------------------------- /test/filters/rpc/toobusy.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var toobusyFilter = require('../../../lib/filters/rpc/toobusy'); 3 | 4 | var mockData = { 5 | serverId : "connector-server-1", 6 | msg : "hello", 7 | opts : {} 8 | }; 9 | 10 | 11 | describe('#toobusyFilter',function(){ 12 | it("should no callback for toobusy",function(done){ 13 | function load() { 14 | var callbackInvoked = false; 15 | toobusyFilter.before(mockData.serverId,mockData.msg,mockData.opts,function(serverId,msg,opts){ 16 | callbackInvoked = true; 17 | }); 18 | 19 | if (!callbackInvoked) { 20 | console.log(' logic of toobusy enterd, done!'); 21 | return done(); 22 | } 23 | var start = new Date(); 24 | while ((new Date() - start) < 250) { 25 | for (var i = 0; i < 1e5;) i++; 26 | } 27 | setTimeout(load, 0); 28 | } 29 | load(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/logs/tmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEase/pomelo/e1615c1305170241efd197c085b7324570dced1f/test/logs/tmp -------------------------------------------------------------------------------- /test/manager/mockChannelManager.js: -------------------------------------------------------------------------------- 1 | var DEFAULT_PREFIX = 'POMELO:CHANNEL'; 2 | var utils = require('../../lib/util/utils'); 3 | 4 | var MockManager = function(app, opts) { 5 | this.app = app; 6 | this.opts = opts || {}; 7 | this.prefix = opts.prefix || DEFAULT_PREFIX; 8 | }; 9 | 10 | module.exports = MockManager; 11 | 12 | MockManager.prototype.start = function(cb) { 13 | this.usersMap = {}; 14 | utils.invokeCallback(cb); 15 | }; 16 | 17 | MockManager.prototype.stop = function(force, cb) { 18 | this.usersMap = null; 19 | utils.invokeCallback(cb); 20 | }; 21 | 22 | MockManager.prototype.add = function(name, uid, sid, cb) { 23 | var key = genKey(this, name, sid); 24 | if(!this.usersMap[key]) { 25 | this.usersMap[key] = []; 26 | } 27 | this.usersMap[key].push(uid); 28 | utils.invokeCallback(cb); 29 | }; 30 | 31 | MockManager.prototype.leave = function(name, uid, sid, cb) { 32 | var key = genKey(this, name, sid); 33 | var res = deleteFrom(uid, this.usersMap[key]); 34 | if(this.usersMap[key] && this.usersMap[key].length === 0) { 35 | delete this.usersMap[sid]; 36 | } 37 | utils.invokeCallback(cb); 38 | }; 39 | 40 | MockManager.prototype.getMembersBySid = function(name, sid, cb) { 41 | var key = genKey(this, name, sid); 42 | if(!this.usersMap[key]) 43 | this.usersMap[key] = []; 44 | utils.invokeCallback(cb, null, this.usersMap[key]); 45 | }; 46 | 47 | MockManager.prototype.destroyChannel = function(name, cb) { 48 | var servers = this.app.getServers(); 49 | var server, removes = []; 50 | for(var sid in servers) { 51 | server = servers[sid]; 52 | if(this.app.isFrontend(server)) { 53 | removes.push(genKey(this, name, sid)); 54 | } 55 | } 56 | 57 | if(removes.length === 0) { 58 | utils.invokeCallback(cb); 59 | return; 60 | } 61 | 62 | for(var i = 0; i