Showing preview only (606K chars total). Download the full file or copy to clipboard to get everything.
Repository: NetEase/pomelo
Branch: master
Commit: e1615c130517
Files: 156
Total size: 565.2 KB
Directory structure:
gitextract_d3h0eibp/
├── .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/
│ │ │ └── servers/
│ │ │ └── connector/
│ │ │ └── handler/
│ │ │ └── entryHandler.js
│ │ ├── app.js
│ │ ├── app.js.mqtt
│ │ ├── app.js.sio
│ │ ├── app.js.sio.wss
│ │ ├── app.js.udp
│ │ ├── app.js.wss
│ │ ├── 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
│ ├── 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
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.project
*/node-log.log
logs/*.log
*.log
!.gitignore
node_modules/*
node_modules_back/*
.project
.settings/
**/*.svn
*.svn
*.swp
*.sublime-project
*.sublime-workspace
lib/doc/
lib-cov/
coverage.html
.DS_Store
.idea/*
================================================
FILE: .jshintrc
================================================
{
"node": true,
"camelcase": true,
"eqeqeq": true,
"undef": true,
"unused": true,
"curly": true,
"newcap": true,
"nonew": true,
"trailing": true,
"smarttabs": true,
"noarg": true
}
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "10.15.0"
before_script:
- npm install -g grunt-cli
================================================
FILE: AUTHORS
================================================
* Charlie Crane <xieccy@gmail.com> <twitter:@xiecc> <weibo:@圈圈套圈圈> <github:xiecc>
* Chang chang <changchang005@gmail.com> <twitter:@changchang005> <weibo:@郁闷的武昌鱼> <github:changchang>
* piaohai <piaohai@gmail.com> <twitter:@piaohai> <weibo:@飘Hai> <github:piaohai>
* py8765 <pengyang633@126.com> <twitter:@py8765> <weibo:@py8765> <github:py8765>
* Demon <zhhang0925@gmail.com> <weibo:@demon0925> <github:demon0925>
* numbcoder <wzhao23@gmail.com> <twitter: @numbcoder> <weibo: @Seekr> <github: numbcoder>
* halfblood <halfblood369@gmail.com> <twitter: @halfblo97338394> <weibo: @衣兜里的东西> <github: halfblood369>
* fantasyni <fantasyni@163.com> <github:fantasyni>
================================================
FILE: History.md
================================================
2.2.7 / 2019-11-8
=================
* [#1150](https://github.com/NetEase/pomelo/pull/1150)
2.2.6 / 2019-7-10
=================
* [NEW] upgrade pomelo-admin to 1.0.1
2.2.5 / 2017-1-22
=================
* [#815](https://github.com/NetEase/pomelo/pull/815)
* [#901](https://github.com/NetEase/pomelo/pull/901)
2.2.4 / 2017-1-20
=================
* [NEW] upgrade pomelo-rpc to 1.0.7
2.2.3 / 2017-1-20
=================
* [NEW] upgrade socket.io to 1.7.x
2.2.2 / 2017-1-20
=================
* [#899](https://github.com/NetEase/pomelo/pull/899)
2.2.1 / 2017-1-20
=================
* [FIX] fix push message with no array
2.2.0 / 2017-1-19
=================
* [NEW] upgrade pomelo-rpc to 1.0.6, rpc protocol moved to MQTT
* [NEW] upgrade pomelo-admin to 1.0.0
* [NEW] pure javaScript without need to install c++ addons
1.2.1 / 2015-12-31
================
* [NEW] upgrade ws to 0.8.0
* [#771](https://github.com/NetEase/pomelo/pull/771)
* [#774](https://github.com/NetEase/pomelo/pull/774)
* [FIX] tls: destory connection when clientError
1.2.0 / 2015-09-18
=================
* [NEW] upgrade pomelo-admin to 0.4.5
* [#751](https://github.com/NetEase/pomelo/pull/751)
* [#741](https://github.com/NetEase/pomelo/pull/741)
* [#740](https://github.com/NetEase/pomelo/pull/740)
* [FIX] fix wrong variable in protobuf
1.1.9 / 2015-06-05
=================
* [NEW] upgrade pomelo-rpc to 0.4.10
* [NEW] upgrade pomelo-admin to 0.4.4
* [NEW] upgrade pomelo-logger to 0.1.7
1.1.8 / 2015-05-29
=================
* fix bug on verison calculating when update proto files
* avoid modules loading error on windows
* [NEW] upgrade pomelo-protocol to 0.1.6
1.1.7 / 2015-05-12
=================
* [#706](https://github.com/NetEase/pomelo/pull/706)
* [#707](https://github.com/NetEase/pomelo/pull/707)
* [#443](https://github.com/NetEase/pomelo/pull/443)
* [#444](https://github.com/NetEase/pomelo/pull/444)
* [#713](https://github.com/NetEase/pomelo/pull/713)
* [NEW] upgrade pomelo-rpc to 0.4.9
* [NEW] upgrade pomelo-admin to 0.4.3
1.1.6 / 2015-03-12
=================
* [NEW] add configure file automatically reload feature
* [NEW] add mqtt connector heartbeat timeout option
* [NEW] upgrade pomelo-rpc to 0.4.8
1.1.5 / 2015-02-26
=================
* [NEW] upgrade pomelo-rpc to 0.4.7
1.1.4 / 2015-01-23
=================
[#670](https://github.com/NetEase/pomelo/pull/670)
[#669](https://github.com/NetEase/pomelo/pull/669)
[#666](https://github.com/NetEase/pomelo/pull/666)
[#665](https://github.com/NetEase/pomelo/pull/665)
[#662](https://github.com/NetEase/pomelo/pull/662)
[#659](https://github.com/NetEase/pomelo/pull/659)
[#657](https://github.com/NetEase/pomelo/pull/657)
[#653](https://github.com/NetEase/pomelo/pull/653)
1.1.2 / 2014-11-12
=================
* [NEW] introduce updateUserInfo for connectionService
[#637](https://github.com/NetEase/pomelo/pull/637)
* [FIX] fix wrong variable err using
[#642](https://github.com/NetEase/pomelo/pull/642)
* [NEW] introduce cancelShutdownHook
[#644](https://github.com/NetEase/pomelo/pull/644)
* [FIX] revert PR #613, which should not be accepted
[#649](https://github.com/NetEase/pomelo/pull/649)
1.1.1 / 2014-10-10
=================
* [NEW] upgrade pomelo-protocol to 0.1.4
[#616](https://github.com/NetEase/pomelo/pull/616)
* [FIX] incorrect this scope
[#622](https://github.com/NetEase/pomelo/pull/622)
* [FIX] fix bug on arg parse
[#623](https://github.com/NetEase/pomelo/pull/623)
* [FIX] connection without communication bug
1.1.0 / 2014-09-12
=================
* [NEW] fit for libpomelo2
* [NEW] upgrade pomelo-rpc to 0.4.5
[#612](https://github.com/NetEase/pomelo/pull/612)
* [FIX] close http server after WebSocketServer.close
[#613](https://github.com/NetEase/pomelo/pull/613)
* [FIX] update timeout.js
[#614](https://github.com/NetEase/pomelo/pull/614)
* [FIX] fix typo
1.0.4 / 2014-08-26
=================
* [NEW] upgrade pomelo-rpc to 0.4.3
* [NEW] upgrade pomelo-logger to 0.1.6
* [FIX] pomelo-masterha-plugin reconnect bug miss parameter env
* [#582](https://github.com/NetEase/pomelo/pull/582)
1.0.3 / 2014-07-18
=================
* [NEW] dictVersion : Similar to `protoVersion`, add `dictVersion` and skip sending dict when handshaking if possible.
[#572](https://github.com/NetEase/pomelo/pull/572)
* [FIX] CRON : Upgrade pomelo-scheduler to v0.3.9. Fix a bug that will loss tasks if The number of days next month is greater than this month.
[#560](https://github.com/NetEase/pomelo/pull/560)
[pomelo-scheduler#4](https://github.com/NetEase/pomelo-scheduler/pull/4)
* [FIX] hot update : Remove the error logging if a server doesn't have any handler.
[#562](https://github.com/NetEase/pomelo/pull/562)
* [NEW] protobuf : Add `protobuf cache` into libpomelo, and add `useProto` handshake option to tell the client whether to use the protobuf.
[#564](https://github.com/NetEase/pomelo/pull/564)
[libpomelo#58](https://github.com/NetEase/libpomelo/pull/58)
[discuss(chinese)](http://nodejs.netease.com/topic/53c6c126898634292c8157a2)
1.0.2 / 2014-07-10
=================
* fix server reconnect bug
1.0.1 / 2014-07-03
=================
* merge pull request #538 #541 #545 #546 #547
* update master watchdog notify method
* upgrade pomelo-rpc to 0.4.2
1.0.0 / 2014-06-19
=================
* mqtt connector
* support ie6,7,8 with sioconnector
* support hot update partially
1.0.0-pre / 2014-05-16
=================
* add udpconnector
* pomelo-rpc load balancing and fault tolerance
* connector wss & tls support
* pomelo-zookeeper-plugin
* pomelo-scale-plugin
* environment directory configuration support
* pomelo-cli dynamic script
0.9.10 / 2014-05-15
=================
* merge pull request #505
* merge pull request #506
* merge pull request from kaisatec
* add getClientAddressBySessionId in sessionService
0.9.9 / 2014-05-06
=================
* merge pull request #495
* merge pull request #499
* merge pull request #501
* upgrade pomelo-admin to 0.3.4
0.9.8 / 2014-05-04
=================
* remove unused module
* update constants definition
0.9.7 / 2014-04-25
=================
* merge pull request #486
* add channelservice rpc error info
0.9.6 / 2014-04-16
=================
* merge pull request #472
* merge pull request #475
* merge pull request #485
* update ssh config
0.9.5 / 2014-04-02
=================
* add support for different env
* add session count method in sessionService
* emit start_all_event
* better prompt for init project
0.9.4 / 2014-03-20
=================
* merge pull request from zhaohaojie
* merge pull request from wuxian
* merge pull request from roytan883
* fix history.md year bug
0.9.3 / 2014-03-11
=================
* upgrade pomelo-admin to 0.3.2 for npm reason
0.9.2 / 2014-03-10
=================
* merge pull request sshPort
* merge pull request #438
* fix hybridconnector dict bug
* upgrade pomelo-rpc to 0.3.2
0.9.1 / 2014-03-03
=================
* fix pomelo stop auto-restart bug
* add restart-force option
* add application.require method
* export constants.js
0.9.0 / 2014-02-26
=================
* rpc support for zmq
* rpc requests callback timeout
* rpc support for hot restart
* optimize for command line
* support for connection blacklist
* protobuf support for decodeIO-protobuf.js
* channel serialization interface
0.8.9 / 2014-02-21
=================
* fix fin_wait2 caused by socket.end bug
0.8.8 / 2014-02-19
=================
* fix some typos in comment
0.8.7 / 2014-01-28
=================
* refactor pomelo command, report remained servers if kill failed
0.8.6 / 2014-01-22
=================
* upgrade pomelo-rpc 0.2.9
* upgrade pomelo-admin 0.2.9
0.8.5 / 2014-01-22
=================
* upgrade pomelo-rpc 0.2.8
* upgrade pomelo-scheduler 0.3.8
0.8.4 / 2014-01-20
=================
* fix bin/pomelo spell bug
0.8.3 / 2014-01-16
=================
* add tcp socket close option
* upgrade pomelo-rpc 0.2.7
* upgrade pomelo-admin 0.2.8
* upgrade pomelo-schedule 0.3.7
0.8.2 / 2014-01-03
=================
* fix session kick bug issue #355
* fix add rpc filter bug
0.8.1 / 2013-12-31
=================
* upgrade pomelo-rpc to 0.2.6
* handle rpc filter error
* add test cases
0.8.0 / 2013-12-24
=================
* refactor bin/pomelo
* pushScheduler add option
* add rpc invoke method
* lifecycle callback feature
* add rcp filter interface
* simplify servers.json configuration
* pomelo-logger dynamic log level
* pomelo-rpc & pomelo-admin white list
* pomelo-data-plugin
0.7.7 / 2013-12-16
=================
* upgrade pomelo-loader to 0.0.6
* upgrade pomelo-logger to 0.1.2(add dynamic change logger level feature)
0.7.6 / 2013-12-3
=================
* upgrade pomelo-rpc to 0.2.4
* upgrade pomelo-admin to 0.2.6(fix reconnect bug)
0.7.5 / 2013-11-27
=================
* fix pomelo kill bug
* fix rpc toobusy filter bug
0.7.4 / 2013-11-20
=================
* fix pomelo add command
* master start servers in 2 mode, detached in production, no detched in development
0.7.3 / 2013-11-15
=================
* add heartbeat timeout option
0.7.2 / 2013-11-14
=================
* add start server detached mode
* add masterha for pomelo stop&list
* fix auto-restart disconnect bug
* update pomelo start for different envs
0.7.1 / 2013-11-11
=================
* fix errorHandler bug
* compatible for schedulerConfig
0.7.0 / 2013-11-6
=================
* crontab
* global filter
* transaction
* pomelo-cli auto-complete
* some components rename
0.6.8 / 2013-11-4
=================
* update pomelo-admin version
0.6.7 / 2013-10-14
=================
* fix masterha monitor reconnect bug
0.6.6 / 2013-10-12
=================
* merge pull request #303 replace tab & remove session get value argument
* upgrade pomelo-admin to 0.2.4
* upgrade pomelo-monitor to 0.3.7
* upgrade pomelo-rpc to 0.2.2
0.6.5 / 2013-9-30
=================
* fix server reconnect bug
* upgrade pomelo-admin to 0.2.3
0.6.4 / 2013-9-27
=================
* update logger config && test log4js config
* update require pomelo path & unuse module
* merge pull request update readme #295
0.6.3 / 2013-9-10
=================
* fix tcp socket package bug
* update filter parameters
* merge pull request localSession unbind #289
0.6.2 / 2013-9-5
=================
* upgrade pomelo-admin to 0.2.2
* update test cases
* fix socket.on end bug
0.6.1 / 2013-9-2
=================
* update pomelo-admin & pomelo-rpc to 0.2.1
* add rpcDebug module in master
0.6.0 / 2013-8-26
=================
* interactive command line tool
* plugin mechanism
* data signature
* handle invalid connections
* rpc debug log
* overload protection
* servers reconnect mechanism
* daemon start mode
* packages upgrade
0.5.5 / 2013-8-9
=================
* fix sioconnector bug
* fix localSession bug
* merge pull request
0.5.4 / 2013-7-25
=================
* update pomelo-protocol version
0.5.3 / 2013-7-25
=================
* update check forever method
* update socket.io transport
* remove redis dependency for test cases
0.5.2 / 2013-7-23
=================
* fix hybridsocket send message bug
* fix globalChannel nextTick bug
* add some test cases
0.5.1 / 2013-7-19
=================
* update pomelo-protobuf version
* receive servers console data event in production environment
0.5.0 / 2013-7-16
=================
* high availability for master(with zookeeper)
* support global channel(with redis)
* server bind to CPU
* server auto-restart when server does not work(configurable)
* add beforeStop hook for application
0.4.6 / 2013-7-15
=================
* fix pomelo-protocol bug, which will lose message when requestId is 128 multiple
0.4.5 / 2013-7-3
=================
* fix load scheduler component bug
* fix hybridconnector check useDict bug
* add keywords, issues, contributor infos to npm
0.4.3 / 2013-6-13
==================
* fix client heartbeat timeout bug
* fix command line debug argument bug
0.4.2 / 2013-6-5
==================
* fix duplicated bind session bug
* add `disconnectOnTimeout` option for hybridconnector
* fix empty group push bug in channel
* fix protobuf encode bug
0.4.1 / 2013-5-28
==================
* refactor protocol layers
* support multiple sessions of the same user
0.3.10 / 2013-5-20
==================
* `pomelo-protocol` upgrades to 0.3.4
* fix session bind bug in backend server
* replace `childprocess.exec` with `spawn` in `starter.js`
* fix configure bug
0.3.9 / 2013-5-8
==================
* fix configure bug
0.3.8 / 2013-5-6
==================
* fix tcpsocket close event bug
* fix error handler bug
0.3.7 / 2013-4-16
==================
* update templates
* sioconnector supports flashsocket
* add `distinctHost` to hybridconnector
* fix rpc `cacheMsg` configure bugs
0.3.6 / 2013-4-9
==================
* compatible with node 0.10 version
* fix daemon forever bugs
* add some unit test case
0.3.5 / 2013-3-25
==================
* fix log4js not compatible bug
* fix function redefined in localSessionServie
0.3.4 / 2013-3-19
==================
* fix server not verifing useDict, useProtobuf bug
* fix can not start pomelo from ide bug
* add host param in listen for hybridconnector, which is important for some load balance strategy
0.3.3 / 2013-3-12
==================
* fix double string decode bug when not compressing route
0.3.2 / 2013-3-11
==================
* fix init template bug
* modify command line help, version to --help, --version
0.3.1 / 2013-3-7
==================
* add hybridconnector to support socket and websocket
* add route dictionary and protobuf for binary protocol
* add localSession query interfaces
* add broadcast method for ChannelService
0.2.5 / 2013-2-28
==================
* dynamic add and remove servers (watchdog module)
* fix filterService before filter bug
* fix connector component bug
0.2.4 / 2013-1-4
==================
* fix stop components bug
* add windows install .bat
* add comand line windows compatible feature
0.2.3 / 2012-12-25
==================
* add mkdirp, update pomelo-admin version
* solve windows comptaible problem
0.2.2 / 2012-12-9
==================
* add fail ids for channel push method
* code format standardize
0.2.0 / 2012-11-20
==================
* establish project on github
0.1.x / before 2012-11
==================
* internal development for 11 months
================================================
FILE: LICENSE
================================================
(The MIT License)
Copyright (c) 2012-2014 Netease, Inc. and other pomelo contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
## Pomelo -- a fast, scalable game server framework for node.js
Pomelo is a fast, scalable game server framework for [node.js](http://nodejs.org).
It provides the basic development framework and many related components, including libraries and tools.
Pomelo is also suitable for real-time web applications; its distributed architecture makes pomelo scale better than other real-time web frameworks.
[](https://travis-ci.org/NetEase/pomelo)
* Homepage: <http://pomelo.netease.com/>
* Mailing list: <https://groups.google.com/group/pomelo>
* Documentation: <http://github.com/NetEase/pomelo>
* Wiki: <https://github.com/NetEase/pomelo/wiki/>
* Issues: <https://github.com/NetEase/pomelo/issues/>
* Tags: game, nodejs
## Features
### Complete support of game server and realtime application server architecture
* Multiple-player game: mobile, social, web, MMO rpg(middle size)
* Realtime application: chat, message push, etc.
### Fast, scalable
* Distributed (multi-process) architecture, can be easily scale up
* Flexible server extension
* Full performance optimization and test
### Easy
* Simple API: request, response, broadcast, etc.
* Lightweight: high development efficiency based on node.js
* Convention over configuration: almost zero config
### Powerful
* Many clients support, including javascript, flash, android, iOS, cocos2d-x, C
* Many libraries and tools, including command line tool, admin tool, performance test tool, AI, path finding etc.
* 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)
### Extensible
* Support plugin architecture, easy to add new features through plugins. We also provide many plugins like online status, master high availability.
* Custom features, users can define their own network protocol, custom components very easy.
## Why should I use pomelo?
Fast, scalable, real-time game server development is not an easy job, and a good container or framework can reduce its complexity.
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.
Pomelo has the following advantages:
* 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.
* 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.
* 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.
* 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.
## How can I develop with pomelo?
With the following references, you can quickly familiarize yourself with the pomelo development process:
* [Pomelo documents](https://github.com/NetEase/pomelo/wiki)
* [Getting started](https://github.com/NetEase/pomelo/wiki/Welcome-to-Pomelo)
* [Tutorial](https://github.com/NetEase/pomelo/wiki/Preface)
## Contributors
* NetEase, Inc. (@NetEase)
* Peter Johnson(@missinglink)
* Aaron Yoshitake
* @D-Deo
* Eduard Gotwig
* Eric Muyser(@stokegames)
* @GeforceLee
* Harold Jiang(@jzsues)
* @ETiV
* [kaisatec](https://github.com/kaisatec)
* [roytan883](https://github.com/roytan883)
* [wuxian](https://github.com/wuxian)
* [zxc122333](https://github.com/zxc122333)
* [newebug](https://github.com/newebug)
* [jiangzhuo](https://github.com/jiangzhuo)
* [youxiachai](https://github.com/youxiachai)
* [qiankanglai](https://github.com/qiankanglai)
* [xieren58](https://github.com/xieren58)
* [prim](https://github.com/prim)
* [Akaleth](https://github.com/Akaleth)
* [pipi32167](https://github.com/pipi32167)
* [ljhsai](https://github.com/ljhsai)
* [zhanghaojie](https://github.com/zhanghaojie)
* [airandfingers](https://github.com/airandfingers)
## License
(The MIT License)
Copyright (c) 2012-2017 NetEase, Inc. and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: bin/pomelo
================================================
#!/usr/bin/env node
/**
* Module dependencies.
*/
var fs = require('fs'),
os = require('os'),
path = require('path'),
util = require('util'),
cliff = require('cliff'),
mkdirp = require('mkdirp'),
co = require('../lib/modules/console'),
utils = require('../lib/util/utils'),
starter = require('../lib/master/starter'),
exec = require('child_process').exec,
spawn = require('child_process').spawn,
version = require('../package.json').version,
adminClient = require('pomelo-admin').adminClient,
constants = require('../lib/util/constants'),
program = require('commander');
/**
* Constant Variables
*/
var TIME_INIT = 1 * 1000;
var TIME_KILL_WAIT = 5 * 1000;
var KILL_CMD_LUX = 'kill -9 `ps -ef|grep node|awk \'{print $2}\'`';
var KILL_CMD_WIN = 'taskkill /im node.exe /f';
var CUR_DIR = process.cwd();
var DEFAULT_GAME_SERVER_DIR = CUR_DIR;
var DEFAULT_USERNAME = 'admin';
var DEFAULT_PWD = 'admin';
var DEFAULT_ENV = 'development';
var DEFAULT_MASTER_HOST = '127.0.0.1';
var DEFAULT_MASTER_PORT = 3005;
var CONNECT_ERROR = 'Fail to connect to admin console server.';
var FILEREAD_ERROR = 'Fail to read the file, please check if the application is started legally.';
var CLOSEAPP_INFO = 'Closing the application......\nPlease wait......';
var ADD_SERVER_INFO = 'Successfully add server.';
var RESTART_SERVER_INFO = 'Successfully restart server.';
var INIT_PROJ_NOTICE = '\nThe default admin user is: \n\n'+ ' username'.green + ': admin\n ' + 'password'.green+ ': admin\n\nYou can configure admin users by editing adminUser.json later.\n ';
var SCRIPT_NOT_FOUND = 'Fail to find an appropriate script to run,\nplease check the current work directory or the directory specified by option `--directory`.\n'.red;
var MASTER_HA_NOT_FOUND = 'Fail to find an appropriate masterha config file, \nplease check the current work directory or the arguments passed to.\n'.red;
var COMMAND_ERROR = 'Illegal command format. Use `pomelo --help` to get more info.\n'.red;
var DAEMON_INFO = 'The application is running in the background now.\n';
program.version(version);
program.command('init [path]')
.description('create a new application')
.action(function(path) {
init(path || CUR_DIR);
});
program.command('start')
.description('start the application')
.option('-e, --env <env>', 'the used environment', DEFAULT_ENV)
.option('-D, --daemon', 'enable the daemon start')
.option('-d, --directory, <directory>', 'the code directory', DEFAULT_GAME_SERVER_DIR)
.option('-t, --type <server-type>,', 'start server type')
.option('-i, --id <server-id>', 'start server id')
.action(function(opts) {
start(opts);
});
program.command('list')
.description('list the servers')
.option('-u, --username <username>', 'administration user name', DEFAULT_USERNAME)
.option('-p, --password <password>', 'administration password', DEFAULT_PWD)
.option('-h, --host <master-host>', 'master server host', DEFAULT_MASTER_HOST)
.option('-P, --port <master-port>', 'master server port', DEFAULT_MASTER_PORT)
.action(function(opts) {
list(opts);
});
program.command('add')
.description('add a new server')
.option('-u, --username <username>', 'administration user name', DEFAULT_USERNAME)
.option('-p, --password <password>', 'administration password', DEFAULT_PWD)
.option('-h, --host <master-host>', 'master server host', DEFAULT_MASTER_HOST)
.option('-P, --port <master-port>', 'master server port', DEFAULT_MASTER_PORT)
.action(function() {
var args = [].slice.call(arguments, 0);
var opts = args[args.length - 1];
opts.args = args.slice(0, -1);
add(opts);
});
program.command('stop')
.description('stop the servers, for multiple servers, use `pomelo stop server-id-1 server-id-2`')
.option('-u, --username <username>', 'administration user name', DEFAULT_USERNAME)
.option('-p, --password <password>', 'administration password', DEFAULT_PWD)
.option('-h, --host <master-host>', 'master server host', DEFAULT_MASTER_HOST)
.option('-P, --port <master-port>', 'master server port', DEFAULT_MASTER_PORT)
.action(function() {
var args = [].slice.call(arguments, 0);
var opts = args[args.length - 1];
opts.serverIds = args.slice(0, -1);
terminal('stop', opts);
});
program.command('kill')
.description('kill the application')
.option('-u, --username <username>', 'administration user name', DEFAULT_USERNAME)
.option('-p, --password <password>', 'administration password', DEFAULT_PWD)
.option('-h, --host <master-host>', 'master server host', DEFAULT_MASTER_HOST)
.option('-P, --port <master-port>', 'master server port', DEFAULT_MASTER_PORT)
.option('-f, --force', 'using this option would kill all the node processes')
.action(function() {
var args = [].slice.call(arguments, 0);
var opts = args[args.length - 1];
opts.serverIds = args.slice(0, -1);
terminal('kill', opts);
});
program.command('restart')
.description('restart the servers, for multiple servers, use `pomelo restart server-id-1 server-id-2`')
.option('-u, --username <username>', 'administration user name', DEFAULT_USERNAME)
.option('-p, --password <password>', 'administration password', DEFAULT_PWD)
.option('-h, --host <master-host>', 'master server host', DEFAULT_MASTER_HOST)
.option('-P, --port <master-port>', 'master server port', DEFAULT_MASTER_PORT)
.option('-t, --type <server-type>,', 'start server type')
.option('-i, --id <server-id>', 'start server id')
.action(function(opts) {
restart(opts);
});
program.command('masterha')
.description('start all the slaves of the master')
.option('-d, --directory <directory>', 'the code directory', DEFAULT_GAME_SERVER_DIR)
.action(function(opts) {
startMasterha(opts);
});
program.command('*')
.action(function() {
console.log(COMMAND_ERROR);
});
program.parse(process.argv);
/**
* Init application at the given directory `path`.
*
* @param {String} path
*/
function init(path) {
console.log(INIT_PROJ_NOTICE);
connectorType(function(type) {
emptyDirectory(path, function(empty) {
if(empty) {
process.stdin.destroy();
createApplicationAt(path, type);
} else {
confirm('Destination is not empty, continue? (y/n) [no] ', function(force) {
process.stdin.destroy();
if(force) {
createApplicationAt(path, type);
} else {
abort('Fail to init a project'.red);
}
});
}
});
});
}
/**
* Create directory and files at the given directory `path`.
*
* @param {String} ph
*/
function createApplicationAt(ph, type) {
var name = path.basename(path.resolve(CUR_DIR, ph));
copy(path.join(__dirname, '../template/'), ph);
mkdir(path.join(ph, 'game-server/logs'));
mkdir(path.join(ph, 'shared'));
// rmdir -r
var rmdir = function(dir) {
var list = fs.readdirSync(dir);
for(var i = 0; i < list.length; i++) {
var filename = path.join(dir, list[i]);
var stat = fs.statSync(filename);
if(filename === "." || filename === "..") {
} else if(stat.isDirectory()) {
rmdir(filename);
} else {
fs.unlinkSync(filename);
}
}
fs.rmdirSync(dir);
};
setTimeout(function() {
switch(type) {
case '1':
// use websocket
var unlinkFiles = ['game-server/app.js.sio',
'game-server/app.js.wss',
'game-server/app.js.mqtt',
'game-server/app.js.sio.wss',
'game-server/app.js.udp',
'web-server/app.js.https',
'web-server/public/index.html.sio',
'web-server/public/js/lib/pomeloclient.js',
'web-server/public/js/lib/pomeloclient.js.wss',
'web-server/public/js/lib/build/build.js.wss',
'web-server/public/js/lib/socket.io.js'];
for(var i = 0; i < unlinkFiles.length; ++i) {
fs.unlinkSync(path.resolve(ph, unlinkFiles[i]));
}
break;
case '2':
// use socket.io
var unlinkFiles = ['game-server/app.js',
'game-server/app.js.wss',
'game-server/app.js.udp',
'game-server/app.js.mqtt',
'game-server/app.js.sio.wss',
'web-server/app.js.https',
'web-server/public/index.html',
'web-server/public/js/lib/component.json',
'web-server/public/js/lib/pomeloclient.js.wss'];
for(var i = 0; i < unlinkFiles.length; ++i) {
fs.unlinkSync(path.resolve(ph, unlinkFiles[i]));
}
fs.renameSync(path.resolve(ph, 'game-server/app.js.sio'), path.resolve(ph, 'game-server/app.js'));
fs.renameSync(path.resolve(ph, 'web-server/public/index.html.sio'), path.resolve(ph, 'web-server/public/index.html'));
rmdir(path.resolve(ph, 'web-server/public/js/lib/build'));
rmdir(path.resolve(ph, 'web-server/public/js/lib/local'));
break;
case '3':
// use websocket wss
var unlinkFiles = ['game-server/app.js.sio',
'game-server/app.js',
'game-server/app.js.udp',
'game-server/app.js.sio.wss',
'game-server/app.js.mqtt',
'web-server/app.js',
'web-server/public/index.html.sio',
'web-server/public/js/lib/pomeloclient.js',
'web-server/public/js/lib/pomeloclient.js.wss',
'web-server/public/js/lib/build/build.js',
'web-server/public/js/lib/socket.io.js'];
for(var i = 0; i < unlinkFiles.length; ++i) {
fs.unlinkSync(path.resolve(ph, unlinkFiles[i]));
}
fs.renameSync(path.resolve(ph, 'game-server/app.js.wss'), path.resolve(ph, 'game-server/app.js'));
fs.renameSync(path.resolve(ph, 'web-server/app.js.https'), path.resolve(ph, 'web-server/app.js'));
fs.renameSync(path.resolve(ph, 'web-server/public/js/lib/build/build.js.wss'), path.resolve(ph, 'web-server/public/js/lib/build/build.js'));
break;
case '4':
// use socket.io wss
var unlinkFiles = ['game-server/app.js.sio',
'game-server/app.js',
'game-server/app.js.udp',
'game-server/app.js.wss',
'game-server/app.js.mqtt',
'web-server/app.js',
'web-server/public/index.html',
'web-server/public/js/lib/pomeloclient.js'];
for(var i = 0; i < unlinkFiles.length; ++i) {
fs.unlinkSync(path.resolve(ph, unlinkFiles[i]));
}
fs.renameSync(path.resolve(ph, 'game-server/app.js.sio.wss'), path.resolve(ph, 'game-server/app.js'));
fs.renameSync(path.resolve(ph, 'web-server/app.js.https'), path.resolve(ph, 'web-server/app.js'));
fs.renameSync(path.resolve(ph, 'web-server/public/index.html.sio'), path.resolve(ph, 'web-server/public/index.html'));
fs.renameSync(path.resolve(ph, 'web-server/public/js/lib/pomeloclient.js.wss'), path.resolve(ph, 'web-server/public/js/lib/pomeloclient.js'));
rmdir(path.resolve(ph, 'web-server/public/js/lib/build'));
rmdir(path.resolve(ph, 'web-server/public/js/lib/local'));
fs.unlinkSync(path.resolve(ph, 'web-server/public/js/lib/component.json'));
break;
case '5':
// use socket.io wss
var unlinkFiles = ['game-server/app.js.sio',
'game-server/app.js',
'game-server/app.js.wss',
'game-server/app.js.mqtt',
'game-server/app.js.sio.wss',
'web-server/app.js.https',
'web-server/public/index.html',
'web-server/public/js/lib/component.json',
'web-server/public/js/lib/pomeloclient.js.wss'];
for(var i = 0; i < unlinkFiles.length; ++i) {
fs.unlinkSync(path.resolve(ph, unlinkFiles[i]));
}
fs.renameSync(path.resolve(ph, 'game-server/app.js.udp'), path.resolve(ph, 'game-server/app.js'));
rmdir(path.resolve(ph, 'web-server/public/js/lib/build'));
rmdir(path.resolve(ph, 'web-server/public/js/lib/local'));
break;
case '6':
// use socket.io
var unlinkFiles = ['game-server/app.js',
'game-server/app.js.wss',
'game-server/app.js.udp',
'game-server/app.js.sio',
'game-server/app.js.sio.wss',
'web-server/app.js.https',
'web-server/public/index.html',
'web-server/public/js/lib/component.json',
'web-server/public/js/lib/pomeloclient.js.wss'];
for(var i = 0; i < unlinkFiles.length; ++i) {
fs.unlinkSync(path.resolve(ph, unlinkFiles[i]));
}
fs.renameSync(path.resolve(ph, 'game-server/app.js.mqtt'), path.resolve(ph, 'game-server/app.js'));
fs.renameSync(path.resolve(ph, 'web-server/public/index.html.sio'), path.resolve(ph, 'web-server/public/index.html'));
rmdir(path.resolve(ph, 'web-server/public/js/lib/build'));
rmdir(path.resolve(ph, 'web-server/public/js/lib/local'));
break;
}
var replaceFiles = ['game-server/app.js',
'game-server/package.json',
'web-server/package.json'];
for(var j = 0; j < replaceFiles.length; j++) {
var str = fs.readFileSync(path.resolve(ph, replaceFiles[j])).toString();
fs.writeFileSync(path.resolve(ph, replaceFiles[j]), str.replace('$', name));
}
var f = path.resolve(ph, 'game-server/package.json');
var content = fs.readFileSync(f).toString();
fs.writeFileSync(f, content.replace('#', version));
}, TIME_INIT);
}
/**
* Start application.
*
* @param {Object} opts options for `start` operation
*/
function start(opts) {
var absScript = path.resolve(opts.directory, 'app.js');
if (!fs.existsSync(absScript)) {
abort(SCRIPT_NOT_FOUND);
}
var logDir = path.resolve(opts.directory, 'logs');
if (!fs.existsSync(logDir)) {
mkdir(logDir);
}
var ls;
var type = opts.type || constants.RESERVED.ALL;
var params = [absScript, 'env=' + opts.env, 'type=' + type];
if(!!opts.id) {
params.push('startId=' + opts.id);
}
if (opts.daemon) {
ls = spawn(process.execPath, params, {detached: true, stdio: 'ignore'});
ls.unref();
console.log(DAEMON_INFO);
process.exit(0);
} else {
ls = spawn(process.execPath, params);
ls.stdout.on('data', function(data) {
console.log(data.toString());
});
ls.stderr.on('data', function(data) {
console.log(data.toString());
});
}
}
/**
* List pomelo processes.
*
* @param {Object} opts options for `list` operation
*/
function list(opts) {
var id = 'pomelo_list_' + Date.now();
connectToMaster(id, opts, function(client) {
client.request(co.moduleId, {signal: 'list'}, function(err, data) {
if(err) {
console.error(err);
}
var servers = [];
for(var key in data.msg) {
servers.push(data.msg[key]);
}
var comparer = function(a, b) {
if (a.serverType < b.serverType) {
return -1;
} else if (a.serverType > b.serverType) {
return 1;
} else if (a.serverId < b.serverId) {
return -1;
} else if (a.serverId > b.serverId) {
return 1;
} else {
return 0;
}
};
servers.sort(comparer);
var rows = [];
rows.push(['serverId', 'serverType', 'pid', 'rss(M)', 'heapTotal(M)', 'heapUsed(M)', 'uptime(m)']);
servers.forEach(function(server) {
rows.push([server.serverId, server.serverType, server.pid, server.rss, server.heapTotal, server.heapUsed, server.uptime]);
});
console.log(cliff.stringifyRows(rows, ['red', 'blue', 'green', 'cyan', 'magenta', 'white', 'yellow']));
process.exit(0);
});
});
}
/**
* Add server to application.
*
* @param {Object} opts options for `add` operation
*/
function add(opts) {
var id = 'pomelo_add_' + Date.now();
connectToMaster(id, opts, function(client) {
client.request(co.moduleId, { signal: 'add', args: opts.args }, function(err) {
if(err) {
console.error(err);
}
else {
console.info(ADD_SERVER_INFO);
}
process.exit(0);
});
});
}
/**
* Terminal application.
*
* @param {String} signal stop/kill
* @param {Object} opts options for `stop/kill` operation
*/
function terminal(signal, opts) {
console.info(CLOSEAPP_INFO);
// option force just for `kill`
if(opts.force) {
if (os.platform() === constants.PLATFORM.WIN) {
exec(KILL_CMD_WIN);
} else {
exec(KILL_CMD_LUX);
}
process.exit(1);
return;
}
var id = 'pomelo_terminal_' + Date.now();
connectToMaster(id, opts, function(client) {
client.request(co.moduleId, {
signal: signal, ids: opts.serverIds
}, function(err, msg) {
if(err) {
console.error(err);
}
if(signal === 'kill') {
if(msg.code === 'ok') {
console.log('All the servers have been terminated!');
} else {
console.log('There may be some servers remained:', msg.serverIds);
}
}
process.exit(0);
});
});
}
function restart(opts) {
var id = 'pomelo_restart_' + Date.now();
var serverIds = [];
var type = null;
if(!!opts.id) {
serverIds.push(opts.id);
}
if(!!opts.type) {
type = opts.type;
}
connectToMaster(id, opts, function(client) {
client.request(co.moduleId, { signal: 'restart', ids: serverIds, type: type}, function(err, fails) {
if(!!err) {
console.error(err);
} else if(!!fails.length) {
console.info('restart fails server ids: %j', fails);
} else {
console.info(RESTART_SERVER_INFO);
}
process.exit(0);
});
});
}
function connectToMaster(id, opts, cb) {
var client = new adminClient({username: opts.username, password: opts.password, md5: true});
client.connect(id, opts.host, opts.port, function(err) {
if(err) {
abort(CONNECT_ERROR + err.red);
}
if(typeof cb === 'function') {
cb(client);
}
});
}
/**
* Start master slaves.
*
* @param {String} option for `startMasterha` operation
*/
function startMasterha(opts) {
var configFile = path.join(opts.directory, constants.FILEPATH.MASTER_HA);
if(!fs.existsSync(configFile)) {
abort(MASTER_HA_NOT_FOUND);
}
var masterha = require(configFile).masterha;
for(var i=0; i<masterha.length; i++) {
var server = masterha[i];
server.mode = constants.RESERVED.STAND_ALONE;
server.masterha = 'true';
server.home = opts.directory;
runServer(server);
}
}
/**
* Check if the given directory `path` is empty.
*
* @param {String} path
* @param {Function} fn
*/
function emptyDirectory(path, fn) {
fs.readdir(path, function(err, files) {
if(err && 'ENOENT' !== err.code) {
abort(FILEREAD_ERROR);
}
fn(!files || !files.length);
});
}
/**
* Prompt confirmation with the given `msg`.
*
* @param {String} msg
* @param {Function} fn
*/
function confirm(msg, fn) {
prompt(msg, function(val) {
fn(/^ *y(es)?/i.test(val));
});
}
/**
* Prompt input with the given `msg` and callback `fn`.
*
* @param {String} msg
* @param {Function} fn
*/
function prompt(msg, fn) {
if(' ' === msg[msg.length - 1]) {
process.stdout.write(msg);
} else {
console.log(msg);
}
process.stdin.setEncoding('ascii');
process.stdin.once('data', function(data) {
fn(data);
}).resume();
}
/**
* Exit with the given `str`.
*
* @param {String} str
*/
function abort(str) {
console.error(str);
process.exit(1);
}
/**
* Copy template files to project.
*
* @param {String} origin
* @param {String} target
*/
function copy(origin, target) {
if(!fs.existsSync(origin)) {
abort(origin + 'does not exist.');
}
if(!fs.existsSync(target)) {
mkdir(target);
console.log(' create : '.green + target);
}
fs.readdir(origin, function(err, datalist) {
if(err) {
abort(FILEREAD_ERROR);
}
for(var i = 0; i < datalist.length; i++) {
var oCurrent = path.resolve(origin, datalist[i]);
var tCurrent = path.resolve(target, datalist[i]);
if(fs.statSync(oCurrent).isFile()) {
fs.writeFileSync(tCurrent, fs.readFileSync(oCurrent, ''), '');
console.log(' create : '.green + tCurrent);
} else if(fs.statSync(oCurrent).isDirectory()) {
copy(oCurrent, tCurrent);
}
}
});
}
/**
* Mkdir -p.
*
* @param {String} path
* @param {Function} fn
*/
function mkdir(path, fn) {
mkdirp(path, 0755, function(err){
if(err) {
throw err;
}
console.log(' create : '.green + path);
if(typeof fn === 'function') {
fn();
}
});
}
/**
* Get user's choice on connector selecting
*
* @param {Function} cb
*/
function connectorType(cb) {
prompt('Please select underly connector, 1 for websocket(native socket), 2 for socket.io, 3 for wss, 4 for socket.io(wss), 5 for udp, 6 for mqtt: [1]', function(msg) {
switch(msg.trim()) {
case '':
cb(1);
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
cb(msg.trim());
break;
default:
console.log('Invalid choice! Please input 1 - 5.'.red + '\n');
connectorType(cb);
break;
}
});
}
/**
* Run server.
*
* @param {Object} server server information
*/
function runServer(server) {
var cmd, key;
var main = path.resolve(server.home, 'app.js');
if(utils.isLocal(server.host)) {
var options = [];
options.push(main);
for(key in server) {
options.push(util.format('%s=%s', key, server[key]));
}
starter.localrun(process.execPath, null, options);
} else {
cmd = util.format('cd "%s" && "%s"', server.home, process.execPath);
cmd += util.format(' "%s" ', main);
for(key in server) {
cmd += util.format(' %s=%s ', key, server[key]);
}
starter.sshrun(cmd, server.host);
}
}
================================================
FILE: coverage/blanket.js
================================================
var path = require('path');
var srcDir = path.join(__dirname, '..', 'lib');
require('blanket')({
pattern: srcDir
});
================================================
FILE: gruntfile.js
================================================
'use strict';
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-mocha-test');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-jshint');
var src = ['test/manager/taskManager.js', 'test/filters/*.js',
'test/remote/*.js', 'test/service/*.js', 'test/modules/*.js', 'test/util/*.js', 'test/*.js'];
// Project configuration.
grunt.initConfig({
mochaTest: {
test: {
options: {
reporter: 'spec',
timeout: 5000,
require: 'coverage/blanket'
},
src: src
},
coverage: {
options: {
reporter: 'html-cov',
quiet: true,
captureFile: 'coverage.html'
},
src: src
}
},
clean: {
"coverage.html" : {
src: ['coverage.html']
}
},
jshint: {
all: ['lib/*']
}
});
// Default task.
grunt.registerTask('default', ['clean', 'mochaTest', 'jshint']);
};
================================================
FILE: index.js
================================================
module.exports = require('./lib/pomelo');
================================================
FILE: lib/application.js
================================================
/*!
* Pomelo -- proto
* Copyright(c) 2012 xiechengchao <xiecc@163.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var utils = require('./util/utils');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
var EventEmitter = require('events').EventEmitter;
var events = require('./util/events');
var appUtil = require('./util/appUtil');
var Constants = require('./util/constants');
var appManager = require('./common/manager/appManager');
var fs = require('fs');
var path = require('path');
/**
* Application prototype.
*
* @module
*/
var Application = module.exports = {};
/**
* Application states
*/
var STATE_INITED = 1; // app has inited
var STATE_START = 2; // app start
var STATE_STARTED = 3; // app has started
var STATE_STOPED = 4; // app has stoped
/**
* Initialize the server.
*
* - setup default configuration
*/
Application.init = function(opts) {
opts = opts || {};
this.loaded = []; // loaded component list
this.components = {}; // name -> component map
this.settings = {}; // collection keep set/get
var base = opts.base || path.dirname(require.main.filename);
this.set(Constants.RESERVED.BASE, base, true);
this.event = new EventEmitter(); // event object to sub/pub events
// current server info
this.serverId = null; // current server id
this.serverType = null; // current server type
this.curServer = null; // current server info
this.startTime = null; // current server start time
// global server infos
this.master = null; // master server info
this.servers = {}; // current global server info maps, id -> info
this.serverTypeMaps = {}; // current global type maps, type -> [info]
this.serverTypes = []; // current global server type list
this.lifecycleCbs = {}; // current server custom lifecycle callbacks
this.clusterSeq = {}; // cluster id seqence
appUtil.defaultConfiguration(this);
this.state = STATE_INITED;
logger.info('application inited: %j', this.getServerId());
};
/**
* Get application base path
*
* // cwd: /home/game/
* pomelo start
* // app.getBase() -> /home/game
*
* @return {String} application base path
*
* @memberOf Application
*/
Application.getBase = function() {
return this.get(Constants.RESERVED.BASE);
};
/**
* Override require method in application
*
* @param {String} relative path of file
*
* @memberOf Application
*/
Application.require = function(ph) {
return require(path.join(Application.getBase(), ph));
};
/**
* Configure logger with {$base}/config/log4js.json
*
* @param {Object} logger pomelo-logger instance without configuration
*
* @memberOf Application
*/
Application.configureLogger = function(logger) {
if (process.env.POMELO_LOGGER !== 'off') {
var base = this.getBase();
var env = this.get(Constants.RESERVED.ENV);
var originPath = path.join(base, Constants.FILEPATH.LOG);
var presentPath = path.join(base, Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.LOG));
if(fs.existsSync(originPath)) {
logger.configure(originPath, {serverId: this.serverId, base: base});
} else if(fs.existsSync(presentPath)) {
logger.configure(presentPath, {serverId: this.serverId, base: base});
} else {
logger.error('logger file path configuration is error.');
}
}
};
/**
* add a filter to before and after filter
*
* @param {Object} filter provide before and after filter method.
* A filter should have two methods: before and after.
* @memberOf Application
*/
Application.filter = function (filter) {
this.before(filter);
this.after(filter);
};
/**
* Add before filter.
*
* @param {Object|Function} bf before fileter, bf(msg, session, next)
* @memberOf Application
*/
Application.before = function (bf) {
addFilter(this, Constants.KEYWORDS.BEFORE_FILTER, bf);
};
/**
* Add after filter.
*
* @param {Object|Function} af after filter, `af(err, msg, session, resp, next)`
* @memberOf Application
*/
Application.after = function (af) {
addFilter(this, Constants.KEYWORDS.AFTER_FILTER, af);
};
/**
* add a global filter to before and after global filter
*
* @param {Object} filter provide before and after filter method.
* A filter should have two methods: before and after.
* @memberOf Application
*/
Application.globalFilter = function (filter) {
this.globalBefore(filter);
this.globalAfter(filter);
};
/**
* Add global before filter.
*
* @param {Object|Function} bf before fileter, bf(msg, session, next)
* @memberOf Application
*/
Application.globalBefore = function (bf) {
addFilter(this, Constants.KEYWORDS.GLOBAL_BEFORE_FILTER, bf);
};
/**
* Add global after filter.
*
* @param {Object|Function} af after filter, `af(err, msg, session, resp, next)`
* @memberOf Application
*/
Application.globalAfter = function (af) {
addFilter(this, Constants.KEYWORDS.GLOBAL_AFTER_FILTER, af);
};
/**
* Add rpc before filter.
*
* @param {Object|Function} bf before fileter, bf(serverId, msg, opts, next)
* @memberOf Application
*/
Application.rpcBefore = function(bf) {
addFilter(this, Constants.KEYWORDS.RPC_BEFORE_FILTER, bf);
};
/**
* Add rpc after filter.
*
* @param {Object|Function} af after filter, `af(serverId, msg, opts, next)`
* @memberOf Application
*/
Application.rpcAfter = function(af) {
addFilter(this, Constants.KEYWORDS.RPC_AFTER_FILTER, af);
};
/**
* add a rpc filter to before and after rpc filter
*
* @param {Object} filter provide before and after filter method.
* A filter should have two methods: before and after.
* @memberOf Application
*/
Application.rpcFilter = function(filter) {
this.rpcBefore(filter);
this.rpcAfter(filter);
};
/**
* Load component
*
* @param {String} name (optional) name of the component
* @param {Object} component component instance or factory function of the component
* @param {[type]} opts (optional) construct parameters for the factory function
* @return {Object} app instance for chain invoke
* @memberOf Application
*/
Application.load = function(name, component, opts) {
if(typeof name !== 'string') {
opts = component;
component = name;
name = null;
if(typeof component.name === 'string') {
name = component.name;
}
}
if(typeof component === 'function') {
component = component(this, opts);
}
if(!name && typeof component.name === 'string') {
name = component.name;
}
if(name && this.components[name]) {
// ignore duplicat component
logger.warn('ignore duplicate component: %j', name);
return;
}
this.loaded.push(component);
if(name) {
// components with a name would get by name throught app.components later.
this.components[name] = component;
}
return this;
};
/**
* Load Configure json file to settings.(support different enviroment directory & compatible for old path)
*
* @param {String} key environment key
* @param {String} val environment value
* @param {Boolean} reload whether reload after change default false
* @return {Server|Mixed} for chaining, or the setting value
* @memberOf Application
*/
Application.loadConfigBaseApp = function (key, val, reload) {
var self = this;
var env = this.get(Constants.RESERVED.ENV);
var originPath = path.join(Application.getBase(), val);
var presentPath = path.join(Application.getBase(), Constants.FILEPATH.CONFIG_DIR, env, path.basename(val));
var realPath;
if(fs.existsSync(originPath)) {
realPath = originPath;
var file = require(originPath);
if (file[env]) {
file = file[env];
}
this.set(key, file);
} else if(fs.existsSync(presentPath)) {
realPath = presentPath;
var pfile = require(presentPath);
this.set(key, pfile);
} else {
logger.error('invalid configuration with file path: %s', key);
}
if(!!realPath && !!reload) {
fs.watch(realPath, function (event, filename) {
if(event === 'change') {
delete require.cache[require.resolve(realPath)];
self.loadConfigBaseApp(key, val);
}
});
}
};
/**
* Load Configure json file to settings.
*
* @param {String} key environment key
* @param {String} val environment value
* @return {Server|Mixed} for chaining, or the setting value
* @memberOf Application
*/
Application.loadConfig = function(key, val) {
var env = this.get(Constants.RESERVED.ENV);
val = require(val);
if (val[env]) {
val = val[env];
}
this.set(key, val);
};
/**
* Set the route function for the specified server type.
*
* Examples:
*
* app.route('area', routeFunc);
*
* var routeFunc = function(session, msg, app, cb) {
* // all request to area would be route to the first area server
* var areas = app.getServersByType('area');
* cb(null, areas[0].id);
* };
*
* @param {String} serverType server type string
* @param {Function} routeFunc route function. routeFunc(session, msg, app, cb)
* @return {Object} current application instance for chain invoking
* @memberOf Application
*/
Application.route = function(serverType, routeFunc) {
var routes = this.get(Constants.KEYWORDS.ROUTE);
if(!routes) {
routes = {};
this.set(Constants.KEYWORDS.ROUTE, routes);
}
routes[serverType] = routeFunc;
return this;
};
/**
* Set before stop function. It would perform before servers stop.
*
* @param {Function} fun before close function
* @return {Void}
* @memberOf Application
*/
Application.beforeStopHook = function(fun) {
logger.warn('this method was deprecated in pomelo 0.8');
if(!!fun && typeof fun === 'function') {
this.set(Constants.KEYWORDS.BEFORE_STOP_HOOK, fun);
}
};
/**
* Start application. It would load the default components and start all the loaded components.
*
* @param {Function} cb callback function
* @memberOf Application
*/
Application.start = function(cb) {
this.startTime = Date.now();
if(this.state > STATE_INITED) {
utils.invokeCallback(cb, new Error('application has already start.'));
return;
}
var self = this;
appUtil.startByType(self, function() {
appUtil.loadDefaultComponents(self);
var startUp = function() {
appUtil.optComponents(self.loaded, Constants.RESERVED.START, function(err) {
self.state = STATE_START;
if(err) {
utils.invokeCallback(cb, err);
} else {
logger.info('%j enter after start...', self.getServerId());
self.afterStart(cb);
}
});
};
var beforeFun = self.lifecycleCbs[Constants.LIFECYCLE.BEFORE_STARTUP];
if(!!beforeFun) {
beforeFun.call(null, self, startUp);
} else {
startUp();
}
});
};
/**
* Lifecycle callback for after start.
*
* @param {Function} cb callback function
* @return {Void}
*/
Application.afterStart = function(cb) {
if(this.state !== STATE_START) {
utils.invokeCallback(cb, new Error('application is not running now.'));
return;
}
var afterFun = this.lifecycleCbs[Constants.LIFECYCLE.AFTER_STARTUP];
var self = this;
appUtil.optComponents(this.loaded, Constants.RESERVED.AFTER_START, function(err) {
self.state = STATE_STARTED;
var id = self.getServerId();
if(!err) {
logger.info('%j finish start', id);
}
if(!!afterFun) {
afterFun.call(null, self, function() {
utils.invokeCallback(cb, err);
});
} else {
utils.invokeCallback(cb, err);
}
var usedTime = Date.now() - self.startTime;
logger.info('%j startup in %s ms', id, usedTime);
self.event.emit(events.START_SERVER, id);
});
};
/**
* Stop components.
*
* @param {Boolean} force whether stop the app immediately
*/
Application.stop = function(force) {
if(this.state > STATE_STARTED) {
logger.warn('[pomelo application] application is not running now.');
return;
}
this.state = STATE_STOPED;
var self = this;
this.stopTimer = setTimeout(function() {
process.exit(0);
}, Constants.TIME.TIME_WAIT_STOP);
var cancelShutDownTimer =function(){
if(!!self.stopTimer) {
clearTimeout(self.stopTimer);
}
};
var shutDown = function() {
appUtil.stopComps(self.loaded, 0, force, function() {
cancelShutDownTimer();
if(force) {
process.exit(0);
}
});
};
var fun = this.get(Constants.KEYWORDS.BEFORE_STOP_HOOK);
var stopFun = this.lifecycleCbs[Constants.LIFECYCLE.BEFORE_SHUTDOWN];
if(!!stopFun) {
stopFun.call(null, this, shutDown, cancelShutDownTimer);
} else if(!!fun) {
utils.invokeCallback(fun, self, shutDown, cancelShutDownTimer);
} else {
shutDown();
}
};
/**
* Assign `setting` to `val`, or return `setting`'s value.
*
* Example:
*
* app.set('key1', 'value1');
* app.get('key1'); // 'value1'
* app.key1; // undefined
*
* app.set('key2', 'value2', true);
* app.get('key2'); // 'value2'
* app.key2; // 'value2'
*
* @param {String} setting the setting of application
* @param {String} val the setting's value
* @param {Boolean} attach whether attach the settings to application
* @return {Server|Mixed} for chaining, or the setting value
* @memberOf Application
*/
Application.set = function (setting, val, attach) {
if (arguments.length === 1) {
return this.settings[setting];
}
this.settings[setting] = val;
if(attach) {
this[setting] = val;
}
return this;
};
/**
* Get property from setting
*
* @param {String} setting application setting
* @return {String} val
* @memberOf Application
*/
Application.get = function (setting) {
return this.settings[setting];
};
/**
* Check if `setting` is enabled.
*
* @param {String} setting application setting
* @return {Boolean}
* @memberOf Application
*/
Application.enabled = function (setting) {
return !!this.get(setting);
};
/**
* Check if `setting` is disabled.
*
* @param {String} setting application setting
* @return {Boolean}
* @memberOf Application
*/
Application.disabled = function (setting) {
return !this.get(setting);
};
/**
* Enable `setting`.
*
* @param {String} setting application setting
* @return {app} for chaining
* @memberOf Application
*/
Application.enable = function (setting) {
return this.set(setting, true);
};
/**
* Disable `setting`.
*
* @param {String} setting application setting
* @return {app} for chaining
* @memberOf Application
*/
Application.disable = function (setting) {
return this.set(setting, false);
};
/**
* Configure callback for the specified env and server type.
* When no env is specified that callback will
* be invoked for all environments and when no type is specified
* that callback will be invoked for all server types.
*
* Examples:
*
* app.configure(function(){
* // executed for all envs and server types
* });
*
* app.configure('development', function(){
* // executed development env
* });
*
* app.configure('development', 'connector', function(){
* // executed for development env and connector server type
* });
*
* @param {String} env application environment
* @param {Function} fn callback function
* @param {String} type server type
* @return {Application} for chaining
* @memberOf Application
*/
Application.configure = function (env, type, fn) {
var args = [].slice.call(arguments);
fn = args.pop();
env = type = Constants.RESERVED.ALL;
if(args.length > 0) {
env = args[0];
}
if(args.length > 1) {
type = args[1];
}
if (env === Constants.RESERVED.ALL || contains(this.settings.env, env)) {
if (type === Constants.RESERVED.ALL || contains(this.settings.serverType, type)) {
fn.call(this);
}
}
return this;
};
/**
* Register admin modules. Admin modules is the extends point of the monitor system.
*
* @param {String} module (optional) module id or provoided by module.moduleId
* @param {Object} module module object or factory function for module
* @param {Object} opts construct parameter for module
* @memberOf Application
*/
Application.registerAdmin = function(moduleId, module, opts) {
var modules = this.get(Constants.KEYWORDS.MODULE);
if(!modules) {
modules = {};
this.set(Constants.KEYWORDS.MODULE, modules);
}
if(typeof moduleId !== 'string') {
opts = module;
module = moduleId;
if(module) {
moduleId = module.moduleId;
}
}
if(!moduleId){
return;
}
modules[moduleId] = {
moduleId: moduleId,
module: module,
opts: opts
};
};
/**
* Use plugin.
*
* @param {Object} plugin plugin instance
* @param {[type]} opts (optional) construct parameters for the factory function
* @memberOf Application
*/
Application.use = function(plugin, opts) {
if(!plugin.components) {
logger.error('invalid components, no components exist');
return;
}
var self = this;
opts = opts || {};
var dir = path.dirname(plugin.components);
if(!fs.existsSync(plugin.components)) {
logger.error('fail to find components, find path: %s', plugin.components);
return;
}
fs.readdirSync(plugin.components).forEach(function (filename) {
if (!/\.js$/.test(filename)) {
return;
}
var name = path.basename(filename, '.js');
var param = opts[name] || {};
var absolutePath = path.join(dir, Constants.DIR.COMPONENT, filename);
if(!fs.existsSync(absolutePath)) {
logger.error('component %s not exist at %s', name, absolutePath);
} else {
self.load(require(absolutePath), param);
}
});
// load events
if(!plugin.events) {
return;
} else {
if(!fs.existsSync(plugin.events)) {
logger.error('fail to find events, find path: %s', plugin.events);
return;
}
fs.readdirSync(plugin.events).forEach(function (filename) {
if (!/\.js$/.test(filename)) {
return;
}
var absolutePath = path.join(dir, Constants.DIR.EVENT, filename);
if(!fs.existsSync(absolutePath)) {
logger.error('events %s not exist at %s', filename, absolutePath);
} else {
bindEvents(require(absolutePath), self);
}
});
}
};
/**
* Application transaction. Transcation includes conditions and handlers, if conditions are satisfied, handlers would be executed.
* And you can set retry times to execute handlers. The transaction log is in file logs/transaction.log.
*
* @param {String} name transaction name
* @param {Object} conditions functions which are called before transaction
* @param {Object} handlers functions which are called during transaction
* @param {Number} retry retry times to execute handlers if conditions are successfully executed
* @memberOf Application
*/
Application.transaction = function(name, conditions, handlers, retry) {
appManager.transaction(name, conditions, handlers, retry);
};
/**
* Get master server info.
*
* @return {Object} master server info, {id, host, port}
* @memberOf Application
*/
Application.getMaster = function() {
return this.master;
};
/**
* Get current server info.
*
* @return {Object} current server info, {id, serverType, host, port}
* @memberOf Application
*/
Application.getCurServer = function() {
return this.curServer;
};
/**
* Get current server id.
*
* @return {String|Number} current server id from servers.json
* @memberOf Application
*/
Application.getServerId = function() {
return this.serverId;
};
/**
* Get current server type.
*
* @return {String|Number} current server type from servers.json
* @memberOf Application
*/
Application.getServerType = function() {
return this.serverType;
};
/**
* Get all the current server infos.
*
* @return {Object} server info map, key: server id, value: server info
* @memberOf Application
*/
Application.getServers = function() {
return this.servers;
};
/**
* Get all server infos from servers.json.
*
* @return {Object} server info map, key: server id, value: server info
* @memberOf Application
*/
Application.getServersFromConfig = function() {
return this.get(Constants.KEYWORDS.SERVER_MAP);
};
/**
* Get all the server type.
*
* @return {Array} server type list
* @memberOf Application
*/
Application.getServerTypes = function() {
return this.serverTypes;
};
/**
* Get server info by server id from current server cluster.
*
* @param {String} serverId server id
* @return {Object} server info or undefined
* @memberOf Application
*/
Application.getServerById = function(serverId) {
return this.servers[serverId];
};
/**
* Get server info by server id from servers.json.
*
* @param {String} serverId server id
* @return {Object} server info or undefined
* @memberOf Application
*/
Application.getServerFromConfig = function(serverId) {
return this.get(Constants.KEYWORDS.SERVER_MAP)[serverId];
};
/**
* Get server infos by server type.
*
* @param {String} serverType server type
* @return {Array} server info list
* @memberOf Application
*/
Application.getServersByType = function(serverType) {
return this.serverTypeMaps[serverType];
};
/**
* Check the server whether is a frontend server
*
* @param {server} server server info. it would check current server
* if server not specified
* @return {Boolean}
*
* @memberOf Application
*/
Application.isFrontend = function(server) {
server = server || this.getCurServer();
return !!server && server.frontend === 'true';
};
/**
* Check the server whether is a backend server
*
* @param {server} server server info. it would check current server
* if server not specified
* @return {Boolean}
* @memberOf Application
*/
Application.isBackend = function(server) {
server = server || this.getCurServer();
return !!server && !server.frontend;
};
/**
* Check whether current server is a master server
*
* @return {Boolean}
* @memberOf Application
*/
Application.isMaster = function() {
return this.serverType === Constants.RESERVED.MASTER;
};
/**
* Add new server info to current application in runtime.
*
* @param {Array} servers new server info list
* @memberOf Application
*/
Application.addServers = function(servers) {
if(!servers || !servers.length) {
return;
}
var item, slist;
for(var i=0, l=servers.length; i<l; i++) {
item = servers[i];
// update global server map
this.servers[item.id] = item;
// update global server type map
slist = this.serverTypeMaps[item.serverType];
if(!slist) {
this.serverTypeMaps[item.serverType] = slist = [];
}
replaceServer(slist, item);
// update global server type list
if(this.serverTypes.indexOf(item.serverType) < 0) {
this.serverTypes.push(item.serverType);
}
}
this.event.emit(events.ADD_SERVERS, servers);
};
/**
* Remove server info from current application at runtime.
*
* @param {Array} ids server id list
* @memberOf Application
*/
Application.removeServers = function(ids) {
if(!ids || !ids.length) {
return;
}
var id, item, slist;
for(var i=0, l=ids.length; i<l; i++) {
id = ids[i];
item = this.servers[id];
if(!item) {
continue;
}
// clean global server map
delete this.servers[id];
// clean global server type map
slist = this.serverTypeMaps[item.serverType];
removeServer(slist, id);
// TODO: should remove the server type if the slist is empty?
}
this.event.emit(events.REMOVE_SERVERS, ids);
};
/**
* Replace server info from current application at runtime.
*
* @param {Object} server id map
* @memberOf Application
*/
Application.replaceServers = function(servers) {
if(!servers){
return;
}
this.servers = servers;
this.serverTypeMaps = {};
this.serverTypes = [];
var serverArray = [];
for(var id in servers){
var server = servers[id];
var serverType = server[Constants.RESERVED.SERVER_TYPE];
var slist = this.serverTypeMaps[serverType];
if(!slist) {
this.serverTypeMaps[serverType] = slist = [];
}
this.serverTypeMaps[serverType].push(server);
// update global server type list
if(this.serverTypes.indexOf(serverType) < 0) {
this.serverTypes.push(serverType);
}
serverArray.push(server);
}
this.event.emit(events.REPLACE_SERVERS, serverArray);
};
/**
* Add crons from current application at runtime.
*
* @param {Array} crons new crons would be added in application
* @memberOf Application
*/
Application.addCrons = function(crons) {
if(!crons || !crons.length) {
logger.warn('crons is not defined.');
return;
}
this.event.emit(events.ADD_CRONS, crons);
};
/**
* Remove crons from current application at runtime.
*
* @param {Array} crons old crons would be removed in application
* @memberOf Application
*/
Application.removeCrons = function(crons) {
if(!crons || !crons.length) {
logger.warn('ids is not defined.');
return;
}
this.event.emit(events.REMOVE_CRONS, crons);
};
var replaceServer = function(slist, serverInfo) {
for(var i=0, l=slist.length; i<l; i++) {
if(slist[i].id === serverInfo.id) {
slist[i] = serverInfo;
return;
}
}
slist.push(serverInfo);
};
var removeServer = function(slist, id) {
if(!slist || !slist.length) {
return;
}
for(var i=0, l=slist.length; i<l; i++) {
if(slist[i].id === id) {
slist.splice(i, 1);
return;
}
}
};
var contains = function(str, settings) {
if(!settings) {
return false;
}
var ts = settings.split("|");
for(var i=0, l=ts.length; i<l; i++) {
if(str === ts[i]) {
return true;
}
}
return false;
};
var bindEvents = function(Event, app) {
var emethods = new Event(app);
for(var m in emethods) {
if(typeof emethods[m] === 'function') {
app.event.on(m, emethods[m].bind(emethods));
}
}
};
var addFilter = function(app, type, filter) {
var filters = app.get(type);
if(!filters) {
filters = [];
app.set(type, filters);
}
filters.push(filter);
};
================================================
FILE: lib/common/manager/appManager.js
================================================
var async = require('async');
var utils = require('../../util/utils');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
var transactionLogger = require('pomelo-logger').getLogger('transaction-log', __filename);
var transactionErrorLogger = require('pomelo-logger').getLogger('transaction-error-log', __filename);
var manager = module.exports;
manager.transaction = function(name, conditions, handlers, retry) {
if(!retry) {
retry = 1;
}
if(typeof name !== 'string') {
logger.error('transaction name is error format, name: %s.', name);
return;
}
if(typeof conditions !== 'object' || typeof handlers !== 'object') {
logger.error('transaction conditions parameter is error format, conditions: %j, handlers: %j.', conditions, handlers);
return;
}
var cmethods=[] ,dmethods=[], cnames=[], dnames=[];
for(var key in conditions) {
if(typeof key !== 'string' || typeof conditions[key] !== 'function') {
logger.error('transaction conditions parameter is error format, condition name: %s, condition function: %j.', key, conditions[key]);
return;
}
cnames.push(key);
cmethods.push(conditions[key]);
}
var i = 0;
// execute conditions
async.forEachSeries(cmethods, function(method, cb) {
method(cb);
transactionLogger.info('[%s]:[%s] condition is executed.', name, cnames[i]);
i++;
}, function(err) {
if(err) {
process.nextTick(function() {
transactionLogger.error('[%s]:[%s] condition is executed with err: %j.', name, cnames[--i], err.stack);
var log = {
name: name,
method: cnames[i],
time: Date.now(),
type: 'condition',
description: err.stack
};
transactionErrorLogger.error(JSON.stringify(log));
});
return;
} else {
// execute handlers
process.nextTick(function() {
for(var key in handlers) {
if(typeof key !== 'string' || typeof handlers[key] !== 'function') {
logger.error('transcation handlers parameter is error format, handler name: %s, handler function: %j.', key, handlers[key]);
return;
}
dnames.push(key);
dmethods.push(handlers[key]);
}
var flag = true;
var times = retry;
// do retry if failed util retry times
async.whilst(
function() {
return retry > 0 && flag;
},
function(callback) {
var j = 0;
retry--;
async.forEachSeries(dmethods, function(method, cb) {
method(cb);
transactionLogger.info('[%s]:[%s] handler is executed.', name, dnames[j]);
j++;
}, function(err) {
if(err) {
process.nextTick(function() {
transactionLogger.error('[%s]:[%s]:[%s] handler is executed with err: %j.', name, dnames[--j], times-retry, err.stack);
var log = {
name: name,
method: dnames[j],
retry: times-retry,
time: Date.now(),
type: 'handler',
description: err.stack
};
transactionErrorLogger.error(JSON.stringify(log));
utils.invokeCallback(callback);
});
return;
}
flag = false;
utils.invokeCallback(callback);
process.nextTick(function() {
transactionLogger.info('[%s] all conditions and handlers are executed successfully.', name);
});
});
},
function(err) {
if(err) {
logger.error('transaction process is executed with error: %j', err);
}
// callback will not pass error
}
);
});
}
});
};
================================================
FILE: lib/common/manager/taskManager.js
================================================
var sequeue = require('seq-queue');
var manager = module.exports;
var queues = {};
manager.timeout = 3000;
/**
* Add tasks into task group. Create the task group if it dose not exist.
*
* @param {String} key task key
* @param {Function} fn task callback
* @param {Function} ontimeout task timeout callback
* @param {Number} timeout timeout for task
*/
manager.addTask = function(key, fn, ontimeout, timeout) {
var queue = queues[key];
if(!queue) {
queue = sequeue.createQueue(manager.timeout);
queues[key] = queue;
}
return queue.push(fn, ontimeout, timeout);
};
/**
* Destroy task group
*
* @param {String} key task key
* @param {Boolean} force whether close task group directly
*/
manager.closeQueue = function(key, force) {
if(!queues[key]) {
// ignore illeagle key
return;
}
queues[key].close(force);
delete queues[key];
};
================================================
FILE: lib/common/remote/backend/msgRemote.js
================================================
var utils = require('../../../util/utils');
var logger = require('pomelo-logger').getLogger('forward-log', __filename);
/**
* Remote service for backend servers.
* Receive and handle request message forwarded from frontend server.
*/
module.exports = function(app) {
return new Remote(app);
};
var Remote = function(app) {
this.app = app;
};
/**
* Forward message from frontend server to other server's handlers
*
* @param msg {Object} request message
* @param session {Object} session object for current request
* @param cb {Function} callback function
*/
Remote.prototype.forwardMessage = function(msg, session, cb) {
var server = this.app.components.__server__;
var sessionService = this.app.components.__backendSession__;
if(!server) {
logger.error('server component not enable on %s', this.app.serverId);
utils.invokeCallback(cb, new Error('server component not enable'));
return;
}
if(!sessionService) {
logger.error('backend session component not enable on %s', this.app.serverId);
utils.invokeCallback(cb, new Error('backend sesssion component not enable'));
return;
}
// generate backend session for current request
var backendSession = sessionService.create(session);
// handle the request
logger.debug('backend server [%s] handle message: %j', this.app.serverId, msg);
server.handle(msg, backendSession, function(err, resp, opts) {
// cb && cb(err, resp, opts);
utils.invokeCallback(cb, err, resp, opts);
});
};
Remote.prototype.forwardMessage2 = function(route, body, aesPassword, compressGzip, session, cb) {
var server = this.app.components.__server__;
var sessionService = this.app.components.__backendSession__;
if(!server) {
logger.error('server component not enable on %s', this.app.serverId);
utils.invokeCallback(cb, new Error('server component not enable'));
return;
}
if(!sessionService) {
logger.error('backend session component not enable on %s', this.app.serverId);
utils.invokeCallback(cb, new Error('backend sesssion component not enable'));
return;
}
// generate backend session for current request
var backendSession = sessionService.create(session);
// handle the request
// logger.debug('backend server [%s] handle message: %j', this.app.serverId, msg);
var dmsg = {
route: route,
body: body,
compressGzip: compressGzip
}
var socket = {
aesPassword: aesPassword
}
var connector = this.app.components.__connector__.connector;
connector.runDecode(dmsg, socket, function(err, msg) {
if(err) {
return cb(err);
}
server.handle(msg, backendSession, function(err, resp, opts) {
utils.invokeCallback(cb, err, resp, opts);
});
});
};
================================================
FILE: lib/common/remote/frontend/channelRemote.js
================================================
/**
* Remote channel service for frontend server.
* Receive push request from backend servers and push it to clients.
*/
var utils = require('../../../util/utils');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
module.exports = function(app) {
return new Remote(app);
};
var Remote = function(app) {
this.app = app;
};
/**
* Push message to client by uids.
*
* @param {String} route route string of message
* @param {Object} msg message
* @param {Array} uids user ids that would receive the message
* @param {Object} opts push options
* @param {Function} cb callback function
*/
Remote.prototype.pushMessage = function(route, msg, uids, opts, cb) {
if(!msg){
logger.error('Can not send empty message! route : %j, compressed msg : %j',
route, msg);
utils.invokeCallback(cb, new Error('can not send empty message.'));
return;
}
var connector = this.app.components.__connector__;
var sessionService = this.app.get('sessionService');
var fails = [], sids = [], sessions, j, k;
for(var i=0, l=uids.length; i<l; i++) {
sessions = sessionService.getByUid(uids[i]);
if(!sessions) {
fails.push(uids[i]);
} else {
for(j=0, k=sessions.length; j<k; j++) {
sids.push(sessions[j].id);
}
}
}
logger.debug('[%s] pushMessage uids: %j, msg: %j, sids: %j', this.app.serverId, uids, msg, sids);
connector.send(null, route, msg, sids, opts, function(err) {
utils.invokeCallback(cb, err, fails);
});
};
/**
* Broadcast to all the client connectd with current frontend server.
*
* @param {String} route route string
* @param {Object} msg message
* @param {Boolean} opts broadcast options.
* @param {Function} cb callback function
*/
Remote.prototype.broadcast = function(route, msg, opts, cb) {
var connector = this.app.components.__connector__;
connector.send(null, route, msg, null, opts, cb);
};
================================================
FILE: lib/common/remote/frontend/sessionRemote.js
================================================
/**
* Remote session service for frontend server.
* Set session info for backend servers.
*/
var utils = require('../../../util/utils');
module.exports = function(app) {
return new Remote(app);
};
var Remote = function(app) {
this.app = app;
};
Remote.prototype.bind = function(sid, uid, cb) {
this.app.get('sessionService').bind(sid, uid, cb);
};
Remote.prototype.unbind = function(sid, uid, cb) {
this.app.get('sessionService').unbind(sid, uid, cb);
};
Remote.prototype.push = function(sid, key, value, cb) {
this.app.get('sessionService').import(sid, key, value, cb);
};
Remote.prototype.pushAll = function(sid, settings, cb) {
this.app.get('sessionService').importAll(sid, settings, cb);
};
/**
* Get session informations with session id.
*
* @param {String} sid session id binded with the session
* @param {Function} cb(err, sinfo) callback funtion, sinfo would be null if the session not exist.
*/
Remote.prototype.getBackendSessionBySid = function(sid, cb) {
var session = this.app.get('sessionService').get(sid);
if(!session) {
utils.invokeCallback(cb);
return;
}
utils.invokeCallback(cb, null, session.toFrontendSession().export());
};
/**
* Get all the session informations with the specified user id.
*
* @param {String} uid user id binded with the session
* @param {Function} cb(err, sinfo) callback funtion, sinfo would be null if the session does not exist.
*/
Remote.prototype.getBackendSessionsByUid = function(uid, cb) {
var sessions = this.app.get('sessionService').getByUid(uid);
if(!sessions) {
utils.invokeCallback(cb);
return;
}
var res = [];
for(var i=0, l=sessions.length; i<l; i++) {
res.push(sessions[i].toFrontendSession().export());
}
utils.invokeCallback(cb, null, res);
};
/**
* Kick a session by session id.
*
* @param {Number} sid session id
* @param {String} reason kick reason
* @param {Function} cb callback function
*/
Remote.prototype.kickBySid = function(sid, reason, cb) {
this.app.get('sessionService').kickBySessionId(sid, reason, cb);
};
/**
* Kick sessions by user id.
*
* @param {Number|String} uid user id
* @param {String} reason kick reason
* @param {Function} cb callback function
*/
Remote.prototype.kickByUid = function(uid, reason, cb) {
this.app.get('sessionService').kick(uid, reason, cb);
};
================================================
FILE: lib/common/service/backendSessionService.js
================================================
/**
* backend session service for backend session
*/
var utils = require('../../util/utils');
var EXPORTED_FIELDS = ['id', 'frontendId', 'uid', 'settings'];
/**
* Service that maintains backend sessions and the communication with frontend
* servers.
*
* BackendSessionService would be created in each server process and maintains
* backend sessions for current process and communicates with the relative
* frontend servers.
*
* BackendSessionService instance could be accessed by
* `app.get('backendSessionService')` or app.backendSessionService.
*
* @class
* @constructor
*/
var BackendSessionService = function(app) {
this.app = app;
};
module.exports = BackendSessionService;
BackendSessionService.prototype.create = function(opts) {
if(!opts) {
throw new Error('opts should not be empty.');
}
return new BackendSession(opts, this);
};
/**
* Get backend session by frontend server id and session id.
*
* @param {String} frontendId frontend server id that session attached
* @param {String} sid session id
* @param {Function} cb callback function. args: cb(err, BackendSession)
*
* @memberOf BackendSessionService
*/
BackendSessionService.prototype.get = function(frontendId, sid, cb) {
var namespace = 'sys';
var service = 'sessionRemote';
var method = 'getBackendSessionBySid';
var args = [sid];
rpcInvoke(this.app, frontendId, namespace, service, method,
args, BackendSessionCB.bind(null, this, cb));
};
/**
* Get backend sessions by frontend server id and user id.
*
* @param {String} frontendId frontend server id that session attached
* @param {String} uid user id binded with the session
* @param {Function} cb callback function. args: cb(err, BackendSessions)
*
* @memberOf BackendSessionService
*/
BackendSessionService.prototype.getByUid = function(frontendId, uid, cb) {
var namespace = 'sys';
var service = 'sessionRemote';
var method = 'getBackendSessionsByUid';
var args = [uid];
rpcInvoke(this.app, frontendId, namespace, service, method,
args, BackendSessionCB.bind(null, this, cb));
};
/**
* Kick a session by session id.
*
* @param {String} frontendId cooperating frontend server id
* @param {Number} sid session id
* @param {Function} cb callback function
*
* @memberOf BackendSessionService
*/
BackendSessionService.prototype.kickBySid = function(frontendId, sid, reason, cb) {
var namespace = 'sys';
var service = 'sessionRemote';
var method = 'kickBySid';
var args = [sid];
if(typeof reason === 'function') {
cb = reason;
}else{
args.push(reason);
}
rpcInvoke(this.app, frontendId, namespace, service, method, args, cb);
};
/**
* Kick sessions by user id.
*
* @param {String} frontendId cooperating frontend server id
* @param {Number|String} uid user id
* @param {String} reason kick reason
* @param {Function} cb callback function
*
* @memberOf BackendSessionService
*/
BackendSessionService.prototype.kickByUid = function(frontendId, uid, reason, cb) {
var namespace = 'sys';
var service = 'sessionRemote';
var method = 'kickByUid';
var args = [uid];
if(typeof reason === 'function') {
cb = reason;
}else{
args.push(reason);
}
rpcInvoke(this.app, frontendId, namespace, service, method, args, cb);
};
/**
* Bind the session with the specified user id. It would finally invoke the
* the sessionService.bind in the cooperating frontend server.
*
* @param {String} frontendId cooperating frontend server id
* @param {Number} sid session id
* @param {String} uid user id
* @param {Function} cb callback function
*
* @memberOf BackendSessionService
* @api private
*/
BackendSessionService.prototype.bind = function(frontendId, sid, uid, cb) {
var namespace = 'sys';
var service = 'sessionRemote';
var method = 'bind';
var args = [sid, uid];
rpcInvoke(this.app, frontendId, namespace, service, method, args, cb);
};
/**
* Unbind the session with the specified user id. It would finally invoke the
* the sessionService.unbind in the cooperating frontend server.
*
* @param {String} frontendId cooperating frontend server id
* @param {Number} sid session id
* @param {String} uid user id
* @param {Function} cb callback function
*
* @memberOf BackendSessionService
* @api private
*/
BackendSessionService.prototype.unbind = function(frontendId, sid, uid, cb) {
var namespace = 'sys';
var service = 'sessionRemote';
var method = 'unbind';
var args = [sid, uid];
rpcInvoke(this.app, frontendId, namespace, service, method, args, cb);
};
/**
* Push the specified customized change to the frontend internal session.
*
* @param {String} frontendId cooperating frontend server id
* @param {Number} sid session id
* @param {String} key key in session that should be push
* @param {Object} value value in session, primitive js object
* @param {Function} cb callback function
*
* @memberOf BackendSessionService
* @api private
*/
BackendSessionService.prototype.push = function(frontendId, sid, key, value, cb) {
var namespace = 'sys';
var service = 'sessionRemote';
var method = 'push';
var args = [sid, key, value];
rpcInvoke(this.app, frontendId, namespace, service, method, args, cb);
};
/**
* Push all the customized changes to the frontend internal session.
*
* @param {String} frontendId cooperating frontend server id
* @param {Number} sid session id
* @param {Object} settings key/values in session that should be push
* @param {Function} cb callback function
*
* @memberOf BackendSessionService
* @api private
*/
BackendSessionService.prototype.pushAll = function(frontendId, sid, settings, cb) {
var namespace = 'sys';
var service = 'sessionRemote';
var method = 'pushAll';
var args = [sid, settings];
rpcInvoke(this.app, frontendId, namespace, service, method, args, cb);
};
var rpcInvoke = function(app, sid, namespace, service, method, args, cb) {
app.rpcInvoke(sid, {namespace: namespace, service: service, method: method, args: args}, cb);
};
/**
* BackendSession is the proxy for the frontend internal session passed to handlers and
* it helps to keep the key/value pairs for the server locally.
* Internal session locates in frontend server and should not be accessed directly.
*
* The mainly operation on backend session should be read and any changes happen in backend
* session is local and would be discarded in next request. You have to push the
* changes to the frontend manually if necessary. Any push would overwrite the last push
* of the same key silently and the changes would be saw in next request.
* And you have to make sure the transaction outside if you would push the session
* concurrently in different processes.
*
* See the api below for more details.
*
* @class
* @constructor
*/
var BackendSession = function(opts, service) {
for(var f in opts) {
this[f] = opts[f];
}
this.__sessionService__ = service;
};
/**
* Bind current session with the user id. It would push the uid to frontend
* server and bind uid to the frontend internal session.
*
* @param {Number|String} uid user id
* @param {Function} cb callback function
*
* @memberOf BackendSession
*/
BackendSession.prototype.bind = function(uid, cb) {
var self = this;
this.__sessionService__.bind(this.frontendId, this.id, uid, function(err) {
if(!err) {
self.uid = uid;
}
utils.invokeCallback(cb, err);
});
};
/**
* Unbind current session with the user id. It would push the uid to frontend
* server and unbind uid from the frontend internal session.
*
* @param {Number|String} uid user id
* @param {Function} cb callback function
*
* @memberOf BackendSession
*/
BackendSession.prototype.unbind = function(uid, cb) {
var self = this;
this.__sessionService__.unbind(this.frontendId, this.id, uid, function(err) {
if(!err) {
self.uid = null;
}
utils.invokeCallback(cb, err);
});
};
/**
* Set the key/value into backend session.
*
* @param {String} key key
* @param {Object} value value
*/
BackendSession.prototype.set = function(key, value) {
this.settings[key] = value;
};
/**
* Get the value from backend session by key.
*
* @param {String} key key
* @return {Object} value
*/
BackendSession.prototype.get = function(key) {
return this.settings[key];
};
/**
* Push the key/value in backend session to the front internal session.
*
* @param {String} key key
* @param {Function} cb callback function
*/
BackendSession.prototype.push = function(key, cb) {
this.__sessionService__.push(this.frontendId, this.id, key, this.get(key), cb);
};
/**
* Push all the key/values in backend session to the frontend internal session.
*
* @param {Function} cb callback function
*/
BackendSession.prototype.pushAll = function(cb) {
this.__sessionService__.pushAll(this.frontendId, this.id, this.settings, cb);
};
/**
* Export the key/values for serialization.
*
* @api private
*/
BackendSession.prototype.export = function() {
var res = {};
EXPORTED_FIELDS.forEach(function(field) {
res[field] = this[field];
});
return res;
};
var BackendSessionCB = function(service, cb, err, sinfo) {
if(err) {
utils.invokeCallback(cb, err);
return;
}
if(!sinfo) {
utils.invokeCallback(cb);
return;
}
var sessions = [];
if(Array.isArray(sinfo)){
// #getByUid
for(var i = 0,k = sinfo.length;i<k;i++){
sessions.push(service.create(sinfo[i]));
}
}
else{
// #get
sessions = service.create(sinfo);
}
utils.invokeCallback(cb, null, sessions);
};
================================================
FILE: lib/common/service/channelService.js
================================================
var countDownLatch = require('../../util/countDownLatch');
var utils = require('../../util/utils');
var ChannelRemote = require('../remote/frontend/channelRemote');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
/**
* constant
*/
var ST_INITED = 0;
var ST_DESTROYED = 1;
/**
* Create and maintain channels for server local.
*
* ChannelService is created by channel component which is a default loaded
* component of pomelo and channel service would be accessed by `app.get('channelService')`.
*
* @class
* @constructor
*/
var ChannelService = function(app, opts) {
opts = opts || {};
this.app = app;
this.channels = {};
this.prefix = opts.prefix;
this.store = opts.store;
this.broadcastFilter = opts.broadcastFilter;
this.channelRemote = new ChannelRemote(app);
};
module.exports = ChannelService;
ChannelService.prototype.start = function(cb) {
restoreChannel(this, cb);
};
/**
* Create channel with name.
*
* @param {String} name channel's name
* @memberOf ChannelService
*/
ChannelService.prototype.createChannel = function(name) {
if(this.channels[name]) {
return this.channels[name];
}
var c = new Channel(name, this);
addToStore(this, genKey(this), genKey(this, name));
this.channels[name] = c;
return c;
};
/**
* Get channel by name.
*
* @param {String} name channel's name
* @param {Boolean} create if true, create channel
* @return {Channel}
* @memberOf ChannelService
*/
ChannelService.prototype.getChannel = function(name, create) {
var channel = this.channels[name];
if(!channel && !!create) {
channel = this.channels[name] = new Channel(name, this);
addToStore(this, genKey(this), genKey(this, name));
}
return channel;
};
/**
* Destroy channel by name.
*
* @param {String} name channel name
* @memberOf ChannelService
*/
ChannelService.prototype.destroyChannel = function(name) {
delete this.channels[name];
removeFromStore(this, genKey(this), genKey(this, name));
removeAllFromStore(this, genKey(this, name));
};
/**
* Push message by uids.
* Group the uids by group. ignore any uid if sid not specified.
*
* @param {String} route message route
* @param {Object} msg message that would be sent to client
* @param {Array} uids the receiver info list, [{uid: userId, sid: frontendServerId}]
* @param {Object} opts user-defined push options, optional
* @param {Function} cb cb(err)
* @memberOf ChannelService
*/
ChannelService.prototype.pushMessageByUids = function(route, msg, uids, opts, cb) {
if(typeof route !== 'string') {
cb = opts;
opts = uids;
uids = msg;
msg = route;
route = msg.route;
}
if(!cb && typeof opts === 'function') {
cb = opts;
opts = {};
}
if(!uids || uids.length === 0) {
utils.invokeCallback(cb, new Error('uids should not be empty'));
return;
}
var groups = {}, record;
for(var i=0, l=uids.length; i<l; i++) {
record = uids[i];
add(record.uid, record.sid, groups);
}
sendMessageByGroup(this, route, msg, groups, opts, cb);
};
/**
* Broadcast message to all the connected clients.
*
* @param {String} stype frontend server type string
* @param {String} route route string
* @param {Object} msg message
* @param {Object} opts user-defined broadcast options, optional
* opts.binded: push to binded sessions or all the sessions
* opts.filterParam: parameters for broadcast filter.
* @param {Function} cb callback
* @memberOf ChannelService
*/
ChannelService.prototype.broadcast = function(stype, route, msg, opts, cb) {
var app = this.app;
var namespace = 'sys';
var service = 'channelRemote';
var method = 'broadcast';
var servers = app.getServersByType(stype);
if(!servers || servers.length === 0) {
// server list is empty
utils.invokeCallback(cb);
return;
}
var count = servers.length;
var successFlag = false;
var latch = countDownLatch.createCountDownLatch(count, function() {
if(!successFlag) {
utils.invokeCallback(cb, new Error('broadcast fails'));
return;
}
utils.invokeCallback(cb, null);
});
var genCB = function(serverId) {
return function(err) {
if(err) {
logger.error('[broadcast] fail to push message to serverId: ' + serverId + ', err:' + err.stack);
latch.done();
return;
}
successFlag = true;
latch.done();
};
};
var self = this;
var sendMessage = function(serverId) {
return (function() {
if(serverId === app.serverId) {
self.channelRemote[method](route, msg, opts, genCB());
} else {
app.rpcInvoke(serverId, {namespace: namespace, service: service,
method: method, args: [route, msg, opts]}, genCB(serverId));
}
}());
};
opts = {type: 'broadcast', userOptions: opts || {}};
// for compatiblity
opts.isBroadcast = true;
if(opts.userOptions) {
opts.binded = opts.userOptions.binded;
opts.filterParam = opts.userOptions.filterParam;
}
for(var i=0, l=count; i<l; i++) {
sendMessage(servers[i].id);
}
};
/**
* Channel maintains the receiver collection for a subject. You can
* add users into a channel and then broadcast message to them by channel.
*
* @class channel
* @constructor
*/
var Channel = function(name, service) {
this.name = name;
this.groups = {}; // group map for uids. key: sid, value: [uid]
this.records = {}; // member records. key: uid
this.__channelService__ = service;
this.state = ST_INITED;
this.userAmount =0;
};
/**
* Add user to channel.
*
* @param {Number} uid user id
* @param {String} sid frontend server id which user has connected to
*/
Channel.prototype.add = function(uid, sid) {
if(this.state > ST_INITED) {
return false;
} else {
var res = add(uid, sid, this.groups);
if(res) {
this.records[uid] = {sid: sid, uid: uid};
this.userAmount =this.userAmount+1;
}
addToStore(this.__channelService__, genKey(this.__channelService__, this.name), genValue(sid, uid));
return res;
}
};
/**
* Remove user from channel.
*
* @param {Number} uid user id
* @param {String} sid frontend server id which user has connected to.
* @return [Boolean] true if success or false if fail
*/
Channel.prototype.leave = function(uid, sid) {
if(!uid || !sid) {
return false;
}
var res = deleteFrom(uid, sid, this.groups[sid]);
if(res){
delete this.records[uid];
this.userAmount = this.userAmount-1;
}
if(this.userAmount<0) this.userAmount=0;//robust
removeFromStore(this.__channelService__, genKey(this.__channelService__, this.name), genValue(sid, uid));
if(this.groups[sid] && this.groups[sid].length === 0) {
delete this.groups[sid];
}
return res;
};
/**
* Get channel UserAmount in a channel.
*
* @return {number } channel member amount
*/
Channel.prototype.getUserAmount = function() {
return this.userAmount;
};
/**
* Get channel members.
*
* <b>Notice:</b> Heavy operation.
*
* @return {Array} channel member uid list
*/
Channel.prototype.getMembers = function() {
var res = [], groups = this.groups;
var group, i, l;
for(var sid in groups) {
group = groups[sid];
for(i=0, l=group.length; i<l; i++) {
res.push(group[i]);
}
}
return res;
};
/**
* Get Member info.
*
* @param {String} uid user id
* @return {Object} member info
*/
Channel.prototype.getMember = function(uid) {
return this.records[uid];
};
/**
* Destroy channel.
*/
Channel.prototype.destroy = function() {
this.state = ST_DESTROYED;
this.__channelService__.destroyChannel(this.name);
};
/**
* Push message to all the members in the channel
*
* @param {String} route message route
* @param {Object} msg message that would be sent to client
* @param {Object} opts user-defined push options, optional
* @param {Function} cb callback function
*/
Channel.prototype.pushMessage = function(route, msg, opts, cb) {
if(this.state !== ST_INITED) {
utils.invokeCallback(new Error('channel is not running now'));
return;
}
if(typeof route !== 'string') {
cb = opts;
opts = msg;
msg = route;
route = msg.route;
}
if(!cb && typeof opts === 'function') {
cb = opts;
opts = {};
}
sendMessageByGroup(this.__channelService__, route, msg, this.groups, opts, cb);
};
/**
* add uid and sid into group. ignore any uid that uid not specified.
*
* @param uid user id
* @param sid server id
* @param groups {Object} grouped uids, , key: sid, value: [uid]
*/
var add = function(uid, sid, groups) {
if(!sid) {
logger.warn('ignore uid %j for sid not specified.', uid);
return false;
}
var group = groups[sid];
if(!group) {
group = [];
groups[sid] = group;
}
group.push(uid);
return true;
};
/**
* delete element from array
*/
var deleteFrom = function(uid, sid, group) {
if(!uid || !sid || !group) {
return false;
}
for(var i=0, l=group.length; i<l; i++) {
if(group[i] === uid) {
group.splice(i, 1);
return true;
}
}
return false;
};
/**
* push message by group
*
* @param route {String} route route message
* @param msg {Object} message that would be sent to client
* @param groups {Object} grouped uids, , key: sid, value: [uid]
* @param opts {Object} push options
* @param cb {Function} cb(err)
*
* @api private
*/
var sendMessageByGroup = function(channelService, route, msg, groups, opts, cb) {
var app = channelService.app;
var namespace = 'sys';
var service = 'channelRemote';
var method = 'pushMessage';
var count = utils.size(groups);
var successFlag = false;
var failIds = [];
logger.debug('[%s] channelService sendMessageByGroup route: %s, msg: %j, groups: %j, opts: %j', app.serverId, route, msg, groups, opts);
if(count === 0) {
// group is empty
utils.invokeCallback(cb);
return;
}
var latch = countDownLatch.createCountDownLatch(count, function() {
if(!successFlag) {
utils.invokeCallback(cb, new Error('all uids push message fail'));
return;
}
utils.invokeCallback(cb, null, failIds);
});
var rpcCB = function(serverId) {
return function(err, fails) {
if(err) {
logger.error('[pushMessage] fail to dispatch msg to serverId: ' + serverId + ', err:' + err.stack);
latch.done();
return;
}
if(fails) {
failIds = failIds.concat(fails);
}
successFlag = true;
latch.done();
};
};
opts = {type: 'push', userOptions: opts || {}};
// for compatiblity
opts.isPush = true;
var sendMessage = function(sid) {
return (function() {
if(sid === app.serverId) {
channelService.channelRemote[method](route, msg, groups[sid], opts, rpcCB(sid));
} else {
app.rpcInvoke(sid, {namespace: namespace, service: service,
method: method, args: [route, msg, groups[sid], opts]}, rpcCB(sid));
}
})();
};
var group;
for(var sid in groups) {
group = groups[sid];
if(group && group.length > 0) {
sendMessage(sid);
} else {
// empty group
process.nextTick(rpcCB(sid));
}
}
};
var restoreChannel = function(self, cb) {
if(!self.store) {
utils.invokeCallback(cb);
return;
} else {
loadAllFromStore(self, genKey(self), function(err, list) {
if(!!err) {
utils.invokeCallback(cb, err);
return;
} else {
if(!list.length || !Array.isArray(list)) {
utils.invokeCallback(cb);
return;
}
var load = function(key) {
return (function() {
loadAllFromStore(self, key, function(err, items) {
for(var j=0; j<items.length; j++) {
var array = items[j].split(':');
var sid = array[0];
var uid = array[1];
var channel = self.channels[name];
var res = add(uid, sid, channel.groups);
if(res) {
channel.records[uid] = {sid: sid, uid: uid};
}
}
});
})();
};
for(var i=0; i<list.length; i++) {
var name = list[i].slice(genKey(self).length + 1);
self.channels[name] = new Channel(name, self);
load(list[i]);
}
utils.invokeCallback(cb);
}
});
}
};
var addToStore = function(self, key, value) {
if(!!self.store) {
self.store.add(key, value, function(err) {
if(!!err) {
logger.error('add key: %s value: %s to store, with err: %j', key, value, err.stack);
}
});
}
};
var removeFromStore = function(self, key, value) {
if(!!self.store) {
self.store.remove(key, value, function(err) {
if(!!err) {
logger.error('remove key: %s value: %s from store, with err: %j', key, value, err.stack);
}
});
}
};
var loadAllFromStore = function(self, key, cb) {
if(!!self.store) {
self.store.load(key, function(err, list) {
if(!!err) {
logger.error('load key: %s from store, with err: %j', key, err.stack);
utils.invokeCallback(cb, err);
} else {
utils.invokeCallback(cb, null, list);
}
});
}
};
var removeAllFromStore = function(self, key) {
if(!!self.store) {
self.store.removeAll(key, function(err) {
if(!!err) {
logger.error('remove key: %s all members from store, with err: %j', key, err.stack);
}
});
}
};
var genKey = function(self, name) {
if(!!name) {
return self.prefix + ':' + self.app.serverId + ':' + name;
} else {
return self.prefix + ':' + self.app.serverId;
}
};
var genValue = function(sid, uid) {
return sid + ':' + uid;
};
================================================
FILE: lib/common/service/connectionService.js
================================================
/**
* connection statistics service
* record connection, login count and list
*/
var Service = function(app) {
this.serverId = app.getServerId();
this.connCount = 0;
this.loginedCount = 0;
this.logined = {};
};
module.exports = Service;
var pro = Service.prototype;
/**
* Add logined user.
*
* @param uid {String} user id
* @param info {Object} record for logined user
*/
pro.addLoginedUser = function(uid, info) {
if(!this.logined[uid]) {
this.loginedCount++;
}
info.uid = uid;
this.logined[uid] = info;
};
/**
* Update user info.
* @param uid {String} user id
* @param info {Object} info for update.
*/
pro.updateUserInfo = function(uid, info) {
var user = this.logined[uid];
if (!user) {
return;
}
for (var p in info) {
if (info.hasOwnProperty(p) && typeof info[p] !== 'function') {
user[p] = info[p];
}
}
};
/**
* Increase connection count
*/
pro.increaseConnectionCount = function() {
this.connCount++;
};
/**
* Remote logined user
*
* @param uid {String} user id
*/
pro.removeLoginedUser = function(uid) {
if(!!this.logined[uid]) {
this.loginedCount--;
}
delete this.logined[uid];
};
/**
* Decrease connection count
*
* @param uid {String} uid
*/
pro.decreaseConnectionCount = function(uid) {
if(this.connCount) {
this.connCount--;
}
if(!!uid) {
this.removeLoginedUser(uid);
}
};
/**
* Get statistics info
*
* @return {Object} statistics info
*/
pro.getStatisticsInfo = function() {
var list = [];
for(var uid in this.logined) {
list.push(this.logined[uid]);
}
return {serverId: this.serverId, totalConnCount: this.connCount, loginedCount: this.loginedCount, loginedList: list};
};
================================================
FILE: lib/common/service/filterService.js
================================================
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
/**
* Filter service.
* Register and fire before and after filters.
*/
var Service = function() {
this.befores = []; // before filters
this.afters = []; // after filters
};
module.exports = Service;
Service.prototype.name = 'filter';
/**
* Add before filter into the filter chain.
*
* @param filter {Object|Function} filter instance or filter function.
*/
Service.prototype.before = function(filter){
this.befores.push(filter);
};
/**
* Add after filter into the filter chain.
*
* @param filter {Object|Function} filter instance or filter function.
*/
Service.prototype.after = function(filter){
this.afters.unshift(filter);
};
/**
* TODO: other insert method for filter? such as unshift
*/
/**
* Do the before filter.
* Fail over if any filter pass err parameter to the next function.
*
* @param msg {Object} clienet request msg
* @param session {Object} a session object for current request
* @param cb {Function} cb(err) callback function to invoke next chain node
*/
Service.prototype.beforeFilter = function(msg, session, cb) {
var index = 0, self = this;
var next = function(err, resp, opts) {
if(err || index >= self.befores.length) {
cb(err, resp, opts);
return;
}
var handler = self.befores[index++];
if(typeof handler === 'function') {
handler(msg, session, next);
} else if(typeof handler.before === 'function') {
handler.before(msg, session, next);
} else {
logger.error('meet invalid before filter, handler or handler.before should be function.');
next(new Error('invalid before filter.'));
}
}; //end of next
next();
};
/**
* Do after filter chain.
* Give server a chance to do clean up jobs after request responsed.
* After filter can not change the request flow before.
* After filter should call the next callback to let the request pass to next after filter.
*
* @param err {Object} error object
* @param session {Object} session object for current request
* @param {Object} resp response object send to client
* @param cb {Function} cb(err) callback function to invoke next chain node
*/
Service.prototype.afterFilter = function(err, msg, session, resp, cb) {
var index = 0, self = this;
function next(err) {
//if done
if(index >= self.afters.length) {
cb(err);
return;
}
var handler = self.afters[index++];
if(typeof handler === 'function') {
handler(err, msg, session, resp, next);
} else if(typeof handler.after === 'function') {
handler.after(err, msg, session, resp, next);
} else {
logger.error('meet invalid after filter, handler or handler.after should be function.');
next(new Error('invalid after filter.'));
}
} //end of next
next(err);
};
================================================
FILE: lib/common/service/handlerService.js
================================================
var fs = require('fs');
var utils = require('../../util/utils');
var Loader = require('pomelo-loader');
var pathUtil = require('../../util/pathUtil');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
var forwardLogger = require('pomelo-logger').getLogger('forward-log', __filename);
/**
* Handler service.
* Dispatch request to the relactive handler.
*
* @param {Object} app current application context
*/
var Service = function(app, opts) {
this.app = app;
this.handlerMap = {};
if(!!opts.reloadHandlers) {
watchHandlers(app, this.handlerMap);
}
this.enableForwardLog = opts.enableForwardLog || false;
};
module.exports = Service;
Service.prototype.name = 'handler';
/**
* Handler the request.
*/
Service.prototype.handle = function(routeRecord, msg, session, cb) {
// the request should be processed by current server
var handler = this.getHandler(routeRecord);
if(!handler) {
logger.error('[handleManager]: fail to find handler for %j', msg.__route__);
utils.invokeCallback(cb, new Error('fail to find handler for ' + msg.__route__));
return;
}
var start = Date.now();
var self = this;
var callback = function(err, resp, opts) {
if(self.enableForwardLog) {
var log = {
route : msg.__route__,
args : msg,
time : utils.format(new Date(start)),
timeUsed : new Date() - start
};
forwardLogger.info(JSON.stringify(log));
}
// resp = getResp(arguments);
utils.invokeCallback(cb, err, resp, opts);
}
var method = routeRecord.method;
if(!Array.isArray(msg)) {
handler[method](msg, session, callback);
} else {
msg.push(session);
msg.push(callback);
handler[method].apply(handler, msg);
}
return;
};
/**
* Get handler instance by routeRecord.
*
* @param {Object} handlers handler map
* @param {Object} routeRecord route record parsed from route string
* @return {Object} handler instance if any matchs or null for match fail
*/
Service.prototype.getHandler = function(routeRecord) {
var serverType = routeRecord.serverType;
if(!this.handlerMap[serverType]) {
loadHandlers(this.app, serverType, this.handlerMap);
}
var handlers = this.handlerMap[serverType] || {};
var handler = handlers[routeRecord.handler];
if(!handler) {
logger.warn('could not find handler for routeRecord: %j', routeRecord);
return null;
}
if(typeof handler[routeRecord.method] !== 'function') {
logger.warn('could not find the method %s in handler: %s', routeRecord.method, routeRecord.handler);
return null;
}
return handler;
};
/**
* Load handlers from current application
*/
var loadHandlers = function(app, serverType, handlerMap) {
var p = pathUtil.getHandlerPath(app.getBase(), serverType);
if(p) {
handlerMap[serverType] = Loader.load(p, app);
}
};
var watchHandlers = function(app, handlerMap) {
var p = pathUtil.getHandlerPath(app.getBase(), app.serverType);
if (!!p){
fs.watch(p, function(event, name) {
if(event === 'change') {
handlerMap[app.serverType] = Loader.load(p, app);
}
});
}
};
var getResp = function(args) {
var len = args.length;
if(len == 1) {
return [];
}
if(len == 2) {
return [args[1]];
}
if(len == 3) {
return [args[1], args[2]];
}
if(len == 4) {
return [args[1], args[2], args[3]];
}
var r = new Array(len);
for (var i = 1; i < len; i++) {
r[i] = args[i];
}
return r;
}
================================================
FILE: lib/common/service/sessionService.js
================================================
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
var utils = require('../../util/utils');
var FRONTEND_SESSION_FIELDS = ['id', 'frontendId', 'uid', '__sessionService__'];
var EXPORTED_SESSION_FIELDS = ['id', 'frontendId', 'uid', 'settings'];
var ST_INITED = 0;
var ST_CLOSED = 1;
/**
* Session service maintains the internal session for each client connection.
*
* Session service is created by session component and is only
* <b>available</b> in frontend servers. You can access the service by
* `app.get('sessionService')` or `app.sessionService` in frontend servers.
*
* @param {Object} opts constructor parameters
* @class
* @constructor
*/
var SessionService = function(opts) {
opts = opts || {};
this.singleSession = opts.singleSession;
this.sessions = {}; // sid -> session
this.uidMap = {}; // uid -> sessions
};
module.exports = SessionService;
/**
* Create and return internal session.
*
* @param {Integer} sid uniqe id for the internal session
* @param {String} frontendId frontend server in which the internal session is created
* @param {Object} socket the underlying socket would be held by the internal session
*
* @return {Session}
*
* @memberOf SessionService
* @api private
*/
SessionService.prototype.create = function(sid, frontendId, socket) {
var session = new Session(sid, frontendId, socket, this);
this.sessions[session.id] = session;
return session;
};
/**
* Bind the session with a user id.
*
* @memberOf SessionService
* @api private
*/
SessionService.prototype.bind = function(sid, uid, cb) {
var session = this.sessions[sid];
if(!session) {
process.nextTick(function() {
cb(new Error('session does not exist, sid: ' + sid));
});
return;
}
if(session.uid) {
if(session.uid === uid) {
// already bound with the same uid
cb();
return;
}
// already bound with other uid
process.nextTick(function() {
cb(new Error('session has already bind with ' + session.uid));
});
return;
}
var sessions = this.uidMap[uid];
if(!!this.singleSession && !!sessions) {
process.nextTick(function() {
cb(new Error('singleSession is enabled, and session has already bind with uid: ' + uid));
});
return;
}
if(!sessions) {
sessions = this.uidMap[uid] = [];
}
for(var i=0, l=sessions.length; i<l; i++) {
// session has binded with the uid
if(sessions[i].id === session.id) {
process.nextTick(cb);
return;
}
}
sessions.push(session);
session.bind(uid);
if(cb) {
process.nextTick(cb);
}
};
/**
* Unbind a session with the user id.
*
* @memberOf SessionService
* @api private
*/
SessionService.prototype.unbind = function(sid, uid, cb) {
var session = this.sessions[sid];
if(!session) {
process.nextTick(function() {
cb(new Error('session does not exist, sid: ' + sid));
});
return;
}
if(!session.uid || session.uid !== uid) {
process.nextTick(function() {
cb(new Error('session has not bind with ' + session.uid));
});
return;
}
var sessions = this.uidMap[uid], sess;
if(sessions) {
for(var i=0, l=sessions.length; i<l; i++) {
sess = sessions[i];
if(sess.id === sid) {
sessions.splice(i, 1);
break;
}
}
if(sessions.length === 0) {
delete this.uidMap[uid];
}
}
session.unbind(uid);
if(cb) {
process.nextTick(cb);
}
};
/**
* Get session by id.
*
* @param {Number} id The session id
* @return {Session}
*
* @memberOf SessionService
* @api private
*/
SessionService.prototype.get = function(sid) {
return this.sessions[sid];
};
/**
* Get sessions by userId.
*
* @param {Number} uid User id associated with the session
* @return {Array} list of session binded with the uid
*
* @memberOf SessionService
* @api private
*/
SessionService.prototype.getByUid = function(uid) {
return this.uidMap[uid];
};
/**
* Remove session by key.
*
* @param {Number} sid The session id
*
* @memberOf SessionService
* @api private
*/
SessionService.prototype.remove = function(sid) {
var session = this.sessions[sid];
if(session) {
var uid = session.uid;
delete this.sessions[session.id];
var sessions = this.uidMap[uid];
if(!sessions) {
return;
}
for(var i=0, l=sessions.length; i<l; i++) {
if(sessions[i].id === sid) {
sessions.splice(i, 1);
if(sessions.length === 0) {
delete this.uidMap[uid];
}
break;
}
}
}
};
/**
* Import the key/value into session.
*
* @api private
*/
SessionService.prototype.import = function(sid, key, value, cb) {
var session = this.sessions[sid];
if(!session) {
utils.invokeCallback(cb, new Error('session does not exist, sid: ' + sid));
return;
}
session.set(key, value);
utils.invokeCallback(cb);
};
/**
* Import new value for the existed session.
*
* @memberOf SessionService
* @api private
*/
SessionService.prototype.importAll = function(sid, settings, cb) {
var session = this.sessions[sid];
if(!session) {
utils.invokeCallback(cb, new Error('session does not exist, sid: ' + sid));
return;
}
for(var f in settings) {
session.set(f, settings[f]);
}
utils.invokeCallback(cb);
};
/**
* Kick all the session offline under the user id.
*
* @param {Number} uid user id asscociated with the session
* @param {Function} cb callback function
*
* @memberOf SessionService
*/
SessionService.prototype.kick = function(uid, reason, cb) {
// compatible for old kick(uid, cb);
if(typeof reason === 'function') {
cb = reason;
reason = 'kick';
}
var sessions = this.getByUid(uid);
if(sessions) {
// notify client
var sids = [];
var self = this;
sessions.forEach(function(session) {
sids.push(session.id);
});
sids.forEach(function(sid) {
self.sessions[sid].closed(reason);
});
process.nextTick(function() {
utils.invokeCallback(cb);
});
} else {
process.nextTick(function() {
utils.invokeCallback(cb);
});
}
};
/**
* Kick a user offline by session id.
*
* @param {Number} sid session id
* @param {Function} cb callback function
*
* @memberOf SessionService
*/
SessionService.prototype.kickBySessionId = function(sid, reason, cb) {
if(typeof reason === 'function') {
cb = reason;
reason = 'kick';
}
var session = this.get(sid);
if(session) {
// notify client
session.closed(reason);
process.nextTick(function() {
utils.invokeCallback(cb);
});
} else {
process.nextTick(function() {
utils.invokeCallback(cb);
});
}
};
/**
* Get client remote address by session id.
*
* @param {Number} sid session id
* @return {Object} remote address of client
*
* @memberOf SessionService
*/
SessionService.prototype.getClientAddressBySessionId = function(sid) {
var session = this.get(sid);
if(session) {
var socket = session.__socket__;
return socket.remoteAddress;
} else {
return null;
}
};
/**
* Send message to the client by session id.
*
* @param {String} sid session id
* @param {Object} msg message to send
*
* @memberOf SessionService
* @api private
*/
SessionService.prototype.sendMessage = function(sid, msg) {
var session = this.get(sid);
if(!session) {
logger.debug('Fail to send message for non-existing session, sid: ' + sid + ' msg: ' + msg);
return false;
}
return send(this, session, msg);
};
/**
* Send message to the client by user id.
*
* @param {String} uid userId
* @param {Object} msg message to send
*
* @memberOf SessionService
* @api private
*/
SessionService.prototype.sendMessageByUid = function(uid, msg) {
var sessions = this.getByUid(uid);
if(!sessions) {
logger.debug('fail to send message by uid for non-existing session. uid: %j',
uid);
return false;
}
for(var i=0, l=sessions.length; i<l; i++) {
send(this, sessions[i], msg);
}
return true
};
/**
* Iterate all the session in the session service.
*
* @param {Function} cb callback function to fetch session
* @api private
*/
SessionService.prototype.forEachSession = function(cb) {
for(var sid in this.sessions) {
cb(this.sessions[sid]);
}
};
/**
* Iterate all the binded session in the session service.
*
* @param {Function} cb callback function to fetch session
* @api private
*/
SessionService.prototype.forEachBindedSession = function(cb) {
var i, l, sessions;
for(var uid in this.uidMap) {
sessions = this.uidMap[uid];
for(i=0, l=sessions.length; i<l; i++) {
cb(sessions[i]);
}
}
};
/**
* Get sessions' quantity in specified server.
*
*/
SessionService.prototype.getSessionsCount = function() {
return utils.size(this.sessions);
};
/**
* Send message to the client that associated with the session.
*
* @api private
*/
var send = function(service, session, msg) {
session.send(msg);
return true;
};
/**
* Session maintains the relationship between client connection and user information.
* There is a session associated with each client connection. And it should bind to a
* user id after the client passes the identification.
*
* Session is created in frontend server and should not be accessed in handler.
* There is a proxy class called BackendSession in backend servers and FrontendSession
* in frontend servers.
*/
var Session = function(sid, frontendId, socket, service) {
EventEmitter.call(this);
this.id = sid; // r
this.frontendId = frontendId; // r
this.uid = null; // r
this.settings = {};
// private
this.__socket__ = socket;
this.__sessionService__ = service;
this.__state__ = ST_INITED;
};
util.inherits(Session, EventEmitter);
/*
* Export current session as frontend session.
*/
Session.prototype.toFrontendSession = function() {
return new FrontendSession(this);
};
/**
* Bind the session with the the uid.
*
* @param {Number} uid User id
* @api public
*/
Session.prototype.bind = function(uid) {
this.uid = uid;
this.emit('bind', uid);
};
/**
* Unbind the session with the the uid.
*
* @param {Number} uid User id
* @api private
*/
Session.prototype.unbind = function(uid) {
this.uid = null;
this.emit('unbind', uid);
};
/**
* Set values (one or many) for the session.
*
* @param {String|Object} key session key
* @param {Object} value session value
* @api public
*/
Session.prototype.set = function(key, value) {
if (utils.isObject(key)) {
for (var i in key) {
this.settings[i] = key[i];
}
} else {
this.settings[key] = value;
}
};
/**
* Remove value from the session.
*
* @param {String} key session key
* @api public
*/
Session.prototype.remove = function(key) {
delete this[key];
};
/**
* Get value from the session.
*
* @param {String} key session key
* @return {Object} value associated with session key
* @api public
*/
Session.prototype.get = function(key) {
return this.settings[key];
};
/**
* Send message to the session.
*
* @param {Object} msg final message sent to client
*/
Session.prototype.send = function(msg) {
this.__socket__.send(msg);
};
/**
* Send message to the session in batch.
*
* @param {Array} msgs list of message
*/
Session.prototype.sendBatch = function(msgs) {
this.__socket__.sendBatch(msgs);
};
/**
* Closed callback for the session which would disconnect client in next tick.
*
* @api public
*/
Session.prototype.closed = function(reason) {
logger.debug('session on [%s] is closed with session id: %s', this.frontendId, this.id);
if(this.__state__ === ST_CLOSED) {
return;
}
this.__state__ = ST_CLOSED;
this.__sessionService__.remove(this.id);
this.emit('closed', this.toFrontendSession(), reason);
this.__socket__.emit('closing', reason);
var self = this;
// give a chance to send disconnect message to client
process.nextTick(function() {
self.__socket__.disconnect();
});
};
/**
* Frontend session for frontend server.
*/
var FrontendSession = function(session) {
EventEmitter.call(this);
clone(session, this, FRONTEND_SESSION_FIELDS);
// deep copy for settings
this.settings = dclone(session.settings);
this.__session__ = session;
};
util.inherits(FrontendSession, EventEmitter);
FrontendSession.prototype.bind = function(uid, cb) {
var self = this;
this.__sessionService__.bind(this.id, uid, function(err) {
if(!err) {
self.uid = uid;
}
utils.invokeCallback(cb, err);
});
};
FrontendSession.prototype.unbind = function(uid, cb) {
var self = this;
this.__sessionService__.unbind(this.id, uid, function(err) {
if(!err) {
self.uid = null;
}
utils.invokeCallback(cb, err);
});
};
FrontendSession.prototype.set = function(key, value) {
this.settings[key] = value;
};
FrontendSession.prototype.get = function(key) {
return this.settings[key];
};
FrontendSession.prototype.push = function(key, cb) {
this.__sessionService__.import(this.id, key, this.get(key), cb);
};
FrontendSession.prototype.pushAll = function(cb) {
this.__sessionService__.importAll(this.id, this.settings, cb);
};
FrontendSession.prototype.on = function(event, listener) {
EventEmitter.prototype.on.call(this, event, listener);
this.__session__.on(event, listener);
};
/**
* Export the key/values for serialization.
*
* @api private
*/
FrontendSession.prototype.export = function() {
var res = {};
clone(this, res, EXPORTED_SESSION_FIELDS);
return res;
};
var clone = function(src, dest, includes) {
var f;
for(var i=0, l=includes.length; i<l; i++) {
f = includes[i];
dest[f] = src[f];
}
};
var dclone = function(src) {
var res = {};
for(var f in src) {
res[f] = src[f];
}
return res;
};
================================================
FILE: lib/components/backendSession.js
================================================
var BackendSessionService = require('../common/service/backendSessionService');
module.exports = function(app) {
var service = new BackendSessionService(app);
service.name = '__backendSession__';
// export backend session service to the application context.
app.set('backendSessionService', service, true);
// for compatibility as `LocalSession` is renamed to `BackendSession`
app.set('localSessionService', service, true);
return service;
};
================================================
FILE: lib/components/channel.js
================================================
var ChannelService = require('../common/service/channelService');
module.exports = function(app, opts) {
var service = new ChannelService(app, opts);
app.set('channelService', service, true);
service.name = '__channel__';
return service;
};
================================================
FILE: lib/components/connection.js
================================================
var ConnectionService = require('../common/service/connectionService');
/**
* Connection component for statistics connection status of frontend servers
*/
module.exports = function(app) {
return new Component(app);
};
var Component = function(app) {
this.app = app;
this.service = new ConnectionService(app);
// proxy the service methods except the lifecycle interfaces of component
var method, self = this;
var getFun = function(m) {
return (function() {
return function() {
return self.service[m].apply(self.service, arguments);
};
})();
};
for(var m in this.service) {
if(m !== 'start' && m !== 'stop') {
method = this.service[m];
if(typeof method === 'function') {
this[m] = getFun(m);
}
}
}
};
Component.prototype.name = '__connection__';
================================================
FILE: lib/components/connector.js
================================================
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
var taskManager = require('../common/manager/taskManager');
var pomelo = require('../pomelo');
var rsa = require("node-bignumber");
var events = require('../util/events');
var utils = require('../util/utils');
module.exports = function(app, opts) {
return new Component(app, opts);
};
/**
* Connector component. Receive client requests and attach session with socket.
*
* @param {Object} app current application context
* @param {Object} opts attach parameters
* opts.connector {Object} provides low level network and protocol details implementation between server and clients.
*/
var Component = function(app, opts) {
opts = opts || {};
this.app = app;
this.connector = getConnector(app, opts);
this.encode = opts.encode;
this.decode = opts.decode;
this.useCrypto = opts.useCrypto;
this.useHostFilter = opts.useHostFilter;
this.useAsyncCoder = opts.useAsyncCoder;
this.blacklistFun = opts.blacklistFun;
this.keys = {};
this.blacklist = [];
if (opts.useDict) {
app.load(pomelo.dictionary, app.get('dictionaryConfig'));
}
if (opts.useProtobuf) {
app.load(pomelo.protobuf, app.get('protobufConfig'));
}
// component dependencies
this.server = null;
this.session = null;
this.connection = null;
};
var pro = Component.prototype;
pro.name = '__connector__';
pro.start = function(cb) {
this.server = this.app.components.__server__;
this.session = this.app.components.__session__;
this.connection = this.app.components.__connection__;
// check component dependencies
if (!this.server) {
process.nextTick(function() {
utils.invokeCallback(cb, new Error('fail to start connector component for no server component loaded'));
});
return;
}
if (!this.session) {
process.nextTick(function() {
utils.invokeCallback(cb, new Error('fail to start connector component for no session component loaded'));
});
return;
}
process.nextTick(cb);
};
pro.afterStart = function(cb) {
this.connector.start(cb);
this.connector.on('connection', hostFilter.bind(this, bindEvents));
};
pro.stop = function(force, cb) {
if (this.connector) {
this.connector.stop(force, cb);
this.connector = null;
return;
} else {
process.nextTick(cb);
}
};
pro.send = function(reqId, route, msg, recvs, opts, cb) {
logger.debug('[%s] send message reqId: %s, route: %s, msg: %j, receivers: %j, opts: %j', this.app.serverId, reqId, route, msg, recvs, opts);
if (this.useAsyncCoder) {
return this.sendAsync(reqId, route, msg, recvs, opts, cb);
}
var emsg = msg;
if (this.encode) {
// use costumized encode
emsg = this.encode.call(this, reqId, route, msg);
} else if (this.connector.encode) {
// use connector default encode
emsg = this.connector.encode(reqId, route, msg);
}
this.doSend(reqId, route, emsg, recvs, opts, cb);
};
pro.sendAsync = function(reqId, route, msg, recvs, opts, cb) {
var emsg = msg;
var self = this;
if (this.encode) {
// use costumized encode
this.encode(reqId, route, msg, function(err, encodeMsg) {
if (err) {
return cb(err);
}
emsg = encodeMsg;
self.doSend(reqId, route, emsg, recvs, opts, cb);
});
} else if (this.connector.encode) {
// use connector default encode
this.connector.encode(reqId, route, msg, function(err, encodeMsg) {
if (err) {
return cb(err);
}
emsg = encodeMsg;
self.doSend(reqId, route, emsg, recvs, opts, cb);
});
}
}
pro.doSend = function(reqId, route, emsg, recvs, opts, cb) {
if (!emsg) {
process.nextTick(function() {
return cb && cb(new Error('fail to send message for encode result is empty.'));
});
}
this.app.components.__pushScheduler__.schedule(reqId, route, emsg,
recvs, opts, cb);
}
pro.setPubKey = function(id, key) {
var pubKey = new rsa.Key();
pubKey.n = new rsa.BigInteger(key.rsa_n, 16);
pubKey.e = key.rsa_e;
this.keys[id] = pubKey;
};
pro.getPubKey = function(id) {
return this.keys[id];
};
var getConnector = function(app, opts) {
var connector = opts.connector;
if (!connector) {
return getDefaultConnector(app, opts);
}
if (typeof connector !== 'function') {
return connector;
}
var curServer = app.getCurServer();
return connector(curServer.clientPort, curServer.host, opts);
};
var getDefaultConnector = function(app, opts) {
var DefaultConnector = require('../connectors/sioconnector');
var curServer = app.getCurServer();
return new DefaultConnector(curServer.clientPort, curServer.host, opts);
};
var hostFilter = function(cb, socket) {
if(!this.useHostFilter) {
return cb(this, socket);
}
var ip = socket.remoteAddress.ip;
var check = function(list) {
for (var address in list) {
var exp = new RegExp(list[address]);
if (exp.test(ip)) {
socket.disconnect();
return true;
}
}
return false;
};
// dynamical check
if (this.blacklist.length !== 0 && !!check(this.blacklist)) {
return;
}
// static check
if (!!this.blacklistFun && typeof this.blacklistFun === 'function') {
var self = this;
self.blacklistFun(function(err, list) {
if (!!err) {
logger.error('connector blacklist error: %j', err.stack);
utils.invokeCallback(cb, self, socket);
return;
}
if (!Array.isArray(list)) {
logger.error('connector blacklist is not array: %j', list);
utils.invokeCallback(cb, self, socket);
return;
}
if (!!check(list)) {
return;
} else {
utils.invokeCallback(cb, self, socket);
return;
}
});
} else {
utils.invokeCallback(cb, this, socket);
}
};
var bindEvents = function(self, socket) {
var curServer = self.app.getCurServer();
var maxConnections = curServer['max-connections'];
if (self.connection && maxConnections) {
self.connection.increaseConnectionCount();
var statisticInfo = self.connection.getStatisticsInfo();
if (statisticInfo.totalConnCount > maxConnections) {
logger.warn('the server %s has reached the max connections %s', curServer.id, maxConnections);
socket.disconnect();
return;
}
}
//create session for connection
var session = getSession(self, socket);
var closed = false;
socket.on('disconnect', function() {
if (closed) {
return;
}
closed = true;
if (self.connection) {
self.connection.decreaseConnectionCount(session.uid);
}
});
socket.on('error', function() {
if (closed) {
return;
}
closed = true;
if (self.connection) {
self.connection.decreaseConnectionCount(session.uid);
}
});
// new message
socket.on('message', function(msg) {
var dmsg = msg;
if (self.useAsyncCoder) {
return handleMessageAsync(self, msg, session, socket);
}
if (self.decode) {
dmsg = self.decode(msg, session);
} else if (self.connector.decode) {
dmsg = self.connector.decode(msg, socket);
}
if (!dmsg) {
// discard invalid message
return;
}
// use rsa crypto
if (self.useCrypto) {
var verified = verifyMessage(self, session, dmsg);
if (!verified) {
logger.error('fail to verify the data received from client.');
return;
}
}
handleMessage(self, session, dmsg);
}); //on message end
};
var handleMessageAsync = function(self, msg, session, socket) {
if (self.decode) {
self.decode(msg, session, function(err, dmsg) {
if (err) {
logger.error('fail to decode message from client %s .', err.stack);
return;
}
doHandleMessage(self, dmsg, session);
});
} else if (self.connector.decode) {
self.connector.decode(msg, socket, function(err, dmsg) {
if (err) {
logger.error('fail to decode message from client %s .', err.stack);
return;
}
doHandleMessage(self, dmsg, session);
});
}
}
var doHandleMessage = function(self, dmsg, session) {
if (!dmsg) {
// discard invalid message
return;
}
// use rsa crypto
if (self.useCrypto) {
var verified = verifyMessage(self, session, dmsg);
if (!verified) {
logger.error('fail to verify the data received from client.');
return;
}
}
handleMessage(self, session, dmsg);
}
/**
* get session for current connection
*/
var getSession = function(self, socket) {
var app = self.app,
sid = socket.id;
var session = self.session.get(sid);
if (session) {
return session;
}
session = self.session.create(sid, app.getServerId(), socket);
logger.debug('[%s] getSession session is created with session id: %s', app.getServerId(), sid);
// bind events for session
socket.on('disconnect', session.closed.bind(session));
socket.on('error', session.closed.bind(session));
session.on('closed', onSessionClose.bind(null, app));
session.on('bind', function(uid) {
logger.debug('session on [%s] bind with uid: %s', self.app.serverId, uid);
// update connection statistics if necessary
if (self.connection) {
self.connection.addLoginedUser(uid, {
loginTime: Date.now(),
uid: uid,
address: socket.remoteAddress.ip + ':' + socket.remoteAddress.port
});
}
self.app.event.emit(events.BIND_SESSION, session);
});
session.on('unbind', function(uid) {
if (self.connection) {
self.connection.removeLoginedUser(uid);
}
self.app.event.emit(events.UNBIND_SESSION, session);
});
return session;
};
var onSessionClose = function(app, session, reason) {
taskManager.closeQueue(session.id, true);
app.event.emit(events.CLOSE_SESSION, session);
};
var handleMessage = function(self, session, msg) {
logger.debug('[%s] handleMessage session id: %s, msg: %j', self.app.serverId, session.id, msg);
var type = checkServerType(msg.route);
if (!type) {
logger.error('invalid route string. route : %j', msg.route);
return;
}
self.server.globalHandle(msg, session.toFrontendSession(), function(err, resp, opts) {
if (resp && !msg.id) {
logger.warn('try to response to a notify: %j', msg.route);
return;
}
if (!msg.id && !resp) return;
if (!resp) resp = {};
if (!!err && !resp.code) {
resp.code = 500;
}
opts = {
type: 'response',
userOptions: opts || {}
};
// for compatiablity
opts.isResponse = true;
self.send(msg.id, msg.route, resp, [session.id], opts,
function() {});
});
};
/**
* Get server type form request message.
*/
var checkServerType = function(route) {
if (!route) {
return null;
}
var idx = route.indexOf('.');
if (idx < 0) {
return null;
}
return route.substring(0, idx);
};
var verifyMessage = function(self, session, msg) {
var sig = msg.body.__crypto__;
if (!sig) {
logger.error('receive data from client has no signature [%s]', self.app.serverId);
return false;
}
var pubKey;
if (!session) {
logger.error('could not find session.');
return false;
}
if (!session.get('pubKey')) {
pubKey = self.getPubKey(session.id);
if (!!pubKey) {
delete self.keys[session.id];
session.set('pubKey', pubKey);
} else {
logger.error('could not get public key, session id is %s', session.id);
return false;
}
} else {
pubKey = session.get('pubKey');
}
if (!pubKey.n || !pubKey.e) {
logger.error('could not verify message without public key [%s]', self.app.serverId);
return false;
}
delete msg.body.__crypto__;
var message = JSON.stringify(msg.body);
if (utils.hasChineseChar(message))
message = utils.unicodeToUtf8(message);
return pubKey.verifyString(message, sig);
};
================================================
FILE: lib/components/dictionary.js
================================================
var fs = require('fs');
var path = require('path');
var utils = require('../util/utils');
var Loader = require('pomelo-loader');
var pathUtil = require('../util/pathUtil');
var crypto = require('crypto');
module.exports = function(app, opts) {
return new Component(app, opts);
};
var Component = function(app, opts) {
this.app = app;
this.dict = {};
this.abbrs = {};
this.userDicPath = null;
this.version = "";
//Set user dictionary
var p = path.join(app.getBase(), '/config/dictionary.json');
if(!!opts && !!opts.dict) {
p = opts.dict;
}
if(fs.existsSync(p)) {
this.userDicPath = p;
}
};
var pro = Component.prototype;
pro.name = '__dictionary__';
pro.start = function(cb) {
var servers = this.app.get('servers');
var routes = [];
//Load all the handler files
for(var serverType in servers) {
var p = pathUtil.getHandlerPath(this.app.getBase(), serverType);
if(!p) {
continue;
}
var handlers = Loader.load(p, this.app);
for(var name in handlers) {
var handler = handlers[name];
for(var key in handler) {
if(typeof(handler[key]) === 'function') {
routes.push(serverType + '.' + name + '.' + key);
}
}
}
}
//Sort the route to make sure all the routers abbr are the same in all the servers
routes.sort();
var abbr;
var i;
for(i = 0; i < routes.length; i++) {
abbr = i + 1;
this.abbrs[abbr] = routes[i];
this.dict[routes[i]] = abbr;
}
//Load user dictionary
if(!!this.userDicPath) {
var userDic = require(this.userDicPath);
abbr = routes.length + 1;
for(i = 0; i < userDic.length; i++) {
var route = userDic[i];
this.abbrs[abbr] = route;
this.dict[route] = abbr;
abbr++;
}
}
this.version = crypto.createHash('md5').update(JSON.stringify(this.dict)).digest('base64');
utils.invokeCallback(cb);
};
pro.getDict = function() {
return this.dict;
};
pro.getAbbrs = function() {
return this.abbrs;
};
pro.getVersion = function() {
return this.version;
};
================================================
FILE: lib/components/master.js
================================================
/**
* Component for master.
*/
var Master = require('../master/master');
/**
* Component factory function
*
* @param {Object} app current application context
* @return {Object} component instances
*/
module.exports = function (app, opts) {
return new Component(app, opts);
};
/**
* Master component class
*
* @param {Object} app current application context
*/
var Component = function (app, opts) {
this.master = new Master(app, opts);
};
var pro = Component.prototype;
pro.name = '__master__';
/**
* Component lifecycle function
*
* @param {Function} cb
* @return {Void}
*/
pro.start = function (cb) {
this.master.start(cb);
};
/**
* Component lifecycle function
*
* @param {Boolean} force whether stop the component immediately
* @param {Function} cb
* @return {Void}
*/
pro.stop = function (force, cb) {
this.master.stop(cb);
};
================================================
FILE: lib/components/monitor.js
================================================
/**
* Component for monitor.
* Load and start monitor client.
*/
var Monitor = require('../monitor/monitor');
/**
* Component factory function
*
* @param {Object} app current application context
* @return {Object} component instances
*/
module.exports = function(app, opts) {
return new Component(app, opts);
};
var Component = function(app, opts) {
this.monitor = new Monitor(app, opts);
};
var pro = Component.prototype;
pro.name = '__monitor__';
pro.start = function(cb) {
this.monitor.start(cb);
};
pro.stop = function(force, cb) {
this.monitor.stop(cb);
};
pro.reconnect = function(masterInfo) {
this.monitor.reconnect(masterInfo);
};
================================================
FILE: lib/components/protobuf.js
================================================
var fs = require('fs');
var path = require('path');
var protobuf = require('pomelo-protobuf');
var Constants = require('../util/constants');
var crypto = require('crypto');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
module.exports = function(app, opts) {
return new Component(app, opts);
};
var Component = function(app, opts) {
this.app = app;
opts = opts || {};
this.watchers = {};
this.serverProtos = {};
this.clientProtos = {};
this.version = "";
var env = app.get(Constants.RESERVED.ENV);
var originServerPath = path.join(app.getBase(), Constants.FILEPATH.SERVER_PROTOS);
var presentServerPath = path.join(Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.SERVER_PROTOS));
var originClientPath = path.join(app.getBase(), Constants.FILEPATH.CLIENT_PROTOS);
var presentClientPath = path.join(Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.CLIENT_PROTOS));
this.serverProtosPath = opts.serverProtos || (fs.existsSync(originServerPath) ? Constants.FILEPATH.SERVER_PROTOS : presentServerPath);
this.clientProtosPath = opts.clientProtos || (fs.existsSync(originClientPath) ? Constants.FILEPATH.CLIENT_PROTOS : presentClientPath);
this.setProtos(Constants.RESERVED.SERVER, path.join(app.getBase(), this.serverProtosPath));
this.setProtos(Constants.RESERVED.CLIENT, path.join(app.getBase(), this.clientProtosPath));
protobuf.init({encoderProtos:this.serverProtos, decoderProtos:this.clientProtos});
};
var pro = Component.prototype;
pro.name = '__protobuf__';
pro.encode = function(key, msg) {
return protobuf.encode(key, msg);
};
pro.encode2Bytes = function(key, msg) {
return protobuf.encode2Bytes(key, msg);
};
pro.decode = function(key, msg) {
return protobuf.decode(key, msg);
};
pro.getProtos = function() {
return {
server : this.serverProtos,
client : this.clientProtos,
version : this.version
};
};
pro.getVersion = function() {
return this.version;
};
pro.setProtos = function(type, path) {
if(!fs.existsSync(path)) {
return;
}
if(type === Constants.RESERVED.SERVER) {
this.serverProtos = protobuf.parse(require(path));
}
if(type === Constants.RESERVED.CLIENT) {
this.clientProtos = protobuf.parse(require(path));
}
var protoStr = JSON.stringify(this.clientProtos) + JSON.stringify(this.serverProtos);
this.version = crypto.createHash('md5').update(protoStr).digest('base64');
//Watch file
var watcher = fs.watch(path, this.onUpdate.bind(this, type, path));
if (this.watchers[type]) {
this.watchers[type].close();
}
this.watchers[type] = watcher;
};
pro.onUpdate = function(type, path, event) {
if(event !== 'change') {
return;
}
var self = this;
fs.readFile(path, 'utf8' ,function(err, data) {
try {
var protos = protobuf.parse(JSON.parse(data));
if(type === Constants.RESERVED.SERVER) {
protobuf.setEncoderProtos(protos);
self.serverProtos = protos;
} else {
protobuf.setDecoderProtos(protos);
self.clientProtos = protos;
}
var protoStr = JSON.stringify(self.clientProtos) + JSON.stringify(self.serverProtos);
self.version = crypto.createHash('md5').update(protoStr).digest('base64');
logger.info('change proto file , type : %j, path : %j, version : %j', type, path, self.version);
} catch(e) {
logger.warn("change proto file error! path : %j", path);
logger.warn(e);
}
});
};
pro.stop = function(force, cb) {
for (var type in this.watchers) {
this.watchers[type].close();
}
this.watchers = {};
process.nextTick(cb);
};
================================================
FILE: lib/components/proxy.js
================================================
/**
* Component for proxy.
* Generate proxies for rpc client.
*/
var crc = require('crc');
var utils = require('../util/utils');
var events = require('../util/events');
var Client = require('pomelo-rpc').client;
var pathUtil = require('../util/pathUtil');
var Constants = require('../util/constants');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
/**
* Component factory function
*
* @param {Object} app current application context
* @param {Object} opts construct parameters
* opts.router: (optional) rpc message route function, route(routeParam, msg, cb),
* opts.mailBoxFactory: (optional) mail box factory instance.
* @return {Object} component instance
*/
module.exports = function(app, opts) {
opts = opts || {};
// proxy default config
// cacheMsg is deprecated, just for compatibility here.
opts.bufferMsg = opts.bufferMsg || opts.cacheMsg || false;
opts.interval = opts.interval || 30;
opts.router = genRouteFun();
opts.context = app;
opts.routeContext = app;
if (app.enabled('rpcDebugLog')) {
opts.rpcDebugLog = true;
opts.rpcLogger = require('pomelo-logger').getLogger('rpc-debug', __filename);
}
return new Component(app, opts);
};
/**
* Proxy component class
*
* @param {Object} app current application context
* @param {Object} opts construct parameters
*/
var Component = function(app, opts) {
this.app = app;
this.opts = opts;
this.client = genRpcClient(this.app, opts);
this.app.event.on(events.ADD_SERVERS, this.addServers.bind(this));
this.app.event.on(events.REMOVE_SERVERS, this.removeServers.bind(this));
this.app.event.on(events.REPLACE_SERVERS, this.replaceServers.bind(this));
};
var pro = Component.prototype;
pro.name = '__proxy__';
/**
* Proxy component lifecycle function
*
* @param {Function} cb
* @return {Void}
*/
pro.start = function(cb) {
if(this.opts.enableRpcLog) {
logger.warn('enableRpcLog is deprecated in 0.8.0, please use app.rpcFilter(pomelo.rpcFilters.rpcLog())');
}
var rpcBefores = this.app.get(Constants.KEYWORDS.RPC_BEFORE_FILTER);
var rpcAfters = this.app.get(Constants.KEYWORDS.RPC_AFTER_FILTER);
var rpcErrorHandler = this.app.get(Constants.RESERVED.RPC_ERROR_HANDLER);
if(!!rpcBefores) {
this.client.before(rpcBefores);
}
if(!!rpcAfters) {
this.client.after(rpcAfters);
}
if(!!rpcErrorHandler) {
this.client.setErrorHandler(rpcErrorHandler);
}
process.nextTick(cb);
};
/**
* Component lifecycle callback
*
* @param {Function} cb
* @return {Void}
*/
pro.afterStart = function(cb) {
var self = this;
this.app.__defineGetter__('rpc', function() {
return self.client.proxies.user;
});
this.app.__defineGetter__('sysrpc', function() {
return self.client.proxies.sys;
});
this.app.set('rpcInvoke', this.client.rpcInvoke.bind(this.client), true);
this.client.start(cb);
};
/**
* Add remote server to the rpc client.
*
* @param {Array} servers server info list, {id, serverType, host, port}
*/
pro.addServers = function(servers) {
if (!servers || !servers.length) {
return;
}
genProxies(this.client, this.app, servers);
this.client.addServers(servers);
};
/**
* Remove remote server from the rpc client.
*
* @param {Array} ids server id list
*/
pro.removeServers = function(ids) {
this.client.removeServers(ids);
};
/**
* Replace remote servers from the rpc client.
*
* @param {Array} ids server id list
*/
pro.replaceServers = function(servers) {
if (!servers || !servers.length) {
return;
}
// update proxies
this.client.proxies = {};
genProxies(this.client, this.app, servers);
this.client.replaceServers(servers);
};
/**
* Proxy for rpc client rpcInvoke.
*
* @param {String} serverId remote server id
* @param {Object} msg rpc message: {serverType: serverType, service: serviceName, method: methodName, args: arguments}
* @param {Function} cb callback function
*/
pro.rpcInvoke = function(serverId, msg, cb) {
this.client.rpcInvoke(serverId, msg, cb);
};
/**
* Generate rpc client
*
* @param {Object} app current application context
* @param {Object} opts contructor parameters for rpc client
* @return {Object} rpc client
*/
var genRpcClient = function(app, opts) {
opts.context = app;
opts.routeContext = app;
if(!!opts.rpcClient) {
return opts.rpcClient.create(opts);
} else {
return Client.create(opts);
}
};
/**
* Generate proxy for the server infos.
*
* @param {Object} client rpc client instance
* @param {Object} app application context
* @param {Array} sinfos server info list
*/
var genProxies = function(client, app, sinfos) {
var item;
for (var i = 0, l = sinfos.length; i < l; i++) {
item = sinfos[i];
if (hasProxy(client, item)) {
continue;
}
client.addProxies(getProxyRecords(app, item));
}
};
/**
* Check a server whether has generated proxy before
*
* @param {Object} client rpc client instance
* @param {Object} sinfo server info
* @return {Boolean} true or false
*/
var hasProxy = function(client, sinfo) {
var proxy = client.proxies;
return !!proxy.sys && !! proxy.sys[sinfo.serverType];
};
/**
* Get proxy path for rpc client.
* Iterate all the remote service path and create remote path record.
*
* @param {Object} app current application context
* @param {Object} sinfo server info, format: {id, serverType, host, port}
* @return {Array} remote path record array
*/
var getProxyRecords = function(app, sinfo) {
var records = [],
appBase = app.getBase(),
record;
// sys remote service path record
if (app.isFrontend(sinfo)) {
record = pathUtil.getSysRemotePath('frontend');
} else {
record = pathUtil.getSysRemotePath('backend');
}
if (record) {
records.push(pathUtil.remotePathRecord('sys', sinfo.serverType, record));
}
// user remote service path record
record = pathUtil.getUserRemotePath(appBase, sinfo.serverType);
if (record) {
records.push(pathUtil.remotePathRecord('user', sinfo.serverType, record));
}
return records;
};
var genRouteFun = function() {
return function(session, msg, app, cb) {
var routes = app.get('__routes__');
if (!routes) {
defaultRoute(session, msg, app, cb);
return;
}
var type = msg.serverType,
route = routes[type] || routes['default'];
if (route) {
route(session, msg, app, cb);
} else {
defaultRoute(session, msg, app, cb);
}
};
};
var defaultRoute = function(session, msg, app, cb) {
var list = app.getServersByType(msg.serverType);
if (!list || !list.length) {
cb(new Error('can not find server info for type:' + msg.serverType));
return;
}
var uid = session ? (session.uid || '') : '';
var index = Math.abs(crc.crc32(uid.toString())) % list.length;
utils.invokeCallback(cb, null, list[index].id);
};
================================================
FILE: lib/components/pushScheduler.js
================================================
/**
* Scheduler component to schedule message sending.
*/
var DefaultScheduler = require('../pushSchedulers/direct');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
module.exports = function(app, opts) {
return new PushScheduler(app, opts);
};
var PushScheduler = function(app, opts) {
this.app = app;
opts = opts || {};
this.scheduler = getScheduler(this, app, opts);
};
PushScheduler.prototype.name = '__pushScheduler__';
/**
* Component lifecycle callback
*
* @param {Function} cb
* @return {Void}
*/
PushScheduler.prototype.afterStart = function(cb) {
if(this.isSelectable) {
for (var k in this.scheduler) {
var sch = this.scheduler[k];
if(typeof sch.start === 'function') {
sch.start();
}
}
process.nextTick(cb);
} else if(typeof this.scheduler.start === 'function') {
this.scheduler.start(cb);
} else {
process.nextTick(cb);
}
};
/**
* Component lifecycle callback
*
* @param {Function} cb
* @return {Void}
*/
PushScheduler.prototype.stop = function(force, cb) {
if(this.isSelectable) {
for (var k in this.scheduler) {
var sch = this.scheduler[k];
if(typeof sch.stop === 'function') {
sch.stop();
}
}
process.nextTick(cb);
} else if(typeof this.scheduler.stop === 'function') {
this.scheduler.stop(cb);
} else {
process.nextTick(cb);
}
};
/**
* Schedule how the message to send.
*
* @param {Number} reqId request id
* @param {String} route route string of the message
* @param {Object} msg message content after encoded
* @param {Array} recvs array of receiver's session id
* @param {Object} opts options
* @param {Function} cb
*/
PushScheduler.prototype.schedule = function(reqId, route, msg, recvs, opts, cb) {
var self = this;
if(self.isSelectable) {
if(typeof self.selector === 'function') {
self.selector(reqId, route, msg, recvs, opts, function(id) {
if(self.scheduler[id] && typeof self.scheduler[id].schedule === 'function') {
self.scheduler[id].schedule(reqId, route, msg, recvs, opts, cb);
} else {
logger.error('invalid pushScheduler id, id: %j', id);
}
});
} else {
logger.error('the selector for pushScheduler is not a function, selector: %j', self.selector);
}
} else {
if (typeof self.scheduler.schedule === 'function') {
self.scheduler.schedule(reqId, route, msg, recvs, opts, cb);
} else {
logger.error('the scheduler does not have a schedule function, scheduler: %j', self.scheduler);
}
}
};
var getScheduler = function(pushSchedulerComp, app, opts) {
var scheduler = opts.scheduler || DefaultScheduler;
if(typeof scheduler === 'function') {
return scheduler(app, opts);
}
if(Array.isArray(scheduler)) {
var res = {};
scheduler.forEach(function(sch) {
if(typeof sch.scheduler === 'function') {
res[sch.id] = sch.scheduler(app, sch.options);
} else {
res[sch.id] = sch.scheduler;
}
});
pushSchedulerComp.isSelectable = true;
pushSchedulerComp.selector = opts.selector;
return res;
}
return scheduler;
};
================================================
FILE: lib/components/remote.js
================================================
/**
* Component for remote service.
* Load remote service and add to global context.
*/
var fs = require('fs');
var pathUtil = require('../util/pathUtil');
var RemoteServer = require('pomelo-rpc').server;
/**
* Remote component factory function
*
* @param {Object} app current application context
* @param {Object} opts construct parameters
* opts.acceptorFactory {Object}: acceptorFactory.create(opts, cb)
* @return {Object} remote component instances
*/
module.exports = function(app, opts) {
opts = opts || {};
// cacheMsg is deprecated, just for compatibility here.
opts.bufferMsg = opts.bufferMsg || opts.cacheMsg || false;
opts.interval = opts.interval || 30;
if(app.enabled('rpcDebugLog')) {
opts.rpcDebugLog = true;
opts.rpcLogger = require('pomelo-logger').getLogger('rpc-debug', __filename);
}
return new Component(app, opts);
};
/**
* Remote component class
*
* @param {Object} app current application context
* @param {Object} opts construct parameters
*/
var Component = function(app, opts) {
this.app = app;
this.opts = opts;
};
var pro = Component.prototype;
pro.name = '__remote__';
/**
* Remote component lifecycle function
*
* @param {Function} cb
* @return {Void}
*/
pro.start = function(cb) {
this.opts.port = this.app.getCurServer().port;
this.remote = genRemote(this.app, this.opts);
this.remote.start();
process.nextTick(cb);
};
/**
* Remote component lifecycle function
*
* @param {Boolean} force whether stop the component immediately
* @param {Function} cb
* @return {Void}
*/
pro.stop = function(force, cb) {
this.remote.stop(force);
process.nextTick(cb);
};
/**
* Get remote paths from application
*
* @param {Object} app current application context
* @return {Array} paths
*
*/
var getRemotePaths = function(app) {
var paths = [];
var role;
// master server should not come here
if(app.isFrontend()) {
role = 'frontend';
} else {
role = 'backend';
}
var sysPath = pathUtil.getSysRemotePath(role), serverType = app.getServerType();
if(fs.existsSync(sysPath)) {
paths.push(pathUtil.remotePathRecord('sys', serverType, sysPath));
}
var userPath = pathUtil.getUserRemotePath(app.getBase(), serverType);
if(fs.existsSync(userPath)) {
paths.push(pathUtil.remotePathRecord('user', serverType, userPath));
}
return paths;
};
/**
* Generate remote server instance
*
* @param {Object} app current application context
* @param {Object} opts contructor parameters for rpc Server
* @return {Object} remote server instance
*/
var genRemote = function(app, opts) {
opts.paths = getRemotePaths(app);
opts.context = app;
if(!!opts.rpcServer) {
return opts.rpcServer.create(opts);
} else {
return RemoteServer.create(opts);
}
};
================================================
FILE: lib/components/server.js
================================================
/**
* Component for server starup.
*/
var Server = require('../server/server');
/**
* Component factory function
*
* @param {Object} app current application context
* @return {Object} component instance
*/
module.exports = function(app, opts) {
return new Component(app, opts);
};
/**
* Server component class
*
* @param {Object} app current application context
*/
var Component = function(app, opts) {
this.server = Server.create(app, opts);
};
var pro = Component.prototype;
pro.name = '__server__';
/**
* Component lifecycle callback
*
* @param {Function} cb
* @return {Void}
*/
pro.start = function(cb) {
this.server.start();
process.nextTick(cb);
};
/**
* Component lifecycle callback
*
* @param {Function} cb
* @return {Void}
*/
pro.afterStart = function(cb) {
this.server.afterStart();
process.nextTick(cb);
};
/**
* Component lifecycle function
*
* @param {Boolean} force whether stop the component immediately
* @param {Function} cb
* @return {Void}
*/
pro.stop = function(force, cb) {
this.server.stop();
process.nextTick(cb);
};
/**
* Proxy server handle
*/
pro.handle = function(msg, session, cb) {
this.server.handle(msg, session, cb);
};
/**
* Proxy server global handle
*/
pro.globalHandle = function(msg, session, cb) {
this.server.globalHandle(msg, session, cb);
};
================================================
FILE: lib/components/session.js
================================================
var SessionService = require('../common/service/sessionService');
module.exports = function(app, opts) {
var cmp = new Component(app, opts);
app.set('sessionService', cmp, true);
return cmp;
};
/**
* Session component. Manage sessions.
*
* @param {Object} app current application context
* @param {Object} opts attach parameters
*/
var Component = function(app, opts) {
opts = opts || {};
this.app = app;
this.service = new SessionService(opts);
var getFun = function(m) {
return (function() {
return function() {
return self.service[m].apply(self.service, arguments);
};
})();
};
// proxy the service methods except the lifecycle interfaces of component
var method, self = this;
for(var m in this.service) {
if(m !== 'start' && m !== 'stop') {
method = this.service[m];
if(typeof method === 'function') {
this[m] = getFun(m);
}
}
}
};
Component.prototype.name = '__session__';
================================================
FILE: lib/connectors/commands/handshake.js
================================================
var pomelo = require('../../pomelo');
var Package = require('pomelo-protocol').Package;
var CODE_OK = 200;
var CODE_USE_ERROR = 500;
var CODE_OLD_CLIENT = 501;
/**
* Process the handshake request.
*
* @param {Object} opts option parameters
* opts.handshake(msg, cb(err, resp)) handshake callback. msg is the handshake message from client.
* opts.hearbeat heartbeat interval (level?)
* opts.version required client level
*/
var Command = function(opts) {
opts = opts || {};
this.userHandshake = opts.handshake;
if(opts.heartbeat) {
this.heartbeatSec = opts.heartbeat;
this.heartbeat = opts.heartbeat * 1000;
}
this.checkClient = opts.checkClient;
this.useDict = opts.useDict;
this.useProtobuf = opts.useProtobuf;
this.useCrypto = opts.useCrypto;
};
module.exports = Command;
Command.prototype.handle = function(socket, msg) {
if(!msg.sys) {
processError(socket, CODE_USE_ERROR);
return;
}
if(typeof this.checkClient === 'function') {
if(!msg || !msg.sys || !this.checkClient(msg.sys.type, msg.sys.version)) {
processError(socket, CODE_OLD_CLIENT);
return;
}
}
var opts = {
heartbeat : setupHeartbeat(this)
};
if(this.useDict) {
var dictVersion = pomelo.app.components.__dictionary__.getVersion();
if(!msg.sys.dictVersion || msg.sys.dictVersion !== dictVersion){
// may be deprecated in future
opts.dict = pomelo.app.components.__dictionary__.getDict();
opts.routeToCode = pomelo.app.components.__dictionary__.getDict();
opts.codeToRoute = pomelo.app.components.__dictionary__.getAbbrs();
opts.dictVersion = dictVersion;
}
opts.useDict = true;
}
if(this.useProtobuf) {
var protoVersion = pomelo.app.components.__protobuf__.getVersion();
if(!msg.sys.protoVersion || msg.sys.protoVersion !== protoVersion){
opts.protos = pomelo.app.components.__protobuf__.getProtos();
}
opts.useProto = true;
}
if(!!pomelo.app.components.__decodeIO__protobuf__) {
if(!!this.useProtobuf) {
throw new Error('protobuf can not be both used in the same project.');
}
var version = pomelo.app.components.__decodeIO__protobuf__.getVersion();
if(!msg.sys.protoVersion || msg.sys.protoVersion < version) {
opts.protos = pomelo.app.components.__decodeIO__protobuf__.getProtos();
}
opts.useProto = true;
}
if(this.useCrypto) {
pomelo.app.components.__connector__.setPubKey(socket.id, msg.sys.rsa);
}
if(typeof this.userHandshake === 'function') {
this.userHandshake(msg, function(err, resp) {
if(err) {
process.nextTick(function() {
processError(socket, CODE_USE_ERROR);
});
return;
}
process.nextTick(function() {
response(socket, opts, resp);
});
}, socket);
return;
}
process.nextTick(function() {
response(socket, opts);
});
};
var setupHeartbeat = function(self) {
return self.heartbeatSec;
};
var response = function(socket, sys, resp) {
var res = {
code: CODE_OK,
sys: sys
};
if(resp) {
res.user = resp;
}
socket.handshakeResponse(Package.encode(Package.TYPE_HANDSHAKE, new Buffer(JSON.stringify(res))));
};
var processError = function(socket, code) {
var res = {
code: code
};
socket.sendForce(Package.encode(Package.TYPE_HANDSHAKE, new Buffer(JSON.stringify(res))));
process.nextTick(function() {
socket.disconnect();
});
};
================================================
FILE: lib/connectors/commands/heartbeat.js
================================================
var Package = require('pomelo-protocol').Package;
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
/**
* Process heartbeat request.
*
* @param {Object} opts option request
* opts.heartbeat heartbeat interval
*/
var Command = function(opts) {
opts = opts || {};
this.heartbeat = null;
this.timeout = null;
this.disconnectOnTimeout = opts.disconnectOnTimeout;
if(opts.heartbeat) {
this.heartbeat = opts.heartbeat * 1000; // heartbeat interval
this.timeout = opts.timeout * 1000 || this.heartbeat * 2; // max heartbeat message timeout
this.disconnectOnTimeout = true;
}
this.timeouts = {};
this.clients = {};
};
module.exports = Command;
Command.prototype.handle = function(socket) {
if(!this.heartbeat) {
// no heartbeat setting
return;
}
var self = this;
if(!this.clients[socket.id]) {
// clear timers when socket disconnect or error
this.clients[socket.id] = 1;
socket.once('disconnect', clearTimers.bind(null, this, socket.id));
socket.once('error', clearTimers.bind(null, this, socket.id));
}
// clear timeout timer
if(self.disconnectOnTimeout) {
this.clear(socket.id);
}
socket.sendRaw(Package.encode(Package.TYPE_HEARTBEAT));
if(self.disconnectOnTimeout) {
self.timeouts[socket.id] = setTimeout(function() {
logger.info('client %j heartbeat timeout.', socket.id);
socket.disconnect();
}, self.timeout);
}
};
Command.prototype.clear = function(id) {
var tid = this.timeouts[id];
if(tid) {
clearTimeout(tid);
delete this.timeouts[id];
}
};
var clearTimers = function(self, id) {
delete self.clients[id];
var tid = self.timeouts[id];
if(tid) {
clearTimeout(tid);
delete self.timeouts[id];
}
};
================================================
FILE: lib/connectors/commands/kick.js
================================================
var Package = require('pomelo-protocol').Package;
module.exports.handle = function(socket, reason) {
// websocket close code 1000 would emit when client close the connection
if(typeof reason === 'string') {
var res = {
reason: reason
};
socket.sendRaw(Package.encode(Package.TYPE_KICK, new Buffer(JSON.stringify(res))));
}
};
================================================
FILE: lib/connectors/common/coder.js
================================================
var Message = require('pomelo-protocol').Message;
var Constants = require('../../util/constants');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
var encode = function(reqId, route, msg) {
if(!!reqId) {
return composeResponse(this, reqId, route, msg);
} else {
return composePush(this, route, msg);
}
};
var decode = function(msg) {
msg = Message.decode(msg.body);
var route = msg.route;
// decode use dictionary
if(!!msg.compressRoute) {
if(!!this.connector.useDict) {
var abbrs = this.dictionary.getAbbrs();
if(!abbrs[route]) {
logger.error('dictionary error! no abbrs for route : %s', route);
return null;
}
route = msg.route = abbrs[route];
} else {
logger.error('fail to uncompress route code for msg: %j, server not enable dictionary.', msg);
return null;
}
}
// decode use protobuf
if(!!this.protobuf && !!this.protobuf.getProtos().client[route]) {
msg.body = this.protobuf.decode(route, msg.body);
} else if(!!this.decodeIO_protobuf && !!this.decodeIO_protobuf.check(Constants.RESERVED.CLIENT, route)) {
msg.body = this.decodeIO_protobuf.decode(route, msg.body);
} else {
try {
msg.body = JSON.parse(msg.body.toString('utf8'));
} catch (ex) {
msg.body = {};
}
}
return msg;
};
var composeResponse = function(server, msgId, route, msgBody) {
if(!msgId || !route || !msgBody) {
return null;
}
msgBody = encodeBody(server, route, msgBody);
return Message.encode(msgId, Message.TYPE_RESPONSE, 0, null, msgBody);
};
var composePush = function(server, route, msgBody) {
if(!route || !msgBody){
return null;
}
msgBody = encodeBody(server, route, msgBody);
// encode use dictionary
var compressRoute = 0;
if(!!server.dictionary) {
var dict = server.dictionary.getDict();
if(!!server.connector.useDict && !!dict[route]) {
route = dict[route];
compressRoute = 1;
}
}
return Message.encode(0, Message.TYPE_PUSH, compressRoute, route, msgBody);
};
var encodeBody = function(server, route, msgBody) {
// encode use protobuf
if(!!server.protobuf && !!server.protobuf.getProtos().server[route]) {
msgBody = server.protobuf.encode(route, msgBody);
} else if(!!server.decodeIO_protobuf && !!server.decodeIO_protobuf.check(Constants.RESERVED.SERVER, route)) {
msgBody = server.decodeIO_protobuf.encode(route, msgBody);
} else {
msgBody = new Buffer(JSON.stringify(msgBody), 'utf8');
}
return msgBody;
};
module.exports = {
encode: encode,
decode: decode
};
================================================
FILE: lib/connectors/common/handler.js
================================================
var protocol = require('pomelo-protocol');
var Package = protocol.Package;
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
var handlers = {};
var ST_INITED = 0;
var ST_WAIT_ACK = 1;
var ST_WORKING = 2;
var ST_CLOSED = 3;
var handleHandshake = function(socket, pkg) {
if(socket.state !== ST_INITED) {
return;
}
try {
socket.emit('handshake', JSON.parse(protocol.strdecode(pkg.body)));
} catch (ex) {
socket.emit('handshake', {});
}
};
var handleHandshakeAck = function(socket, pkg) {
if(socket.state !== ST_WAIT_ACK) {
return;
}
socket.state = ST_WORKING;
socket.emit('heartbeat');
};
var handleHeartbeat = function(socket, pkg) {
if(socket.state !== ST_WORKING) {
return;
}
socket.emit('heartbeat');
};
var handleData = function(socket, pkg) {
if(socket.state !== ST_WORKING) {
return;
}
socket.emit('message', pkg);
};
handlers[Package.TYPE_HANDSHAKE] = handleHandshake;
handlers[Package.TYPE_HANDSHAKE_ACK] = handleHandshakeAck;
handlers[Package.TYPE_HEARTBEAT] = handleHeartbeat;
handlers[Package.TYPE_DATA] = handleData;
var handle = function(socket, pkg) {
var handler = handlers[pkg.type];
if(!!handler) {
handler(socket, pkg);
} else {
logger.error('could not find handle invalid data package.');
socket.disconnect();
}
};
module.exports = handle;
================================================
FILE: lib/connectors/hybrid/switcher.js
================================================
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var WSProcessor = require('./wsprocessor');
var TCPProcessor = require('./tcpprocessor');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
var HTTP_METHODS = [
'GET', 'POST', 'DELETE', 'PUT', 'HEAD'
];
var ST_STARTED = 1;
var ST_CLOSED = 2;
var DEFAULT_TIMEOUT = 90;
/**
* Switcher for tcp and websocket protocol
*
* @param {Object} server tcp server instance from node.js net module
*/
var Switcher = function(server, opts) {
EventEmitter.call(this);
this.server = server;
this.wsprocessor = new WSProcessor();
this.tcpprocessor = new TCPProcessor(opts.closeMethod);
this.id = 1;
this.timeout = (opts.timeout || DEFAULT_TIMEOUT) * 1000;
this.setNoDelay = opts.setNoDelay;
if (!opts.ssl) {
this.server.on('connection', this.newSocket.bind(this));
} else {
this.server.on('secureConnection', this.newSocket.bind(this));
this.server.on('clientError', function(e, tlsSo) {
logger.warn('an ssl error occured before handshake established: ', e);
tlsSo.destroy();
});
}
this.wsprocessor.on('connection', this.emit.bind(this, 'connection'));
this.tcpprocessor.on('connection', this.emit.bind(this, 'connection'));
this.state = ST_STARTED;
};
util.inherits(Switcher, EventEmitter);
module.exports = Switcher;
Switcher.prototype.newSocket = function(socket) {
if(this.state !== ST_STARTED) {
return;
}
socket.setTimeout(this.timeout, function() {
logger.warn('connection is timeout without communication, the remote ip is %s && port is %s',
socket.remoteAddress, socket.remotePort);
socket.destroy();
});
var self = this;
socket.once('data', function(data) {
// FIXME: handle incomplete HTTP method
if(isHttp(data)) {
processHttp(self, self.wsprocessor, socket, data);
} else {
if(!!self.setNoDelay) {
socket.setNoDelay(true);
}
processTcp(self, self.tcpprocessor, socket, data);
}
});
};
Switcher.prototype.close = function() {
if(this.state !== ST_STARTED) {
return;
}
this.state = ST_CLOSED;
this.wsprocessor.close();
this.tcpprocessor.close();
};
var isHttp = function(data) {
var head = data.toString('utf8', 0, 4);
for(var i=0, l=HTTP_METHODS.length; i<l; i++) {
if(head.indexOf(HTTP_METHODS[i]) === 0) {
return true;
}
}
return false;
};
var processHttp = function(switcher, processor, socket, data) {
processor.add(socket, data);
};
var processTcp = function(switcher, processor, socket, data) {
processor.add(socket, data);
};
================================================
FILE: lib/connectors/hybrid/tcpprocessor.js
================================================
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var utils = require('../../util/utils');
var TcpSocket = require('./tcpsocket');
var ST_STARTED = 1;
var ST_CLOSED = 2;
// private protocol, no need exports
var HEAD_SIZE = 4;
/**
* websocket protocol processor
*/
var Processor = function(closeMethod) {
EventEmitter.call(this);
this.closeMethod = closeMethod;
this.state = ST_STARTED;
};
util.inherits(Processor, EventEmitter);
module.exports = Processor;
Processor.prototype.add = function(socket, data) {
if(this.state !== ST_STARTED) {
return;
}
var tcpsocket = new TcpSocket(socket, {headSize: HEAD_SIZE,
headHandler: utils.headHandler,
closeMethod: this.closeMethod});
this.emit('connection', tcpsocket);
socket.emit('data', data);
};
Processor.prototype.close = function() {
if(this.state !== ST_STARTED) {
return;
}
this.state = ST_CLOSED;
};
================================================
FILE: lib/connectors/hybrid/tcpsocket.js
================================================
var Stream = require('stream');
var util = require('util');
var protocol = require('pomelo-protocol');
var Package = protocol.Package;
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
/**
* Work states
*/
var ST_HEAD = 1; // wait for head
var ST_BODY = 2; // wait for body
var ST_CLOSED = 3; // closed
/**
* Tcp socket wrapper with package compositing.
* Collect the package from socket and emit a completed package with 'data' event.
* Uniform with ws.WebSocket interfaces.
*
* @param {Object} socket origin socket from node.js net module
* @param {Object} opts options parameter.
* opts.headSize size of package head
* opts.headHandler(headBuffer) handler for package head. caculate and return body size from head data.
*/
var Socket = function(socket, opts) {
if(!(this instanceof Socket)) {
return new Socket(socket, opts);
}
if(!socket || !opts) {
throw new Error('invalid socket or opts');
}
if(!opts.headSize || typeof opts.headHandler !== 'function') {
throw new Error('invalid opts.headSize or opts.headHandler');
}
// stream style interfaces.
// TODO: need to port to stream2 after node 0.9
Stream.call(this);
this.readable = true;
this.writeable = true;
this._socket = socket;
this.headSize = opts.headSize;
this.closeMethod = opts.closeMethod;
this.headBuffer = new Buffer(opts.headSize);
this.headHandler = opts.headHandler;
this.headOffset = 0;
this.packageOffset = 0;
this.packageSize = 0;
this.packageBuffer = null;
// bind event form the origin socket
this._socket.on('data', ondata.bind(null, this));
this._socket.on('end', onend.bind(null, this));
this._socket.on('error', this.emit.bind(this, 'error'));
this._socket.on('close', this.emit.bind(this, 'close'));
this.state = ST_HEAD;
};
util.inherits(Socket, Stream);
module.exports = Socket;
Socket.prototype.send = function(msg, encode, cb) {
this._socket.write(msg, encode, cb);
};
Socket.prototype.close = function() {
if(!!this.closeMethod && this.closeMethod === 'end') {
this._socket.end();
} else {
try {
this._socket.destroy();
} catch (e) {
logger.error('socket close with destroy error: %j', e.stack);
}
}
};
var ondata = function(socket, chunk) {
if(socket.state === ST_CLOSED) {
throw new Error('socket has closed');
}
if(typeof chunk !== 'string' && !Buffer.isBuffer(chunk)) {
throw new Error('invalid data');
}
if(typeof chunk === 'string') {
chunk = new Buffer(chunk, 'utf8');
}
var offset = 0, end = chunk.length;
while(offset < end && socket.state !== ST_CLOSED) {
if(socket.state === ST_HEAD) {
offset = readHead(socket, chunk, offset);
}
if(socket.state === ST_BODY) {
offset = readBody(socket, chunk, offset);
}
}
return true;
};
var onend = function(socket, chunk) {
if(chunk) {
socket._socket.write(chunk);
}
socket.state = ST_CLOSED;
reset(socket);
socket.emit('end');
};
/**
* Read head segment from data to socket.headBuffer.
*
* @param {Object} socket Socket instance
* @param {Object} data Buffer instance
* @param {Number} offset offset read star from data
* @return {Number} new offset of data after read
*/
var readHead = function(socket, data, offset) {
var hlen = socket.headSize - socket.headOffset;
var dlen = data.length - offset;
var len = Math.min(hlen, dlen);
var dend = offset + len;
data.copy(socket.headBuffer, socket.headOffset, offset, dend);
socket.headOffset += len;
if(socket.headOffset === socket.headSize) {
// if head segment finished
var size = socket.headHandler(socket.headBuffer);
if(size < 0) {
throw new Error('invalid body size: ' + size);
}
// check if header contains a valid type
if(checkTypeData(socket.headBuffer[0])) {
socket.packageSize = size + socket.headSize;
socket.packageBuffer = new Buffer(socket.packageSize);
socket.headBuffer.copy(socket.packageBuffer, 0, 0, socket.headSize);
socket.packageOffset = socket.headSize;
socket.state = ST_BODY;
} else {
dend = data.length;
logger.error('close the connection with invalid head message, the remote ip is %s && port is %s && message is %j', socket._socket.remoteAddress, socket._socket.remotePort, data);
socket.close();
}
}
return dend;
};
/**
* Read body segment from data buffer to socket.packageBuffer;
*
* @param {Object} socket Socket instance
* @param {Object} data Buffer instance
* @param {Number} offset offset read star from data
* @return {Number} new offset of data after read
*/
var readBody = function(socket, data, offset) {
var blen = socket.packageSize - socket.packageOffset;
var dlen = data.length - offset;
var len = Math.min(blen, dlen);
var dend = offset + len;
data.copy(socket.packageBuffer, socket.packageOffset, offset, dend);
socket.packageOffset += len;
if(socket.packageOffset === socket.packageSize) {
// if all the package finished
var buffer = socket.packageBuffer;
socket.emit('message', buffer);
reset(socket);
}
return dend;
};
var reset = function(socket) {
socket.headOffset = 0;
socket.packageOffset = 0;
socket.packageSize = 0;
socket.packageBuffer = null;
socket.state = ST_HEAD;
};
var checkTypeData = function(data) {
return data === Package.TYPE_HANDSHAKE || data === Package.TYPE_HANDSHAKE_ACK || data === Package.TYPE_HEARTBEAT || data === Package.TYPE_DATA || data === Package.TYPE_KICK;
};
================================================
FILE: lib/connectors/hybrid/wsprocessor.js
================================================
var HttpServer = require('http').Server;
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var WebSocketServer = require('ws').Server;
var ST_STARTED = 1;
var ST_CLOSED = 2;
/**
* websocket protocol processor
*/
var Processor = function() {
EventEmitter.call(this);
this.httpServer = new HttpServer();
var self = this;
this.wsServer = new WebSocketServer({server: this.httpServer});
this.wsServer.on('connection', function(socket) {
// emit socket to outside
self.emit('connection', socket);
});
this.state = ST_STARTED;
};
util.inherits(Processor, EventEmitter);
module.exports = Processor;
Processor.prototype.add = function(socket, data) {
if(this.state !== ST_STARTED) {
return;
}
this.httpServer.emit('connection', socket);
if(typeof socket.ondata === 'function') {
// compatible with stream2
socket.ondata(data, 0, data.length);
} else {
// compatible with old stream
socket.emit('data', data);
}
};
Processor.prototype.close = function() {
if(this.state !== ST_STARTED) {
return;
}
this.state = ST_CLOSED;
this.wsServer.close();
this.wsServer = null;
this.httpServer = null;
};
================================================
FILE: lib/connectors/hybridconnector.js
================================================
var net = require('net');
var tls = require('tls');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var HybridSocket = require('./hybridsocket');
var Switcher = require('./hybrid/switcher');
var Handshake = require('./commands/handshake');
var Heartbeat = require('./commands/heartbeat');
var Kick = require('./commands/kick');
var coder = require('./common/coder');
var curId = 1;
/**
* Connector that manager low level connection and protocol bewteen server and client.
* Develper can provide their own connector to switch the low level prototol, such as tcp or probuf.
*/
var Connector = function(port, host, opts) {
if (!(this instanceof Connector)) {
return new Connector(port, host, opts);
}
EventEmitter.call(this);
this.opts = opts || {};
this.port = port;
this.host = host;
this.useDict = opts.useDict;
this.useProtobuf = opts.useProtobuf;
this.handshake = new Handshake(opts);
this.heartbeat = new Heartbeat(opts);
this.distinctHost = opts.distinctHost;
this.ssl = opts.ssl;
this.switcher = null;
};
util.inherits(Connector, EventEmitter);
module.exports = Connector;
/**
* Start connector to listen the specified port
*/
Connector.prototype.start = function(cb) {
var app = require('../pomelo').app;
var self = this;
var gensocket = function(socket) {
var hybridsocket = new HybridSocket(curId++, socket);
hybridsocket.on('handshake', self.handshake.handle.bind(self.handshake, hybridsocket));
hybridsocket.on('heartbeat', self.heartbeat.handle.bind(self.heartbeat, hybridsocket));
hybridsocket.on('disconnect', self.heartbeat.clear.bind(self.heartbeat, hybridsocket.id));
hybridsocket.on('closing', Kick.handle.bind(null, hybridsocket));
self.emit('connection', hybridsocket);
};
this.connector = app.components.__connector__.connector;
this.dictionary = app.components.__dictionary__;
this.protobuf = app.components.__protobuf__;
this.decodeIO_protobuf = app.components.__decodeIO__protobuf__;
if(!this.ssl) {
this.listeningServer = net.createServer();
} else {
this.listeningServer = tls.createServer(this.ssl);
}
this.switcher = new Switcher(this.listeningServer, self.opts);
this.switcher.on('connection', function(socket) {
gensocket(socket);
});
if(!!this.distinctHost) {
this.listeningServer.listen(this.port, this.host);
} else {
this.listeningServer.listen(this.port);
}
process.nextTick(cb);
};
Connector.prototype.stop = function(force, cb) {
this.switcher.close();
this.listeningServer.close();
process.nextTick(cb);
};
Connector.decode = Connector.prototype.decode = coder.decode;
Connector.encode = Connector.prototype.encode = coder.encode;
================================================
FILE: lib/connectors/hybridsocket.js
================================================
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var handler = require('./common/handler');
var protocol = require('pomelo-protocol');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
var Package = protocol.Package;
var ST_INITED = 0;
var ST_WAIT_ACK = 1;
var ST_WORKING = 2;
var ST_CLOSED = 3;
/**
* Socket class that wraps socket and websocket to provide unified interface for up level.
*/
var Socket = function(id, socket) {
EventEmitter.call(this);
this.id = id;
this.socket = socket;
if(!socket._socket) {
this.remoteAddress = {
ip: socket.address().address,
port: socket.address().port
};
} else {
this.remoteAddress = {
ip: socket._socket.remoteAddress,
port: socket._socket.remotePort
};
}
var self = this;
socket.once('close', this.emit.bind(this, 'disconnect'));
socket.on('error', this.emit.bind(this, 'error'));
socket.on('message', function(msg) {
if(msg) {
msg = Package.decode(msg);
handler(self, msg);
}
});
this.state = ST_INITED;
// TODO: any other events?
};
util.inherits(Socket, EventEmitter);
module.exports = Socket;
/**
* Send raw byte data.
*
* @api private
*/
Socket.prototype.sendRaw = function(msg) {
if(this.state !== ST_WORKING) {
return;
}
var self = this;
this.socket.send(msg, {binary: true}, function(err) {
if(!!err) {
logger.error('websocket send binary data failed: %j', err.stack);
return;
}
});
};
/**
* Send byte data package to client.
*
* @param {Buffer} msg byte data
*/
Socket.prototype.send = function(msg) {
if(msg instanceof String) {
msg = new Buffer(msg);
} else if(!(msg instanceof Buffer)) {
msg = new Buffer(JSON.stringify(msg));
}
this.sendRaw(Package.encode(Package.TYPE_DATA, msg));
};
/**
* Send byte data packages to client in batch.
*
* @param {Buffer} msgs byte data
*/
Socket.prototype.sendBatch = function(msgs) {
var rs = [];
for(var i=0; i<msgs.length; i++) {
var src = Package.encode(Package.TYPE_DATA, msgs[i]);
rs.push(src);
}
this.sendRaw(Buffer.concat(rs));
};
/**
* Send message to client no matter whether handshake.
*
* @api private
*/
Socket.prototype.sendForce = function(msg) {
if(this.state === ST_CLOSED) {
return;
}
this.socket.send(msg, {binary: true});
};
/**
* Response handshake request
*
* @api private
*/
Socket.prototype.handshakeResponse = function(resp) {
if(this.state !== ST_INITED) {
return;
}
this.socket.send(resp, {binary: true});
this.state = ST_WAIT_ACK;
};
/**
* Close the connection.
*
* @api private
*/
Socket.prototype.disconnect = function() {
if(this.state === ST_CLOSED) {
return;
}
this.state = ST_CLOSED;
this.socket.emit('close');
this.socket.close();
};
================================================
FILE: lib/connectors/mqtt/generate.js
================================================
var protocol = require('./protocol');
var crypto = require('crypto');
/* TODO: consider rewriting these functions using buffers instead
* of arrays
*/
/* Publish */
module.exports.publish = function(opts) {
opts = opts || {};
var dup = opts.dup ? protocol.DUP_MASK : 0;
var qos = opts.qos || 0;
var retain = opts.retain ? protocol.RETAIN_MASK : 0;
var topic = opts.topic;
var payload = opts.payload || new Buffer(0);
var id = (typeof opts.messageId === 'undefined') ? randint() : opts.messageId;
var packet = {header: 0, payload: []};
/* Check required fields */
if (typeof topic !== 'string' || topic.length <= 0) return null;
/* if payload is a string, we'll convert it into a buffer */
if(typeof payload == 'string') {
payload = new Buffer(payload);
}
/* accepting only a buffer for payload */
if (!Buffer.isBuffer(payload)) return null;
if (typeof qos !== 'number' || qos < 0 || qos > 2) return null;
if (typeof id !== 'number' || id < 0 || id > 0xFFFF) return null;
/* Generate header */
packet.header = protocol.codes.publish << protocol.CMD_SHIFT | dup | qos << protocol.QOS_SHIFT | retain;
/* Topic name */
packet.payload = packet.payload.concat(gen_string(topic));
/* Message ID */
if (qos > 0) packet.payload = packet.payload.concat(gen_number(id));
var buf = new Buffer([packet.header]
.concat(gen_length(packet.payload.length + payload.length))
.concat(packet.payload));
return Buffer.concat([buf, payload]);
};
/* Requires length be a number > 0 */
var gen_length = function(length) {
if(typeof length !== "number") return null;
if(length < 0) return null;
var len = [];
var digit = 0;
do {
digit = length % 128 | 0;
length = length / 128 | 0;
if (length > 0) {
digit = digit | 0x80;
}
len.push(digit);
} while (length > 0);
return len;
};
var gen_string = function(str, without_length) { /* based on code in (from http://farhadi.ir/downloads/utf8.js) */
if(arguments.length < 2) without_length = false;
if(typeof str !== "string") return null;
if(typeof without_length !== "boolean") return null;
var string = [];
var length = 0;
for(var i = 0; i < str.length; i++) {
var code = str.charCodeAt(i);
if (code < 128) {
string.push(code); ++length;
} else if (code < 2048) {
string.push(192 + ((code >> 6 ) )); ++length;
string.push(128 + ((code ) & 63)); ++length;
} else if (code < 65536) {
string.push(224 + ((code >> 12) )); ++length;
string.push(128 + ((code >> 6 ) & 63)); ++length;
string.push(128 + ((code ) & 63)); ++length;
} else if (code < 2097152) {
string.push(240 + ((code >> 18) )); ++length;
string.push(128 + ((code >> 12) & 63)); ++length;
string.push(128 + ((code >> 6 ) & 63)); ++length;
string.push(128 + ((code ) & 63)); ++length;
} else {
throw new Error("Can't encode character with code "
gitextract_d3h0eibp/
├── .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/
│ │ │ └── servers/
│ │ │ └── connector/
│ │ │ └── handler/
│ │ │ └── entryHandler.js
│ │ ├── app.js
│ │ ├── app.js.mqtt
│ │ ├── app.js.sio
│ │ ├── app.js.sio.wss
│ │ ├── app.js.udp
│ │ ├── app.js.wss
│ │ ├── 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
│ ├── 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
SYMBOL INDEX (155 symbols across 7 files)
FILE: lib/common/service/filterService.js
function next (line 81) | function next(err) {
FILE: lib/pomelo.js
function load (line 125) | function load(path, name) {
FILE: template/web-server/public/js/lib/build/build.js
function require (line 18) | function require(path, parent, orig) {
function lastIndexOf (line 164) | function lastIndexOf(arr, obj) {
function localRequire (line 176) | function localRequire(path) {
function Emitter (line 242) | function Emitter(obj) {
function mixin (line 254) | function mixin(obj) {
function on (line 291) | function on() {
function encode2UTF8 (line 974) | function encode2UTF8(charCode){
function codeLength (line 984) | function codeLength(code){
function checkMsg (line 1041) | function checkMsg(msg, protos){
function encodeMsg (line 1078) | function encodeMsg(buffer, offset, protos, msg){
function encodeProp (line 1101) | function encodeProp(value, type, offset, buffer, protos){
function encodeArray (line 1151) | function encodeArray(array, proto, offset, buffer, protos){
function writeBytes (line 1170) | function writeBytes(buffer, offset, bytes){
function encodeTag (line 1178) | function encodeTag(type, tag){
function decodeMsg (line 1220) | function decodeMsg(msg, protos, length){
function isFinish (line 1247) | function isFinish(msg, protos){
function getHead (line 1253) | function getHead(){
function peekHead (line 1265) | function peekHead(){
function decodeProp (line 1274) | function decodeProp(type, protos){
function decodeArray (line 1307) | function decodeArray(array, type, protos){
function getBytes (line 1319) | function getBytes(flag){
function peekBytes (line 1338) | function peekBytes(){
function F (line 1362) | function F() {}
FILE: template/web-server/public/js/lib/pomeloclient.js
function EventEmitter (line 6) | function EventEmitter() {
function g (line 165) | function g() {
function F (line 312) | function F() {}
function processCall (line 424) | function processCall(msg) {
function filter (line 446) | function filter(msg,route){
FILE: template/web-server/public/js/lib/socket.io.js
function e (line 1) | function e(n){if(r[n])return r[n].exports;var o=r[n]={exports:{},id:n,lo...
function n (line 1) | function n(t,e){"object"===("undefined"==typeof t?"undefined":i(t))&&(e=...
function o (line 1) | function o(t){var e=[];for(var r in t)t.hasOwnProperty(r)&&e.push(encode...
function n (line 1) | function n(t,r){var n=t;r=r||e.location,null==t&&(t=r.protocol+"//"+r.ho...
function o (line 1) | function o(){return"undefined"!=typeof document&&"WebkitAppearance"in do...
function i (line 1) | function i(){var t=arguments,r=this.useColors;if(t[0]=(r?"%c":"")+this.n...
function s (line 1) | function s(){return"object"==typeof console&&console.log&&Function.proto...
function a (line 1) | function a(t){try{null==t?e.storage.removeItem("debug"):e.storage.debug=...
function c (line 1) | function c(){try{return e.storage.debug}catch(t){}if("undefined"!=typeof...
function u (line 1) | function u(){try{return window.localStorage}catch(t){}}
function r (line 1) | function r(){throw new Error("setTimeout has not been defined")}
function n (line 1) | function n(){throw new Error("clearTimeout has not been defined")}
function o (line 1) | function o(t){if(h===setTimeout)return setTimeout(t,0);if((h===r||!h)&&s...
function i (line 1) | function i(t){if(p===clearTimeout)return clearTimeout(t);if((p===n||!p)&...
function s (line 1) | function s(){y&&l&&(y=!1,l.length?d=l.concat(d):g=-1,d.length&&a())}
function a (line 1) | function a(){if(!y){var t=o(s);y=!0;for(var e=d.length;e;){for(l=d,d=[];...
function c (line 1) | function c(t,e){this.fun=t,this.array=e}
function u (line 1) | function u(){}
function n (line 1) | function n(){return e.colors[h++%e.colors.length]}
function o (line 1) | function o(t){function r(){}function o(){var t=o,r=+new Date,i=r-(u||r);...
function i (line 1) | function i(t){e.save(t);for(var r=(t||"").split(/[\s,]+/),n=r.length,o=0...
function s (line 1) | function s(){e.enable("")}
function a (line 1) | function a(t){var r,n;for(r=0,n=e.skips.length;r<n;r++)if(e.skips[r].tes...
function c (line 1) | function c(t){return t instanceof Error?t.stack||t.message:t}
function r (line 1) | function r(t){if(t=String(t),!(t.length>1e4)){var e=/^((?:\d+)?\.?\d+) *...
function n (line 1) | function n(t){return t>=u?Math.round(t/u)+"d":t>=c?Math.round(t/c)+"h":t...
function o (line 1) | function o(t){return i(t,u,"day")||i(t,c,"hour")||i(t,a,"minute")||i(t,s...
function i (line 1) | function i(t,e,r){if(!(t<e))return t<1.5*e?Math.floor(t/e)+" "+r:Math.ce...
function n (line 1) | function n(){}
function o (line 1) | function o(t){var r="",n=!1;return r+=t.type,e.BINARY_EVENT!=t.type&&e.B...
function i (line 1) | function i(t,e){function r(t){var r=d.deconstructPacket(t),n=o(r.packet)...
function s (line 1) | function s(){this.reconstructor=null}
function a (line 1) | function a(t){var r={},n=0;if(r.type=Number(t.charAt(0)),null==e.types[r...
function c (line 1) | function c(t,e){try{t.data=f.parse(e)}catch(t){return h()}return t}
function u (line 1) | function u(t){this.reconPack=t,this.buffers=[]}
function h (line 1) | function h(t){return{type:e.ERROR,data:"parser error"}}
function n (line 1) | function n(){return"WebkitAppearance"in document.documentElement.style||...
function o (line 1) | function o(){var t=arguments,r=this.useColors;if(t[0]=(r?"%c":"")+this.n...
function i (line 1) | function i(){return"object"==typeof console&&console.log&&Function.proto...
function s (line 1) | function s(t){try{null==t?e.storage.removeItem("debug"):e.storage.debug=...
function a (line 1) | function a(){var t;try{t=e.storage.debug}catch(t){}return t}
function c (line 1) | function c(){try{return window.localStorage}catch(t){}}
function n (line 1) | function n(){return e.colors[h++%e.colors.length]}
function o (line 1) | function o(t){function r(){}function o(){var t=o,r=+new Date,i=r-(u||r);...
function i (line 1) | function i(t){e.save(t);for(var r=(t||"").split(/[\s,]+/),n=r.length,o=0...
function s (line 1) | function s(){e.enable("")}
function a (line 1) | function a(t){var r,n;for(r=0,n=e.skips.length;r<n;r++)if(e.skips[r].tes...
function c (line 1) | function c(t){return t instanceof Error?t.stack||t.message:t}
function r (line 1) | function r(t){if(t=""+t,!(t.length>1e4)){var e=/^((?:\d+)?\.?\d+) *(mill...
function n (line 1) | function n(t){return t>=u?Math.round(t/u)+"d":t>=c?Math.round(t/c)+"h":t...
function o (line 1) | function o(t){return i(t,u,"day")||i(t,c,"hour")||i(t,a,"minute")||i(t,s...
function i (line 1) | function i(t,e,r){if(!(t<e))return t<1.5*e?Math.floor(t/e)+" "+r:Math.ce...
function o (line 1) | function o(t,e){function r(t){if(r[t]!==g)return r[t];var o;if("bug-stri...
function r (line 1) | function r(t){if(t)return n(t)}
function n (line 1) | function n(t){for(var e in r.prototype)t[e]=r.prototype[e];return t}
function r (line 1) | function r(){n.off(t,r),e.apply(this,arguments)}
function e (line 1) | function e(t){if(!t)return t;if(o(t)){var i={_placeholder:!0,num:r.lengt...
function r (line 1) | function r(t){if(t&&t._placeholder){var o=e[t.num];return o}if(n(t)){for...
function i (line 1) | function i(e,c,u){if(!e)return e;if(t.Blob&&e instanceof Blob||t.File&&e...
function r (line 1) | function r(t){return e.Buffer&&e.Buffer.isBuffer(t)||e.ArrayBuffer&&t in...
function n (line 1) | function n(t,e){return this instanceof n?(t&&"object"===("undefined"==ty...
function r (line 1) | function r(){~f(o.connecting,n)||o.connecting.push(n)}
function n (line 1) | function n(t,r){if(!(this instanceof n))return new n(t,r);r=r||{},t&&"ob...
function o (line 2) | function o(t){var e={};for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r]);r...
function e (line 2) | function e(){if(f.onlyBinaryUpgrades){var e=!this.supportsBinary&&f.tran...
function r (line 2) | function r(){p||(p=!0,u(),h.close(),h=null)}
function o (line 2) | function o(e){var n=new Error("probe error: "+e);n.transport=h.name,r(),...
function i (line 2) | function i(){o("transport closed")}
function s (line 2) | function s(){o("socket closed")}
function c (line 2) | function c(t){h&&t.name!==h.name&&(a('"%s" works - aborting "%s"',t.name...
function u (line 2) | function u(){h.removeListener("open",e),h.removeListener("error",o),h.re...
function t (line 2) | function t(){n.onClose("forced close"),a("socket closing - telling trans...
function e (line 2) | function e(){n.removeListener("upgrade",e),n.removeListener("upgradeErro...
function r (line 2) | function r(){n.once("upgrade",e),n.once("upgradeError",e)}
function n (line 2) | function n(e){var r,n=!1,a=!1,c=!1!==e.jsonp;if(t.location){var u="https...
function n (line 2) | function n(){}
function o (line 2) | function o(t){if(c.call(this,t),this.requestTimeout=t.requestTimeout,e.l...
function i (line 2) | function i(t){this.method=t.method||"GET",this.uri=t.uri,this.xd=!!t.xd,...
function s (line 2) | function s(){for(var t in i.requests)i.requests.hasOwnProperty(t)&&i.req...
function n (line 2) | function n(t){var e=t&&t.forceBase64;h&&!e||(this.supportsBinary=!1),o.c...
function e (line 2) | function e(){u("paused"),r.readyState="paused",t()}
function t (line 2) | function t(){u("writing close packet"),e.write([{type:"close"}])}
function n (line 2) | function n(t){this.path=t.path,this.hostname=t.hostname,this.port=t.port...
function n (line 2) | function n(t,r){var n="b"+e.packets[t.type]+t.data.data;return r(n)}
function o (line 2) | function o(t,r,n){if(!r)return e.encodeBase64Packet(t,n);var o=t.data,i=...
function i (line 2) | function i(t,r,n){if(!r)return e.encodeBase64Packet(t,n);var o=new FileR...
function s (line 2) | function s(t,r,n){if(!r)return e.encodeBase64Packet(t,n);if(m)return i(t...
function a (line 2) | function a(t){try{t=d.decode(t)}catch(t){return!1}return t}
function c (line 2) | function c(t,e,r){for(var n=new Array(t.length),o=l(t.length,r),i=functi...
function o (line 2) | function o(t){return t.length+":"+t}
function i (line 2) | function i(t,n){e.encodePacket(t,!!s&&r,!0,function(t){n(null,o(t))})}
function n (line 2) | function n(t,r){e.encodePacket(t,!0,!0,function(t){return r(null,t)})}
function n (line 2) | function n(t,r){e.encodePacket(t,!0,!0,function(t){var e=new Uint8Array(...
function n (line 2) | function n(t){function r(t){if(!t)return!1;if(e.Buffer&&e.Buffer.isBuffe...
function r (line 2) | function r(t,e,r){function o(t,n){if(o.count<=0)throw new Error("after c...
function n (line 2) | function n(){}
function s (line 2) | function s(t){for(var e,r,n=[],o=0,i=t.length;o<i;)e=t.charCodeAt(o++),e...
function a (line 2) | function a(t){for(var e,r=t.length,n=-1,o="";++n<r;)e=t[n],e>65535&&(e-=...
function c (line 2) | function c(t,e){return b(t>>e&63|128)}
function u (line 2) | function u(t){if(0==(4294967168&t))return b(t);var e="";return 0==(42949...
function h (line 2) | function h(t){for(var e,r=s(t),n=r.length,o=-1,i="";++o<n;)e=r[o],i+=u(e...
function p (line 2) | function p(){if(v>=m)throw Error("Invalid byte index");var t=255&g[v];if...
function f (line 2) | function f(){var t,e,r,n,o;if(v>m)throw Error("Invalid byte index");if(v...
function l (line 2) | function l(t){g=s(t),m=g.length,v=0;for(var e,r=[];(e=f())!==!1;)r.push(...
function r (line 2) | function r(t){for(var e=0;e<t.length;e++){var r=t[e];if(r.buffer instanc...
function n (line 2) | function n(t,e){e=e||{};var n=new i;r(t);for(var o=0;o<t.length;o++)n.ap...
function o (line 2) | function o(t,e){return r(t),new Blob(t,e||{})}
function n (line 2) | function n(t){if(t)return o(t)}
function o (line 2) | function o(t){for(var e in n.prototype)t[e]=n.prototype[e];return t}
function r (line 2) | function r(){this.off(t,r),e.apply(this,arguments)}
function r (line 2) | function r(t){var e="";do e=s[t%a]+e,t=Math.floor(t/a);while(t>0);return e}
function n (line 2) | function n(t){var e=0;for(h=0;h<t.length;h++)e=e*a+c[t.charAt(h)];return e}
function o (line 2) | function o(){var t=r(+new Date);return t!==i?(u=0,i=t):t+"."+r(u++)}
function n (line 2) | function n(){}
function o (line 2) | function o(t){i.call(this,t),this.query=this.query||{},a||(e.___eio||(e....
function r (line 2) | function r(){n(),e()}
function n (line 2) | function n(){if(o.iframe)try{o.form.removeChild(o.iframe)}catch(t){o.onE...
function n (line 3) | function n(t){var e=t&&t.forceBase64;e&&(this.supportsBinary=!1),this.pe...
function r (line 3) | function r(){n.emit("flush"),setTimeout(function(){n.writable=!0,n.emit(...
function n (line 3) | function n(t,e,r){this.io=t,this.nsp=e,this.json=this,this.ids=0,this.ac...
function r (line 3) | function r(t,e){var r=[];e=e||0;for(var n=e||0;n<t.length;n++)r[n-e]=t[n...
function r (line 3) | function r(t,e,r){return t.on(e,r),{destroy:function(){t.removeListener(...
function r (line 3) | function r(t){t=t||{},this.ms=t.min||100,this.max=t.max||1e4,this.factor...
FILE: test/filters/handler/toobusy.js
function load (line 28) | function load() {
FILE: test/filters/rpc/toobusy.js
function load (line 13) | function load() {
Condensed preview — 156 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (611K chars).
[
{
"path": ".gitignore",
"chars": 216,
"preview": ".project\n*/node-log.log\nlogs/*.log\n*.log\n!.gitignore\nnode_modules/*\nnode_modules_back/*\n.project\n.settings/\n**/*.svn\n*.s"
},
{
"path": ".jshintrc",
"chars": 202,
"preview": "{\n \"node\": true,\n \"camelcase\": true,\n \"eqeqeq\": true,\n \"undef\": true,\n \"unused\": true,\n \"curly\": true,\n \"newcap\":"
},
{
"path": ".travis.yml",
"chars": 85,
"preview": "language: node_js\nnode_js:\n - \"10.15.0\"\nbefore_script:\n - npm install -g grunt-cli\n"
},
{
"path": "AUTHORS",
"chars": 661,
"preview": "* Charlie Crane <xieccy@gmail.com> <twitter:@xiecc> <weibo:@圈圈套圈圈> <github:xiecc>\n* Chang chang <changchang005@gmail.co"
},
{
"path": "History.md",
"chars": 14352,
"preview": "2.2.7 / 2019-11-8\n=================\n * [#1150](https://github.com/NetEase/pomelo/pull/1150)\n2.2.6 / 2019-7-10\n========"
},
{
"path": "LICENSE",
"chars": 1111,
"preview": "(The MIT License)\n\nCopyright (c) 2012-2014 Netease, Inc. and other pomelo contributors\n\nPermission is hereby granted, fr"
},
{
"path": "README.md",
"chars": 5563,
"preview": "## Pomelo -- a fast, scalable game server framework for node.js\n\nPomelo is a fast, scalable game server framework for [n"
},
{
"path": "bin/pomelo",
"chars": 22042,
"preview": "#!/usr/bin/env node\n\n/**\n * Module dependencies.\n */\nvar fs = require('fs'),\n os = require('os'),\n path = require('pat"
},
{
"path": "coverage/blanket.js",
"chars": 119,
"preview": "var path = require('path');\nvar srcDir = path.join(__dirname, '..', 'lib');\n\nrequire('blanket')({\n pattern: srcDir\n});"
},
{
"path": "gruntfile.js",
"chars": 971,
"preview": "'use strict';\n\nmodule.exports = function(grunt) {\n\n grunt.loadNpmTasks('grunt-mocha-test');\n grunt.loadNpmTasks('grunt"
},
{
"path": "index.js",
"chars": 41,
"preview": "module.exports = require('./lib/pomelo');"
},
{
"path": "lib/application.js",
"chars": 26005,
"preview": "/*!\n * Pomelo -- proto\n * Copyright(c) 2012 xiechengchao <xiecc@163.com>\n * MIT Licensed\n */\n\n/**\n * Module dependencies"
},
{
"path": "lib/common/manager/appManager.js",
"chars": 3940,
"preview": "var async = require('async');\nvar utils = require('../../util/utils');\nvar logger = require('pomelo-logger').getLogger('"
},
{
"path": "lib/common/manager/taskManager.js",
"chars": 902,
"preview": "var sequeue = require('seq-queue');\n\nvar manager = module.exports;\n\nvar queues = {};\n\nmanager.timeout = 3000;\n\n/**\n * Ad"
},
{
"path": "lib/common/remote/backend/msgRemote.js",
"chars": 2748,
"preview": "var utils = require('../../../util/utils');\nvar logger = require('pomelo-logger').getLogger('forward-log', __filename);\n"
},
{
"path": "lib/common/remote/frontend/channelRemote.js",
"chars": 1967,
"preview": "/**\n * Remote channel service for frontend server.\n * Receive push request from backend servers and push it to clients.\n"
},
{
"path": "lib/common/remote/frontend/sessionRemote.js",
"chars": 2381,
"preview": "/**\n * Remote session service for frontend server.\n * Set session info for backend servers.\n */\nvar utils = require('../"
},
{
"path": "lib/common/service/backendSessionService.js",
"chars": 9855,
"preview": "/**\n * backend session service for backend session\n */\nvar utils = require('../../util/utils');\n\nvar EXPORTED_FIELDS = ["
},
{
"path": "lib/common/service/channelService.js",
"chars": 13793,
"preview": "var countDownLatch = require('../../util/countDownLatch');\nvar utils = require('../../util/utils');\nvar ChannelRemote = "
},
{
"path": "lib/common/service/connectionService.js",
"chars": 1741,
"preview": "/**\n * connection statistics service\n * record connection, login count and list\n */\nvar Service = function(app) {\n this"
},
{
"path": "lib/common/service/filterService.js",
"chars": 2846,
"preview": "var logger = require('pomelo-logger').getLogger('pomelo', __filename);\n\n/**\n * Filter service.\n * Register and fire befo"
},
{
"path": "lib/common/service/handlerService.js",
"chars": 3505,
"preview": "var fs = require('fs');\nvar utils = require('../../util/utils');\nvar Loader = require('pomelo-loader');\nvar pathUtil = r"
},
{
"path": "lib/common/service/sessionService.js",
"chars": 13948,
"preview": "var EventEmitter = require('events').EventEmitter;\nvar util = require('util');\nvar logger = require('pomelo-logger').get"
},
{
"path": "lib/components/backendSession.js",
"chars": 461,
"preview": "var BackendSessionService = require('../common/service/backendSessionService');\n\nmodule.exports = function(app) {\n var "
},
{
"path": "lib/components/channel.js",
"chars": 249,
"preview": "var ChannelService = require('../common/service/channelService');\n\nmodule.exports = function(app, opts) {\n var service "
},
{
"path": "lib/components/connection.js",
"chars": 844,
"preview": "var ConnectionService = require('../common/service/connectionService');\n\n/**\n * Connection component for statistics conn"
},
{
"path": "lib/components/connector.js",
"chars": 11873,
"preview": "var logger = require('pomelo-logger').getLogger('pomelo', __filename);\nvar taskManager = require('../common/manager/task"
},
{
"path": "lib/components/dictionary.js",
"chars": 2061,
"preview": "var fs = require('fs');\nvar path = require('path');\nvar utils = require('../util/utils');\nvar Loader = require('pomelo-l"
},
{
"path": "lib/components/master.js",
"chars": 876,
"preview": "/**\n * Component for master.\n */\nvar Master = require('../master/master');\n\n/**\n * Component factory function\n *\n * @par"
},
{
"path": "lib/components/monitor.js",
"chars": 674,
"preview": "/**\n * Component for monitor.\n * Load and start monitor client.\n */\nvar Monitor = require('../monitor/monitor');\n\n\n\n/**\n"
},
{
"path": "lib/components/protobuf.js",
"chars": 3646,
"preview": "var fs = require('fs');\nvar path = require('path');\nvar protobuf = require('pomelo-protobuf');\nvar Constants = require('"
},
{
"path": "lib/components/proxy.js",
"chars": 6932,
"preview": "/**\n * Component for proxy.\n * Generate proxies for rpc client.\n */\nvar crc = require('crc');\nvar utils = require('../ut"
},
{
"path": "lib/components/pushScheduler.js",
"chars": 3200,
"preview": "/**\n * Scheduler component to schedule message sending.\n */\n\nvar DefaultScheduler = require('../pushSchedulers/direct');"
},
{
"path": "lib/components/remote.js",
"chars": 2818,
"preview": "/**\n * Component for remote service.\n * Load remote service and add to global context.\n */\nvar fs = require('fs');\nvar p"
},
{
"path": "lib/components/server.js",
"chars": 1340,
"preview": "/**\n * Component for server starup.\n */\nvar Server = require('../server/server');\n\n/**\n * Component factory function\n *\n"
},
{
"path": "lib/components/session.js",
"chars": 986,
"preview": "var SessionService = require('../common/service/sessionService');\n\nmodule.exports = function(app, opts) {\n var cmp = ne"
},
{
"path": "lib/connectors/commands/handshake.js",
"chars": 3518,
"preview": "var pomelo = require('../../pomelo');\nvar Package = require('pomelo-protocol').Package;\n\nvar CODE_OK = 200;\nvar CODE_USE"
},
{
"path": "lib/connectors/commands/heartbeat.js",
"chars": 1788,
"preview": "var Package = require('pomelo-protocol').Package;\nvar logger = require('pomelo-logger').getLogger('pomelo', __filename);"
},
{
"path": "lib/connectors/commands/kick.js",
"chars": 349,
"preview": "var Package = require('pomelo-protocol').Package;\n\nmodule.exports.handle = function(socket, reason) {\n// websocket close"
},
{
"path": "lib/connectors/common/coder.js",
"chars": 2598,
"preview": "var Message = require('pomelo-protocol').Message;\nvar Constants = require('../../util/constants');\nvar logger = require("
},
{
"path": "lib/connectors/common/handler.js",
"chars": 1361,
"preview": "var protocol = require('pomelo-protocol');\nvar Package = protocol.Package;\nvar logger = require('pomelo-logger').getLogg"
},
{
"path": "lib/connectors/hybrid/switcher.js",
"chars": 2630,
"preview": "var EventEmitter = require('events').EventEmitter;\nvar util = require('util');\nvar WSProcessor = require('./wsprocessor'"
},
{
"path": "lib/connectors/hybrid/tcpprocessor.js",
"chars": 999,
"preview": "var EventEmitter = require('events').EventEmitter;\nvar util = require('util');\nvar utils = require('../../util/utils');\n"
},
{
"path": "lib/connectors/hybrid/tcpsocket.js",
"chars": 5617,
"preview": "var Stream = require('stream');\nvar util = require('util');\nvar protocol = require('pomelo-protocol');\nvar Package = pro"
},
{
"path": "lib/connectors/hybrid/wsprocessor.js",
"chars": 1194,
"preview": "var HttpServer = require('http').Server;\nvar EventEmitter = require('events').EventEmitter;\nvar util = require('util');\n"
},
{
"path": "lib/connectors/hybridconnector.js",
"chars": 2739,
"preview": "var net = require('net');\nvar tls = require('tls');\nvar util = require('util');\nvar EventEmitter = require('events').Eve"
},
{
"path": "lib/connectors/hybridsocket.js",
"chars": 2845,
"preview": "var util = require('util');\nvar EventEmitter = require('events').EventEmitter;\nvar handler = require('./common/handler')"
},
{
"path": "lib/connectors/mqtt/generate.js",
"chars": 3249,
"preview": "var protocol = require('./protocol');\nvar crypto = require('crypto');\n\n/* TODO: consider rewriting these functions using"
},
{
"path": "lib/connectors/mqtt/mqttadaptor.js",
"chars": 2087,
"preview": "var Adaptor = function(opts) {\n opts = opts || {};\n this.subReqs = {};\n this.publishRoute = opts.publishRoute;\n this"
},
{
"path": "lib/connectors/mqtt/protocol.js",
"chars": 1104,
"preview": "/* Protocol - protocol constants */\n\n/* Command code => mnemonic */\nmodule.exports.types = {\n 0: 'reserved',\n 1: 'conn"
},
{
"path": "lib/connectors/mqttconnector.js",
"chars": 2495,
"preview": "var util = require('util');\nvar EventEmitter = require('events').EventEmitter;\nvar mqtt = require('mqtt');\nvar constants"
},
{
"path": "lib/connectors/mqttsocket.js",
"chars": 1527,
"preview": "var util = require('util');\nvar EventEmitter = require('events').EventEmitter;\n\nvar ST_INITED = 1;\nvar ST_CLOSED = 2;\n\n/"
},
{
"path": "lib/connectors/sioconnector.js",
"chars": 3454,
"preview": "var util = require('util');\nvar EventEmitter = require('events').EventEmitter;\nvar httpServer = require('http').createSe"
},
{
"path": "lib/connectors/siosocket.js",
"chars": 1522,
"preview": "var util = require('util');\nvar EventEmitter = require('events').EventEmitter;\n\nvar ST_INITED = 0;\nvar ST_CLOSED = 1;\n\n/"
},
{
"path": "lib/connectors/udpconnector.js",
"chars": 2746,
"preview": "var net = require('net');\nvar util = require('util');\nvar dgram = require(\"dgram\");\nvar utils = require('../util/utils')"
},
{
"path": "lib/connectors/udpsocket.js",
"chars": 2175,
"preview": "var util = require('util');\nvar handler = require('./common/handler');\nvar protocol = require('pomelo-protocol');\nvar Pa"
},
{
"path": "lib/filters/handler/serial.js",
"chars": 921,
"preview": "/**\n * Filter to keep request sequence.\n */\nvar logger = require('pomelo-logger').getLogger('pomelo', __filename);\nvar t"
},
{
"path": "lib/filters/handler/time.js",
"chars": 763,
"preview": "/**\n * Filter for statistics.\n * Record used time for each request.\n */\nvar conLogger = require('pomelo-logger').getLogg"
},
{
"path": "lib/filters/handler/timeout.js",
"chars": 1197,
"preview": "/**\n * Filter for timeout.\n * Print a warn information when request timeout.\n */\nvar logger = require('pomelo-logger').g"
},
{
"path": "lib/filters/handler/toobusy.js",
"chars": 700,
"preview": "/**\n * Filter for toobusy.\n * if the process is toobusy, just skip the new request\n */\nvar conLogger = require('pomelo-l"
},
{
"path": "lib/filters/rpc/rpcLog.js",
"chars": 911,
"preview": "/**\n * Filter for rpc log.\n * Record used time for remote process call.\n */\nvar rpcLogger = require('pomelo-logger').get"
},
{
"path": "lib/filters/rpc/toobusy.js",
"chars": 844,
"preview": "/**\n * Filter for rpc log.\n * Reject rpc request when toobusy\n */\nvar rpcLogger = require('pomelo-logger').getLogger('rp"
},
{
"path": "lib/index.js",
"chars": 37,
"preview": "module.exports = require('./pomelo');"
},
{
"path": "lib/master/master.js",
"chars": 3981,
"preview": "var starter = require('./starter');\nvar logger = require('pomelo-logger').getLogger('pomelo', __filename);\nvar crashLogg"
},
{
"path": "lib/master/starter.js",
"chars": 5785,
"preview": "var cp = require('child_process');\nvar logger = require('pomelo-logger').getLogger('pomelo', __filename);\nvar starter = "
},
{
"path": "lib/master/watchdog.js",
"chars": 3538,
"preview": "var logger = require('pomelo-logger').getLogger('pomelo', __filename);\nvar utils = require('../util/utils');\nvar Constan"
},
{
"path": "lib/modules/console.js",
"chars": 12306,
"preview": "/*!\n * Pomelo -- consoleModule serverStop stop/kill\n * Copyright(c) 2012 fantasyni <fantasyni@163.com>\n * MIT Licensed\n "
},
{
"path": "lib/modules/masterwatcher.js",
"chars": 3170,
"preview": "var logger = require('pomelo-logger').getLogger('pomelo', __filename);\nvar utils = require('../util/utils');\nvar Constan"
},
{
"path": "lib/modules/monitorwatcher.js",
"chars": 3782,
"preview": "var logger = require('pomelo-logger').getLogger('pomelo', __filename);\nvar utils = require('../util/utils');\nvar events "
},
{
"path": "lib/monitor/monitor.js",
"chars": 2254,
"preview": "/**\n * Component for monitor.\n * Load and start monitor client.\n */\nvar logger = require('pomelo-logger').getLogger('pom"
},
{
"path": "lib/pomelo.js",
"chars": 2816,
"preview": "/*!\n * Pomelo\n * Copyright(c) 2012 xiechengchao <xiecc@163.com>\n * MIT Licensed\n */\n\n/**\n * Module dependencies.\n */\nvar"
},
{
"path": "lib/pushSchedulers/buffer.js",
"chars": 2703,
"preview": "var utils = require('../util/utils');\nvar DEFAULT_FLUSH_INTERVAL = 20;\n\nvar Service = function(app, opts) {\n if (!(this"
},
{
"path": "lib/pushSchedulers/direct.js",
"chars": 1463,
"preview": "var utils = require('../util/utils');\n\nvar Service = function(app, opts) {\n if (!(this instanceof Service)) {\n retur"
},
{
"path": "lib/server/server.js",
"chars": 11450,
"preview": "/**\n * Implementation of server component.\n * Init and start server instance.\n */\nvar logger = require('pomelo-logger')."
},
{
"path": "lib/util/appUtil.js",
"chars": 7661,
"preview": "var async = require('async');\nvar log = require('./log');\nvar utils = require('./utils');\nvar path = require('path');\nva"
},
{
"path": "lib/util/constants.js",
"chars": 2724,
"preview": "module.exports = {\n KEYWORDS: {\n BEFORE_FILTER: '__befores__',\n AFTER_FILTER: '__afters__',\n GLOBAL_BEFORE_FIL"
},
{
"path": "lib/util/countDownLatch.js",
"chars": 1171,
"preview": "var exp = module.exports;\n\n/**\n * Count down to zero or timeout and invoke cb finally.\n */\nvar CountDownLatch = function"
},
{
"path": "lib/util/events.js",
"chars": 335,
"preview": "module.exports = {\n\tADD_SERVERS: 'add_servers',\n\tREMOVE_SERVERS: 'remove_servers',\n REPLACE_SERVERS: 'replace_servers',"
},
{
"path": "lib/util/log.js",
"chars": 259,
"preview": "var logger = require('pomelo-logger');\n\n/**\n * Configure pomelo logger\n */\nmodule.exports.configure = function(app, file"
},
{
"path": "lib/util/moduleUtil.js",
"chars": 2615,
"preview": "var os = require('os');\nvar admin = require('pomelo-admin');\nvar utils = require('./utils');\nvar Constants = require('./"
},
{
"path": "lib/util/pathUtil.js",
"chars": 2940,
"preview": "var fs = require('fs');\nvar path = require('path');\nvar Constants = require('./constants');\nvar exp = module.exports;\n\n/"
},
{
"path": "lib/util/utils.js",
"chars": 9003,
"preview": "var os = require('os');\nvar util = require('util');\nvar exec = require('child_process').exec;\nvar logger = require('pome"
},
{
"path": "package.json",
"chars": 1269,
"preview": "{\n \"name\": \"pomelo\",\n \"version\": \"2.2.7\",\n \"homepage\": \"https://github.com/NetEase/pomelo\",\n \"repository\": {\n \"ty"
},
{
"path": "template/game-server/app/servers/connector/handler/entryHandler.js",
"chars": 1254,
"preview": "module.exports = function(app) {\n return new Handler(app);\n};\n\nvar Handler = function(app) {\n this.app = app;\n};\n\n/**\n"
},
{
"path": "template/game-server/app.js",
"chars": 505,
"preview": "var pomelo = require('pomelo');\n\n/**\n * Init app for client.\n */\nvar app = pomelo.createApp();\napp.set('name', '$');\n\n//"
},
{
"path": "template/game-server/app.js.mqtt",
"chars": 545,
"preview": "var pomelo = require('pomelo');\n\n/**\n * Init app for client.\n */\nvar app = pomelo.createApp();\napp.set('name', '$');\n\n//"
},
{
"path": "template/game-server/app.js.sio",
"chars": 672,
"preview": "var pomelo = require('pomelo');\n\n/**\n * Init app for client.\n */\nvar app = pomelo.createApp();\napp.set('name', '$');\n\n//"
},
{
"path": "template/game-server/app.js.sio.wss",
"chars": 546,
"preview": "var fs = require('fs');\nvar pomelo = require('pomelo');\n\n/**\n * Init app for client.\n */\nvar app = pomelo.createApp();\na"
},
{
"path": "template/game-server/app.js.udp",
"chars": 440,
"preview": "var pomelo = require('pomelo');\n\n/**\n * Init app for client.\n */\nvar app = pomelo.createApp();\napp.set('name', '$');\n\n//"
},
{
"path": "template/game-server/app.js.wss",
"chars": 675,
"preview": "var fs = require('fs');\nvar pomelo = require('pomelo');\n\n/**\n * Init app for client.\n */\nvar app = pomelo.createApp();\na"
},
{
"path": "template/game-server/config/adminServer.json",
"chars": 101,
"preview": "[{\n \"type\": \"connector\",\n \"token\": \"agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn\"\n}\n]"
},
{
"path": "template/game-server/config/adminUser.json",
"chars": 291,
"preview": "[\n {\n \"id\": \"user-1\",\n \"username\": \"admin\",\n \"password\": \"admin\",\n \"level\": 1\n },\n \n {\n \"id\": \"user-2"
},
{
"path": "template/game-server/config/clientProtos.json",
"chars": 2,
"preview": "{}"
},
{
"path": "template/game-server/config/dictionary.json",
"chars": 2,
"preview": "{}"
},
{
"path": "template/game-server/config/log4js.json",
"chars": 2269,
"preview": "{\n \"appenders\": [\n {\n \"type\": \"console\"\n },\n {\n \"type\": \"file\",\n \"filename\": \"${opts:base}/logs"
},
{
"path": "template/game-server/config/master.json",
"chars": 176,
"preview": "{\n \"development\": {\n \"id\": \"master-server-1\", \"host\": \"127.0.0.1\", \"port\": 3005\n },\n \"production\": {\n \"id\": \"ma"
},
{
"path": "template/game-server/config/serverProtos.json",
"chars": 2,
"preview": "{}"
},
{
"path": "template/game-server/config/servers.json",
"chars": 364,
"preview": "{\n \"development\":{\n \"connector\": [\n {\"id\": \"connector-server-1\", \"host\": \"127.0.0.1\", \"port\": 3150, \"clientHost\":"
},
{
"path": "template/game-server/package.json",
"chars": 113,
"preview": "{\n \"name\":\"$\",\n \"version\":\"0.0.1\",\n \"private\":false,\n \"dependencies\":{\n \"pomelo\":\"#\"\n }\n}\n\n"
},
{
"path": "template/npm-install.bat",
"chars": 166,
"preview": "::npm-install.bat\n@echo off\n::install web server dependencies && game server dependencies\ncd web-server && npm install -"
},
{
"path": "template/npm-install.sh",
"chars": 196,
"preview": "cd ./game-server && npm install -d\necho '============ game-server npm installed ============'\ncd ..\ncd ./web-server &&"
},
{
"path": "template/shared/server.crt",
"chars": 854,
"preview": "-----BEGIN CERTIFICATE-----\nMIICSzCCAbQCCQCQVN8rD6MylDANBgkqhkiG9w0BAQUFADBqMQswCQYDVQQGEwJD\nTjERMA8GA1UECAwIemhlamlhbmc"
},
{
"path": "template/shared/server.key",
"chars": 886,
"preview": "-----BEGIN RSA PRIVATE KEY-----\nMIICWwIBAAKBgQDMPe8oscKpTlQFZRrpbWmSO1UE+H65nq50l5+ptOVPMK3wgEj+\nYRyGWhBjugj9teVmLXY9ImW"
},
{
"path": "template/web-server/app.js",
"chars": 818,
"preview": "var express = require('express');\nvar app = express.createServer();\n\napp.configure(function(){\n app.use(express.methodO"
},
{
"path": "template/web-server/app.js.https",
"chars": 1037,
"preview": "var https = require('https');\nvar express = require('express');\n\nvar fs = require('fs');\n\nvar options = {\n key: fs.read"
},
{
"path": "template/web-server/bin/component.bat",
"chars": 62,
"preview": "cd public/js/lib && component install -f && component build -v"
},
{
"path": "template/web-server/bin/component.sh",
"chars": 62,
"preview": "cd public/js/lib && component install -f && component build -v"
},
{
"path": "template/web-server/package.json",
"chars": 107,
"preview": "{\n \"name\": \"$\",\n \"version\": \"0.0.1\",\n \"private\": false,\n \"dependencies\": {\n \"express\": \"3.4.8\"\n }\n}"
},
{
"path": "template/web-server/public/css/base.css",
"chars": 1046,
"preview": ".g-doc {\n width: 1000px;\n margin: 0 auto;\n text-align: left;\n line-height: 18px;\n font-size: 12px;\n color: #555;\n"
},
{
"path": "template/web-server/public/index.html",
"chars": 1679,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <title>\n Pomelo\n </title>\n <meta http-equiv="
},
{
"path": "template/web-server/public/index.html.sio",
"chars": 1643,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <title>\n Pomelo\n </title>\n <meta http-equiv="
},
{
"path": "template/web-server/public/js/lib/build/build.js",
"chars": 40557,
"preview": "\n\n/**\n * hasOwnProperty.\n */\n\nvar has = Object.prototype.hasOwnProperty;\n\n/* Refer to https://github.com/componentjs/req"
},
{
"path": "template/web-server/public/js/lib/build/build.js.wss",
"chars": 40243,
"preview": "\n\n/**\n * hasOwnProperty.\n */\n\nvar has = Object.prototype.hasOwnProperty;\n\n/**\n * Require the given path.\n *\n * @param {S"
},
{
"path": "template/web-server/public/js/lib/component.json",
"chars": 109,
"preview": "{\n \"name\": \"pomelo-client\",\n \"description\": \"pomelo-client\",\n \"local\": [ \"boot\" ],\n \"paths\": [ \"local\"]\n}"
},
{
"path": "template/web-server/public/js/lib/local/boot/component.json",
"chars": 283,
"preview": "{\n \"name\": \"boot\",\n \"description\": \"Main app boot component\",\n \"dependencies\": {\n \"component/emitter\":\"master\",\n "
},
{
"path": "template/web-server/public/js/lib/local/boot/index.js",
"chars": 305,
"preview": " var Emitter = require('emitter');\n window.EventEmitter = Emitter;\n\n var protocol = require('pomelo-protocol');\n win"
},
{
"path": "template/web-server/public/js/lib/pomeloclient.js",
"chars": 11637,
"preview": "(function() {\n var isArray = Array.isArray;\n\n var root = this;\n\n function EventEmitter() {\n }\n\n\n if (typeof module "
},
{
"path": "template/web-server/public/js/lib/pomeloclient.js.wss",
"chars": 11648,
"preview": "(function() {\n var isArray = Array.isArray;\n\n var root = this;\n\n function EventEmitter() {\n }\n\n\n if (typeof module "
},
{
"path": "template/web-server/public/js/lib/socket.io.js",
"chars": 72202,
"preview": "!function(t,e){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=e():\"function\"==typeof define&&define.am"
},
{
"path": "test/application.js",
"chars": 16454,
"preview": "var app = require('../lib/application');\nvar pomelo = require('../');\nvar should = require('should');\n\nvar WAIT_TIME = 1"
},
{
"path": "test/config/log4js.json",
"chars": 1712,
"preview": "{\n \"appenders\": [\n {\n \"type\": \"file\",\n \"filename\": \"./test/logs/node-log-${opts:serverId}.log\",\n \"fil"
},
{
"path": "test/config/master.json",
"chars": 180,
"preview": "{\n \"development\": {\n \"id\": \"master-server-1\", \"host\": \"127.0.0.1\", \"port\": 3005\n },\n\n \"production\": {\n \"id\": \"m"
},
{
"path": "test/config/servers.json",
"chars": 50,
"preview": "{\n \"development\": {\n },\n \"production\": {\n }\n}\n"
},
{
"path": "test/filters/handler/serial.js",
"chars": 1202,
"preview": "var should = require('should');\nvar serialFilter = require('../../../lib/filters/handler/serial');\nvar FilterService = r"
},
{
"path": "test/filters/handler/time.js",
"chars": 1221,
"preview": "var should = require('should');\nvar serialFilter = require('../../../lib/filters/handler/time');\nvar FilterService = req"
},
{
"path": "test/filters/handler/timeout.js",
"chars": 1211,
"preview": "var should = require('should');\nvar timeoutFilter = require('../../../lib/filters/handler/timeout');\nvar FilterService ="
},
{
"path": "test/filters/handler/toobusy.js",
"chars": 1260,
"preview": "var should = require('should');\nvar toobusyFilter = require('../../../lib/filters/handler/toobusy');\nvar FilterService ="
},
{
"path": "test/filters/rpc/rpcLog.js",
"chars": 537,
"preview": "var should = require('should');\nvar rpcLogFilter = require('../../../lib/filters/rpc/rpcLog');\n\nvar mockData = {\n serve"
},
{
"path": "test/filters/rpc/toobusy.js",
"chars": 769,
"preview": "var should = require('should');\nvar toobusyFilter = require('../../../lib/filters/rpc/toobusy');\n\nvar mockData = {\n ser"
},
{
"path": "test/logs/tmp",
"chars": 0,
"preview": ""
},
{
"path": "test/manager/mockChannelManager.js",
"chars": 1958,
"preview": "var DEFAULT_PREFIX = 'POMELO:CHANNEL';\nvar utils = require('../../lib/util/utils');\n\nvar MockManager = function(app, opt"
},
{
"path": "test/manager/taskManager.js",
"chars": 1725,
"preview": "var should = require('should');\nvar taskManager = require('../../lib/common/manager/taskManager');\n\n// set timeout for t"
},
{
"path": "test/mock-base/.gitignore",
"chars": 0,
"preview": ""
},
{
"path": "test/mock-base/app/.gitignore",
"chars": 0,
"preview": ""
},
{
"path": "test/mock-base/app/servers/.file-start-with-dot",
"chars": 0,
"preview": ""
},
{
"path": "test/mock-base/app/servers/.folder-start-with-dot/.gitignore",
"chars": 0,
"preview": ""
},
{
"path": "test/mock-base/app/servers/.gitignore",
"chars": 0,
"preview": ""
},
{
"path": "test/mock-base/app/servers/area/.gitignore",
"chars": 0,
"preview": ""
},
{
"path": "test/mock-base/app/servers/connector/.gitignore",
"chars": 0,
"preview": ""
},
{
"path": "test/mock-base/app/servers/connector/handler/.gitignore",
"chars": 0,
"preview": ""
},
{
"path": "test/mock-base/app/servers/connector/remote/.gitignore",
"chars": 0,
"preview": ""
},
{
"path": "test/mock-base/app/servers/other-file",
"chars": 0,
"preview": ""
},
{
"path": "test/mock-plugin/components/mockPlugin.js",
"chars": 97,
"preview": "module.exports = function(app, opts) {\n var service = {name: 'mockPlugin'};\n return service;\n};"
},
{
"path": "test/mock-plugin/events/mockEvent.js",
"chars": 127,
"preview": "var Event = function(app) {\n\tthis.app = app;\n};\n\nmodule.exports = Event;\n\nEvent.prototype.bind_session = function(sessio"
},
{
"path": "test/modules/console.js",
"chars": 5875,
"preview": "var should = require('should');\nvar pomelo = require('../../');\nvar consoleModule = require('../../lib/modules/console')"
},
{
"path": "test/pomelo.js",
"chars": 449,
"preview": "var pomelo = require('../');\nvar should = require('should');\nvar mockBase = process.cwd() + '/test';\n\ndescribe('pomelo',"
},
{
"path": "test/remote/channelRemote.js",
"chars": 5642,
"preview": "var should = require('should');\nvar pomelo = require('../../');\nvar remote = require('../../lib/common/remote/frontend/c"
},
{
"path": "test/service/channel.js",
"chars": 4851,
"preview": "var should = require('should');\nvar pomelo = require('../../');\nvar ChannelService = require('../../lib/common/service/c"
},
{
"path": "test/service/channelService.js",
"chars": 8086,
"preview": "var should = require('should');\nvar pomelo = require('../../');\nvar ChannelService = require('../../lib/common/service/c"
},
{
"path": "test/service/connectionService.js",
"chars": 4027,
"preview": "var should = require('should');\nvar ConnectionService = require('../../lib/common/service/connectionService');\n\nvar mock"
},
{
"path": "test/service/filterService.js",
"chars": 4577,
"preview": "var should = require('should');\nvar FilterService = require('../../lib/common/service/filterService');\n\nvar WAIT_TIME = "
},
{
"path": "test/service/handlerService.js",
"chars": 1778,
"preview": "var should = require('should');\nvar HandlerService = require('../../lib/common/service/handlerService');\n\nvar mockApp = "
},
{
"path": "test/service/sessionService.js",
"chars": 13468,
"preview": "var should = require('should');\nvar pomelo = require('../../');\nvar SessionService = require('../../lib/common/service/s"
},
{
"path": "test/util/countDownLatch.js",
"chars": 1919,
"preview": "var CountDownLatch = require('../../lib/util/countDownLatch');\nvar should = require('should');\n\nvar cbCreator = (functio"
},
{
"path": "test/util/pathUtil.js",
"chars": 3821,
"preview": "var pathUtil = require('../../lib/util/pathUtil');\nvar utils = require('../../lib/util/utils');\nvar should = require('sh"
},
{
"path": "test/util/utils.js",
"chars": 4672,
"preview": "var utils = require('../../lib/util/utils');\nvar should = require('should');\n\ndescribe('utils test', function() {\n desc"
}
]
About this extraction
This page contains the full source code of the NetEase/pomelo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 156 files (565.2 KB), approximately 157.8k tokens, and a symbol index with 155 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.