├── .gitignore ├── CHANGELOG ├── LICENSE ├── README.md ├── VERSION ├── Vagrantfile ├── bin ├── getopt_long.c ├── shell_func.lua ├── shell_func.sh └── start_worker.lua ├── check_server ├── conf ├── config.lua ├── nginx.conf ├── redis.conf ├── server.conf └── supervisord.conf ├── dists ├── beanstalkd-1.10.1.tar.gz ├── beanstalkd-1.10.tar.gz ├── lua-process-1.6.0.tar.gz ├── luabson-20160519.tar.gz ├── luapbc-20160531.tar.gz ├── luasocket-3.0-rc1.tar.gz ├── openresty-1.11.2.1.tar.gz ├── openresty-1.13.6.2.tar.gz ├── redis-3.2.5.tar.gz ├── redis-4.0.8.tar.gz ├── supervisor-3.3.1.tar.gz ├── supervisor-3.3.4.tar.gz └── virtualenv-15.0.3.tar.gz ├── make.sh ├── restart_server ├── sites └── sample │ ├── tests │ ├── actions │ │ ├── BeanstalkdAction.lua │ │ ├── ComponentsAction.lua │ │ ├── JobsAction.lua │ │ ├── RedisAction.lua │ │ ├── SessionAction.lua │ │ └── helper.lua │ ├── conf │ │ ├── app_config.lua │ │ └── app_entry.conf │ ├── jobs │ │ └── JobsAction.lua │ └── shells │ │ ├── run_tests │ │ ├── run_tests_func.lua │ │ └── show_nginx_error_log │ └── welcome │ ├── WebSocketInstance.lua │ ├── actions │ ├── ChatAction.lua │ └── UserAction.lua │ ├── conf │ └── app_entry.conf │ ├── jobs │ └── JobsAction.lua │ ├── packages │ └── online │ │ └── online.lua │ └── public_html │ ├── android-chrome-192x192.png │ ├── apple-touch-icon-180x180.png │ ├── css │ ├── custom.css │ ├── notify.almost-flat.min.css │ ├── uikit.almost-flat.min.css │ └── uikit.min.css │ ├── demo.html │ ├── favicon.ico │ ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 │ ├── index.html │ ├── js │ ├── demo.js │ ├── jquery-2.1.3.min.js │ ├── notify.min.js │ └── uikit.min.js │ └── manifest.json ├── src ├── framework │ ├── class.lua │ ├── ctype.lua │ ├── debug.lua │ ├── init.lua │ ├── io.lua │ ├── math.lua │ ├── os.lua │ ├── string.lua │ └── table.lua └── packages │ ├── beanstalkd │ └── beanstalkd.lua │ ├── event │ └── event.lua │ ├── gbc │ ├── ActionBase.lua │ ├── Broadcast.lua │ ├── CommandLineBootstrap.lua │ ├── CommandLineInstanceBase.lua │ ├── Constants.lua │ ├── Factory.lua │ ├── HttpInstanceBase.lua │ ├── InstanceBase.lua │ ├── NginxBootstrap.lua │ ├── WebSocketInstanceBase.lua │ ├── WorkerBootstrap.lua │ ├── WorkerInstanceBase.lua │ └── gbc.lua │ ├── jobs │ └── jobs.lua │ ├── json │ └── json.lua │ ├── luamd5 │ └── luamd5.lua │ ├── redis │ ├── NginxRedisLoop.lua │ └── redis.lua │ ├── session │ └── session.lua │ └── tests │ ├── Check.lua │ ├── TestCase.lua │ └── tests.lua ├── start_server ├── stop_server └── vagrant-support └── bootstrap.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | 3 | bin/getopt_long 4 | bin/python_env/ 5 | bin/beanstalkd/ 6 | bin/openresty/ 7 | bin/redis/ 8 | 9 | tmp/ 10 | logs/ 11 | db/ 12 | 13 | ubuntu-xenial-16.04-cloudimg-console.log 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 YuLei Liao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to GameBox Cloud Core 2 | 3 | GameBox Cloud Core 为开发者提供一个稳定可靠,可伸缩的服务端架构,让开发者可以使用 Lua 脚本语言快速完成服务端的功能开发。 4 | 5 | 主要特征: 6 | 7 | - 稳定可靠、经过验证的高性能游戏服务端架构 8 | 9 | 基于 OpenResty_ 和 LuaJIT_ 架构,得到了包括 CloudFlare 等大型机构的应用,无论是稳定性还是性能都得到了验证。 10 | 11 | GameBox Cloud Core 在 OpenResty 之上封装了一个 Lua Server Framework,为开发者创建游戏服务端功能提供了一个容易学习、容易扩展的基础架构。 12 | 13 | - [OpenResty](http://openresty.org) 14 | - [LuaJIT](http://luajit.org) 15 | 16 | 17 | - 使用 Lua 脚本语言开发服务端功能 18 | 19 | 也许您认为在服务端使用 Lua 脚本显得有点不务正业,但 NodeJS 的流行却证明了合适的基础架构可以让一种语言突破原本的应用场景。更何况相比 NodeJS,OpenResty 提供的同步非阻塞编程模型,可以避免写出大量的嵌套 callback,不管是从开发效率还是维护成本上来说都更胜 NodeJS。 20 | 21 | 用 Lua 脚本语言开发服务端功能还有一个巨大的好处,那就是可以和使用 Cocos2d-Lua(quick-cocos2d-x)的客户端共享大量代码。比如数据 Schema 定义、数据对象、游戏逻辑等等,都可以在客户端和服务端之间共享同一份代码。做过网络游戏的同学一定对如何保持客户端和服务端代码在数据接口上的一致头疼过。现在使用 GameBox Cloud Core,这些问题统统消失不见。 22 | 23 | 24 | - 支持短连接和长连接,满足从异步网络到实时网络的各种需求 25 | 26 | GameBox Cloud Core 支持 HTTP 和 WebSocket_ 两种连接方式,分别对应短连接和长连接,满足了异步和实时网络游戏的需求。 27 | 28 | > WebSocket 是一种通讯协议。在连接时通过 HTTP 协议进行。在客户端和服务端连接成功后,则变成标准的 TCP Socket 通讯。 29 | > 30 | > 而相比自己实现 TCP Socket,WebSocket 已经内部处理了数据包的拼合、拆分等问题,极大简化了服务端底层的复杂度。而在传输性能、带宽消耗上,WebSocket 相比传统 TCP Socket 没有任何区别。 31 | 32 | - [WebSocket RFC 文档](https://tools.ietf.org/html/rfc6455) 33 | - [WebSocket](http://zh.wikipedia.org/wiki/WebSocket) 34 | 35 | ## 下载 GameBox Cloud Core 36 | 37 | - 下载页面: [https://github.com/dualface/gbc-core/releases](https://github.com/dualface/gbc-core/releases) 38 | 39 | 40 | ## Get Started 41 | 42 | - [快速开始](https://github.com/dualface/gbc-docs/blob/master/src/guide/get-started.md) 43 | - [更多文档](https://github.com/dualface/gbc-docs/) 44 | - [源代码仓库](https://github.com/dualface/gbc-core) 45 | - [项目管理](https://www.pivotaltracker.com/n/projects/1474648) 46 | - [Bug 报告](https://github.com/dualface/gbc-core/issues) 47 | - 技术支持: QQ群 <315146510> 48 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.8.0 2 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure(2) do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://atlas.hashicorp.com/search. 15 | config.vm.box = "ubuntu/xenial64" 16 | 17 | # Disable automatic box update checking. If you disable this, then 18 | # boxes will only be checked for updates when the user runs 19 | # `vagrant box outdated`. This is not recommended. 20 | # config.vm.box_check_update = false 21 | 22 | # Create a forwarded port mapping which allows access to a specific port 23 | # within the machine from a port on the host machine. In the example below, 24 | # accessing "localhost:8080" will access port 80 on the guest machine. 25 | config.vm.network "forwarded_port", guest: 8088, host: 8088 26 | 27 | # Create a private network, which allows host-only access to the machine 28 | # using a specific IP. 29 | # config.vm.network "private_network", ip: "192.168.33.10" 30 | 31 | # Create a public network, which generally matched to bridged network. 32 | # Bridged networks make the machine appear as another physical device on 33 | # your network. 34 | # config.vm.network "public_network" 35 | 36 | # Share an additional folder to the guest VM. The first argument is 37 | # the path on the host to the actual folder. The second argument is 38 | # the path on the guest to mount the folder. And the optional third 39 | # argument is a set of non-required options. 40 | # config.vm.synced_folder "../data", "/vagrant_data" 41 | 42 | # Provider-specific configuration so you can fine-tune various 43 | # backing providers for Vagrant. These expose provider-specific options. 44 | # Example for VirtualBox: 45 | # 46 | # config.vm.provider "virtualbox" do |vb| 47 | # # Display the VirtualBox GUI when booting the machine 48 | # vb.gui = true 49 | # 50 | # # Customize the amount of memory on the VM: 51 | # vb.memory = "1024" 52 | # end 53 | # 54 | # View the documentation for the provider you are using for more 55 | # information on available options. 56 | 57 | # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies 58 | # such as FTP and Heroku are also available. See the documentation at 59 | # https://docs.vagrantup.com/v2/push/atlas.html for more information. 60 | # config.push.define "atlas" do |push| 61 | # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" 62 | # end 63 | 64 | # Enable provisioning with a shell script. Additional provisioners such as 65 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the 66 | # documentation for more information about their specific syntax and use. 67 | config.vm.provision :shell, run: "always", path: "vagrant-support/bootstrap.sh" 68 | 69 | # config.vm.provision "shell", inline: <<-SHELL 70 | # sudo apt-get update 71 | # sudo apt-get install -y apache2 72 | # SHELL 73 | end 74 | -------------------------------------------------------------------------------- /bin/getopt_long.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #define K_PREFIX 10000 31 | 32 | static int k_debug = 0; 33 | static int k_reload = 0; 34 | 35 | static struct option long_options[] = { 36 | /* These options set a flag. */ 37 | {"all", no_argument, 0, 'a'}, 38 | {"nginx", no_argument, 0, 'n'}, 39 | {"redis", no_argument, 0, 'r'}, 40 | {"beanstalkd", no_argument, 0, 'b'}, 41 | {"help", no_argument, 0, 'h'}, 42 | {"version", no_argument, 0, 'v'}, 43 | {"debug", no_argument, &k_debug, 1}, 44 | {"reload", no_argument, &k_reload, 1}, 45 | {"prefix", required_argument, 0, K_PREFIX}, 46 | {0, 0, 0, 0} 47 | }; 48 | 49 | char* save_option(char **opts, char c, int index, unsigned* len, unsigned* max_len) { 50 | char opt[20]; 51 | 52 | if (index > 0) { 53 | strcpy(opt, " --"); 54 | strcat(opt, long_options[index].name); 55 | } 56 | else { 57 | char tmp[4] = {' ', '-', c, '\0'}; 58 | strcpy(opt, tmp); 59 | } 60 | 61 | if (*len + strlen(opt) + 1 > *max_len) { 62 | *opts = (char*)realloc(*opts, *max_len+strlen(opt)*2+1); 63 | if (!*opts) { 64 | return NULL; 65 | } 66 | } 67 | 68 | *opts = strcat(*opts, opt); 69 | *len = *len + strlen(opt) + 1; 70 | *max_len = *max_len + strlen(opt)*2 + 1; 71 | 72 | return *opts; 73 | } 74 | 75 | int main (int argc, char **argv) { 76 | int c; 77 | char *install_path = NULL; 78 | char *options = NULL; 79 | unsigned int cur_opt_len = 0; 80 | unsigned int max_opt_len = 0; 81 | 82 | while(1) { 83 | 84 | int option_index = -1; 85 | 86 | c = getopt_long (argc, argv, "abrnh", long_options, &option_index); 87 | if (c == -1) 88 | break; 89 | 90 | switch(c) { 91 | /* store path from prefix */ 92 | case K_PREFIX: 93 | install_path = optarg; 94 | break; 95 | 96 | /* handle "--debug" and "--reload" */ 97 | case 0: 98 | break; 99 | 100 | case 'a': 101 | case 'b': 102 | case 'r': 103 | case 'n': 104 | case 'v': 105 | case 'h': 106 | if (!save_option(&options, c, option_index, &cur_opt_len, &max_opt_len)) { 107 | free(options); 108 | exit(-1); 109 | } 110 | break; 111 | 112 | case '?': 113 | free(options); 114 | exit(-1); 115 | 116 | default: 117 | abort(); 118 | } 119 | } 120 | 121 | /* output args as command "getopt". */ 122 | if (options) { 123 | printf("%s ", options); 124 | } 125 | 126 | if (install_path) { 127 | printf("--prefix %s ", install_path); 128 | } 129 | 130 | if (k_debug) { 131 | printf("--debug "); 132 | } 133 | 134 | if (k_reload) { 135 | printf("--reload "); 136 | } 137 | 138 | printf("--"); 139 | 140 | free(options); 141 | 142 | return 0; 143 | } 144 | -------------------------------------------------------------------------------- /bin/shell_func.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | if [ "$ROOT_DIR" == "" ]; then 4 | echo "Not set ROOT_DIR, exit." 5 | exit 1 6 | fi 7 | 8 | echo -e "\033[31mROOT_DIR\033[0m=$ROOT_DIR" 9 | echo "" 10 | 11 | cd $ROOT_DIR 12 | 13 | LUA_BIN=$ROOT_DIR/bin/openresty/luajit/bin/lua 14 | TMP_DIR=$ROOT_DIR/tmp 15 | CONF_DIR=$ROOT_DIR/conf 16 | CONF_PATH=$CONF_DIR/config.lua 17 | VAR_SUPERVISORD_CONF_PATH=$TMP_DIR/supervisord.conf 18 | 19 | function getOsType() 20 | { 21 | if [ `uname -s` == "Darwin" ]; then 22 | echo "MACOS" 23 | else 24 | echo "LINUX" 25 | fi 26 | } 27 | 28 | OS_TYPE=$(getOsType) 29 | if [ $OS_TYPE == "MACOS" ]; then 30 | SED_BIN='sed -i --' 31 | else 32 | SED_BIN='sed -i' 33 | fi 34 | 35 | function updateConfigs() 36 | { 37 | $LUA_BIN -e "ROOT_DIR='$ROOT_DIR'; DEBUG=$DEBUG; dofile('$ROOT_DIR/bin/shell_func.lua'); updateConfigs()" 38 | } 39 | 40 | function startSupervisord() 41 | { 42 | echo "[CMD] supervisord -c $VAR_SUPERVISORD_CONF_PATH" 43 | echo "" 44 | cd $ROOT_DIR/bin/python_env/gbc 45 | source bin/activate 46 | $ROOT_DIR/bin/python_env/gbc/bin/supervisord -c $VAR_SUPERVISORD_CONF_PATH 47 | cd $ROOT_DIR 48 | echo "Start supervisord DONE" 49 | echo "" 50 | } 51 | 52 | function stopSupervisord() 53 | { 54 | echo "[CMD] supervisorctl -c $VAR_SUPERVISORD_CONF_PATH shutdown" 55 | echo "" 56 | cd $ROOT_DIR/bin/python_env/gbc 57 | source bin/activate 58 | $ROOT_DIR/bin/python_env/gbc/bin/supervisorctl -c $VAR_SUPERVISORD_CONF_PATH shutdown 59 | cd $ROOT_DIR 60 | echo "" 61 | } 62 | 63 | function checkStatus() 64 | { 65 | cd $ROOT_DIR/bin/python_env/gbc 66 | source bin/activate 67 | $ROOT_DIR/bin/python_env/gbc/bin/supervisorctl -c $VAR_SUPERVISORD_CONF_PATH status 68 | cd $ROOT_DIR 69 | echo "" 70 | } 71 | -------------------------------------------------------------------------------- /bin/start_worker.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local args = {...} 26 | 27 | local help = function() 28 | print [[ 29 | 30 | $ lua start_worker.lua 31 | 32 | ]] 33 | end 34 | 35 | if #args < 2 then 36 | return help() 37 | end 38 | 39 | ROOT_DIR = args[1] 40 | APP_ROOT_PATH = args[2] 41 | 42 | package.path = ROOT_DIR .. '/src/?.lua;' .. package.path 43 | 44 | require("framework.init") 45 | local appKeys = dofile(ROOT_DIR .. "/tmp/app_keys.lua") 46 | local globalConfig = dofile(ROOT_DIR .. "/tmp/config.lua") 47 | 48 | cc.DEBUG = globalConfig.DEBUG 49 | 50 | local gbc = cc.import("#gbc") 51 | local bootstrap = gbc.WorkerBootstrap:new(appKeys, globalConfig) 52 | 53 | os.exit(bootstrap:runapp(APP_ROOT_PATH)) 54 | -------------------------------------------------------------------------------- /check_server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ROOT_DIR=$(cd "$(dirname $0)" && pwd) 4 | source $ROOT_DIR/bin/shell_func.sh 5 | 6 | checkStatus 7 | -------------------------------------------------------------------------------- /conf/config.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local config = { 26 | DEBUG = cc.DEBUG_VERBOSE, 27 | 28 | -- all sites 29 | sites = { 30 | sample = { 31 | server = { 32 | nginx = { 33 | port = 8088 34 | } 35 | }, 36 | apps = { 37 | welcome = "_GBC_CORE_ROOT_/sites/sample/welcome", 38 | tests = "_GBC_CORE_ROOT_/sites/sample/tests", 39 | } 40 | } 41 | }, 42 | 43 | 44 | -- default app config 45 | app = { 46 | messageFormat = "json", 47 | defaultAcceptedRequestType = "http", 48 | sessionExpiredTime = 60 * 10, -- 10m 49 | 50 | httpEnabled = true, 51 | httpMessageFormat = "json", 52 | 53 | websocketEnabled = true, 54 | websocketMessageFormat = "json", 55 | websocketsTimeout = 60 * 1000, -- 60s 56 | websocketsMaxPayloadLen = 16 * 1024, -- 16KB 57 | 58 | jobMessageFormat = "json", 59 | numOfJobWorkers = 2, 60 | 61 | jobWorkerRequests = 1000, 62 | }, 63 | 64 | -- server config 65 | server = { 66 | nginx = { 67 | numOfWorkers = 4, 68 | --port = 8088, 69 | }, 70 | 71 | -- internal memory database 72 | redis = { 73 | socket = "unix:_GBC_CORE_ROOT_/tmp/redis.sock", 74 | -- host = "127.0.0.1", 75 | -- port = 6379, 76 | timeout = 10 * 1000, -- 10 seconds 77 | }, 78 | 79 | -- background job server 80 | beanstalkd = { 81 | -- host = "127.0.0.1", 82 | host = "unix:_GBC_CORE_ROOT_/tmp/beanstalkd.sock", 83 | port = 11300, 84 | }, 85 | } 86 | } 87 | 88 | return config 89 | -------------------------------------------------------------------------------- /conf/nginx.conf: -------------------------------------------------------------------------------- 1 | 2 | daemon off; # control by supervisord 3 | worker_processes 4; 4 | error_log _GBC_CORE_ROOT_/logs/nginx-error.log; 5 | pid _GBC_CORE_ROOT_/tmp/nginx.pid; 6 | 7 | events { 8 | worker_connections 256; 9 | } 10 | 11 | http { 12 | include '_GBC_CORE_ROOT_/bin/openresty/nginx/conf/mime.types'; 13 | 14 | # logs 15 | log_format compression '$remote_addr - $remote_user [$time_local] ' 16 | '"$request" $status $bytes_sent ' 17 | '"$http_referer" "$http_user_agent" "$gzip_ratio"'; 18 | access_log _GBC_CORE_ROOT_/logs/nginx-access.log compression; 19 | 20 | # tmp 21 | client_body_temp_path _GBC_CORE_ROOT_/tmp/client_body_temp; 22 | fastcgi_temp_path _GBC_CORE_ROOT_/tmp/fastcgi_temp; 23 | proxy_temp_path _GBC_CORE_ROOT_/tmp/proxy_temp; 24 | scgi_temp_path _GBC_CORE_ROOT_/tmp/scgi_temp; 25 | uwsgi_temp_path _GBC_CORE_ROOT_/tmp/uwsgi_temp; 26 | 27 | # security 28 | client_max_body_size 32k; 29 | server_tokens off; 30 | client_body_buffer_size 16K; 31 | client_header_buffer_size 1k; 32 | large_client_header_buffers 2 1k; 33 | autoindex off; 34 | ssi off; 35 | 36 | # lua 37 | lua_check_client_abort on; 38 | lua_socket_log_errors off; 39 | lua_package_path '_GBC_CORE_ROOT_/src/?.lua;;'; 40 | lua_shared_dict _GBC_ 1024k; 41 | lua_code_cache off; 42 | 43 | init_by_lua ' 44 | 45 | require("framework.init") 46 | 47 | local appKeys = dofile("_GBC_CORE_ROOT_/tmp/app_keys.lua") 48 | local globalConfig = dofile("_GBC_CORE_ROOT_/tmp/config.lua") 49 | 50 | cc.DEBUG = globalConfig.DEBUG 51 | 52 | local gbc = cc.import("#gbc") 53 | cc.exports.nginxBootstrap = gbc.NginxBootstrap:new(appKeys, globalConfig) 54 | 55 | '; 56 | 57 | #sites 58 | #_INCLUDE_SITES_ENTRY_ 59 | 60 | } 61 | -------------------------------------------------------------------------------- /conf/server.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8088 so_keepalive=on; 3 | 4 | location = /nginx_status { 5 | stub_status; 6 | access_log off; 7 | allow 127.0.0.1; 8 | deny all; 9 | } 10 | 11 | # apps 12 | # DO NOT MODIFY BELOW LINES 13 | #_INCLUDE_APPS_ENTRY_ 14 | } -------------------------------------------------------------------------------- /conf/supervisord.conf: -------------------------------------------------------------------------------- 1 | ; Sample supervisor config file. 2 | ; 3 | ; For more information on the config file, please see: 4 | ; http://supervisord.org/configuration.html 5 | ; 6 | ; Notes: 7 | ; - Shell expansion ("~" or "$HOME") is not supported. Environment 8 | ; variables can be expanded using this syntax: "%(ENV_HOME)s". 9 | ; - Comments must have a leading space: "a=b ;comment" not "a=b;comment". 10 | 11 | [unix_http_server] 12 | file=_GBC_CORE_ROOT_/tmp/supervisor.sock ; (the path to the socket file) 13 | ;chmod=0700 ; socket file mode (default 0700) 14 | ;chown=nobody:nogroup ; socket file uid:gid owner 15 | ;username=user ; (default is no username (open server)) 16 | ;password=123 ; (default is no password (open server)) 17 | 18 | ;[inet_http_server] ; inet (TCP) server disabled by default 19 | ;port=127.0.0.1:9001 ; (ip_address:port specifier, *:port for all iface) 20 | ;username=user ; (default is no username (open server)) 21 | ;password=123 ; (default is no password (open server)) 22 | 23 | [supervisord] 24 | logfile=_GBC_CORE_ROOT_/logs/supervisord.log ; (main log file;default $CWD/supervisord.log) 25 | logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) 26 | logfile_backups=10 ; (num of main logfile rotation backups;default 10) 27 | loglevel=warn ; (log level;default info; others: debug,warn,trace) 28 | pidfile=_GBC_CORE_ROOT_/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid) 29 | nodaemon=false ; (start in foreground if true;default false) 30 | minfds=1024 ; (min. avail startup file descriptors;default 1024) 31 | minprocs=200 ; (min. avail process descriptors;default 200) 32 | ;umask=022 ; (process file creation umask;default 022) 33 | ;user=chrism ; (default is current user, required if root) 34 | ;identifier=supervisor ; (supervisord identifier, default is 'supervisor') 35 | ;directory=/tmp ; (default is not to cd during start) 36 | ;nocleanup=true ; (don't clean up tempfiles at start;default false) 37 | ;childlogdir=/tmp ; ('AUTO' child log dir, default $TEMP) 38 | ;environment=KEY="value" ; (key value pairs to add to environment) 39 | ;strip_ansi=false ; (strip ansi escape codes in logs; def. false) 40 | 41 | ; the below section must remain in the config file for RPC 42 | ; (supervisorctl/web interface) to work, additional interfaces may be 43 | ; added by defining them in separate rpcinterface: sections 44 | [rpcinterface:supervisor] 45 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 46 | 47 | [supervisorctl] 48 | serverurl=unix://_GBC_CORE_ROOT_/tmp/supervisor.sock ; use a unix:// URL for a unix socket 49 | ;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket 50 | ;username=chris ; should be same as http_username if set 51 | ;password=123 ; should be same as http_password if set 52 | ;prompt=mysupervisor ; cmd line prompt (default "supervisor") 53 | ;history_file=~/.sc_history ; use readline history if available 54 | 55 | ; The below sample program section shows all possible program subsection values, 56 | ; create one or more 'real' program: sections to be able to control them under 57 | ; supervisor. 58 | 59 | 60 | ;[program:theprogramname] 61 | ;command=/bin/cat ; the program (relative uses PATH, can take args) 62 | ;process_name=%(program_name)s ; process_name expr (default %(program_name)s) 63 | ;numprocs=1 ; number of processes copies to start (def 1) 64 | ;directory=/tmp ; directory to cwd to before exec (def no cwd) 65 | ;umask=022 ; umask for process (default None) 66 | ;priority=999 ; the relative start priority (default 999) 67 | ;autostart=true ; start at supervisord start (default: true) 68 | ;autorestart=unexpected ; whether/when to restart (default: unexpected) 69 | ;startsecs=1 ; number of secs prog must stay running (def. 1) 70 | ;startretries=3 ; max # of serial start failures (default 3) 71 | ;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2) 72 | ;stopsignal=QUIT ; signal used to kill process (default TERM) 73 | ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) 74 | ;stopasgroup=false ; send stop signal to the UNIX process group (default false) 75 | ;killasgroup=false ; SIGKILL the UNIX process group (def false) 76 | ;user=chrism ; setuid to this UNIX account to run the program 77 | ;redirect_stderr=true ; redirect proc stderr to stdout (default false) 78 | ;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO 79 | ;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 80 | ;stdout_logfile_backups=10 ; # of stdout logfile backups (default 10) 81 | ;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 82 | ;stdout_events_enabled=false ; emit events on stdout writes (default false) 83 | ;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO 84 | ;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 85 | ;stderr_logfile_backups=10 ; # of stderr logfile backups (default 10) 86 | ;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 87 | ;stderr_events_enabled=false ; emit events on stderr writes (default false) 88 | ;environment=A="1",B="2" ; process environment additions (def no adds) 89 | ;serverurl=AUTO ; override serverurl computation (childutils) 90 | 91 | ; The below sample eventlistener section shows all possible 92 | ; eventlistener subsection values, create one or more 'real' 93 | ; eventlistener: sections to be able to handle event notifications 94 | ; sent by supervisor. 95 | 96 | ;[eventlistener:theeventlistenername] 97 | ;command=/bin/eventlistener ; the program (relative uses PATH, can take args) 98 | ;process_name=%(program_name)s ; process_name expr (default %(program_name)s) 99 | ;numprocs=1 ; number of processes copies to start (def 1) 100 | ;events=EVENT ; event notif. types to subscribe to (req'd) 101 | ;buffer_size=10 ; event buffer queue size (default 10) 102 | ;directory=/tmp ; directory to cwd to before exec (def no cwd) 103 | ;umask=022 ; umask for process (default None) 104 | ;priority=-1 ; the relative start priority (default -1) 105 | ;autostart=true ; start at supervisord start (default: true) 106 | ;autorestart=unexpected ; whether/when to restart (default: unexpected) 107 | ;startsecs=1 ; number of secs prog must stay running (def. 1) 108 | ;startretries=3 ; max # of serial start failures (default 3) 109 | ;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2) 110 | ;stopsignal=QUIT ; signal used to kill process (default TERM) 111 | ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) 112 | ;stopasgroup=false ; send stop signal to the UNIX process group (default false) 113 | ;killasgroup=false ; SIGKILL the UNIX process group (def false) 114 | ;user=chrism ; setuid to this UNIX account to run the program 115 | ;redirect_stderr=true ; redirect proc stderr to stdout (default false) 116 | ;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO 117 | ;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 118 | ;stdout_logfile_backups=10 ; # of stdout logfile backups (default 10) 119 | ;stdout_events_enabled=false ; emit events on stdout writes (default false) 120 | ;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO 121 | ;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 122 | ;stderr_logfile_backups ; # of stderr logfile backups (default 10) 123 | ;stderr_events_enabled=false ; emit events on stderr writes (default false) 124 | ;environment=A="1",B="2" ; process environment additions 125 | ;serverurl=AUTO ; override serverurl computation (childutils) 126 | 127 | ; The below sample group section shows all possible group values, 128 | ; create one or more 'real' group: sections to create "heterogeneous" 129 | ; process groups. 130 | 131 | ;[group:thegroupname] 132 | ;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions 133 | ;priority=999 ; the relative start priority (default 999) 134 | 135 | ; The [include] section can just contain the "files" setting. This 136 | ; setting can list multiple files (separated by whitespace or 137 | ; newlines). It can also contain wildcards. The filenames are 138 | ; interpreted as relative to this file. Included files *cannot* 139 | ; include files themselves. 140 | 141 | ;[include] 142 | ;files = relative/directory/*.ini 143 | 144 | 145 | [program:beanstalkd] 146 | command=_GBC_CORE_ROOT_/bin/beanstalkd/bin/beanstalkd -l _BEANSTALKD_HOST_ -p _BEANSTALKD_PORT_ -b _GBC_CORE_ROOT_/db 147 | 148 | [program:redis] 149 | command=_GBC_CORE_ROOT_/bin/redis/bin/redis-server _GBC_CORE_ROOT_/tmp/redis.conf 150 | 151 | [program:nginx] 152 | command=_GBC_CORE_ROOT_/bin/openresty/nginx/sbin/nginx -c _GBC_CORE_ROOT_/tmp/nginx.conf 153 | 154 | ;all workers 155 | ;_WORKERS_ 156 | -------------------------------------------------------------------------------- /dists/beanstalkd-1.10.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/dists/beanstalkd-1.10.1.tar.gz -------------------------------------------------------------------------------- /dists/beanstalkd-1.10.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/dists/beanstalkd-1.10.tar.gz -------------------------------------------------------------------------------- /dists/lua-process-1.6.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/dists/lua-process-1.6.0.tar.gz -------------------------------------------------------------------------------- /dists/luabson-20160519.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/dists/luabson-20160519.tar.gz -------------------------------------------------------------------------------- /dists/luapbc-20160531.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/dists/luapbc-20160531.tar.gz -------------------------------------------------------------------------------- /dists/luasocket-3.0-rc1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/dists/luasocket-3.0-rc1.tar.gz -------------------------------------------------------------------------------- /dists/openresty-1.11.2.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/dists/openresty-1.11.2.1.tar.gz -------------------------------------------------------------------------------- /dists/openresty-1.13.6.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/dists/openresty-1.13.6.2.tar.gz -------------------------------------------------------------------------------- /dists/redis-3.2.5.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/dists/redis-3.2.5.tar.gz -------------------------------------------------------------------------------- /dists/redis-4.0.8.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/dists/redis-4.0.8.tar.gz -------------------------------------------------------------------------------- /dists/supervisor-3.3.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/dists/supervisor-3.3.1.tar.gz -------------------------------------------------------------------------------- /dists/supervisor-3.3.4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/dists/supervisor-3.3.4.tar.gz -------------------------------------------------------------------------------- /dists/virtualenv-15.0.3.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/dists/virtualenv-15.0.3.tar.gz -------------------------------------------------------------------------------- /restart_server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ROOT_DIR=$(cd "$(dirname $0)" && pwd) 4 | source $ROOT_DIR/bin/shell_func.sh 5 | 6 | echo -e "\033[33mRestart GameBox Cloud Core $VERSION\033[0m" 7 | echo "" 8 | 9 | stopSupervisord 10 | sleep 3s 11 | 12 | startSupervisord 13 | sleep 3s 14 | 15 | checkStatus 16 | 17 | -------------------------------------------------------------------------------- /sites/sample/tests/actions/BeanstalkdAction.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local helper = cc.import(".helper") 26 | local tests = cc.import("#tests") 27 | local check = tests.Check 28 | local Beanstalkd = cc.import("#beanstalkd") 29 | 30 | local BeanstalkdTestCase = cc.class("BeanstalkdTestCase", tests.TestCase) 31 | 32 | local _newbean, _flush 33 | 34 | local _DEFAULT_TUBE = "default" 35 | local _TEST_TUBE = "_test_" 36 | local _JOB_PRIORITY = 0 37 | local _JOB_DELAY = 1 38 | local _JOB_TTR = 2 39 | local _JOB_WORD = "hello, number is " .. math.random(1, 100) 40 | 41 | function BeanstalkdTestCase:setup() 42 | local config = self:getInstanceConfig() 43 | self._beanstalkd = _newbean(config.server.beanstalkd) 44 | _flush(self._beanstalkd) 45 | end 46 | 47 | function BeanstalkdTestCase:teardown() 48 | _flush(self._beanstalkd) 49 | end 50 | 51 | function BeanstalkdTestCase:basicsAction() 52 | local bean = self._beanstalkd 53 | local errors = bean.ERRORS 54 | 55 | -- add job, reserve it 56 | local id = bean:put(_JOB_WORD, _JOB_PRIORITY, _JOB_DELAY, _JOB_TTR) 57 | check.isInt(id) 58 | local job = bean:reserve() 59 | check.equals(job, {id = id, data = _JOB_WORD}) 60 | 61 | -- sleep, reserve again with deadline_soon 62 | helper.sleep(_JOB_TTR - 1) 63 | 64 | check.equals({bean:reserve(0)}, {nil, errors.DEADLINE_SOON}) 65 | check.equals({bean:touch(job.id)}, {true}) 66 | 67 | -- delete it 68 | check.equals({bean:delete(job.id)}, {true}) 69 | -- delete non exists job 70 | check.equals({bean:delete(job.id)}, {nil, errors.NOT_FOUND}) 71 | 72 | -- reserve with timeout 73 | check.equals({bean:reserve(1)}, {nil, errors.TIMED_OUT}) 74 | 75 | return true 76 | end 77 | 78 | function BeanstalkdTestCase:releaseAction() 79 | local bean = self._beanstalkd 80 | local errors = bean.ERRORS 81 | 82 | -- add job, reserve it, release it 83 | local id = bean:put(_JOB_WORD, 0, _JOB_DELAY, _JOB_TTR) 84 | check.isInt(id) 85 | local job = bean:reserve() 86 | check.equals(job, {id = id, data = _JOB_WORD}) 87 | check.equals({bean:release(job.id, _JOB_PRIORITY, _JOB_DELAY)}, {true}) 88 | 89 | -- release non exists job 90 | check.equals({bean:release(job.id, _JOB_PRIORITY, _JOB_DELAY)}, {nil, errors.NOT_FOUND}) 91 | -- delete it 92 | check.equals({bean:delete(job.id)}, {true}) 93 | 94 | return true 95 | end 96 | 97 | function BeanstalkdTestCase:changestateAction() 98 | local bean = self._beanstalkd 99 | local errors = bean.ERRORS 100 | 101 | -- add job, peek it 102 | local id = bean:put(_JOB_WORD, 0, _JOB_DELAY, _JOB_TTR) 103 | check.isInt(id) 104 | 105 | local expected = {id = id, data = _JOB_WORD} 106 | local job = bean:peek(id) 107 | check.equals(job, expected) 108 | 109 | -- peek delayed job 110 | local job = bean:peek("delayed") 111 | check.equals(job, expected) 112 | 113 | -- reserve it, bury reserved job, peek buried job 114 | check.equals({bean:reserve()}, {expected}) 115 | check.equals({bean:bury(job.id, _JOB_PRIORITY)}, {true}) 116 | local job = bean:peek("buried") 117 | check.equals(job, expected) 118 | 119 | -- kick it 120 | check.equals({bean:kick(100)}, {1}) 121 | -- wait it ready 122 | helper.sleep(_JOB_DELAY) 123 | local job = bean:peek("ready") 124 | check.equals(job, expected) 125 | 126 | return true 127 | end 128 | 129 | function BeanstalkdTestCase:statsAction() 130 | local bean = self._beanstalkd 131 | local errors = bean.ERRORS 132 | 133 | -- add job 134 | local id = bean:put(_JOB_WORD, 0, _JOB_DELAY, _JOB_TTR) 135 | check.isInt(id) 136 | 137 | -- get job info 138 | local res = bean:statsJob(id) 139 | check.equals(res["id"], tostring(id)) 140 | check.equals(res["state"], "delayed") 141 | 142 | -- get tube info 143 | local res = bean:statsTube(_TEST_TUBE) 144 | check.equals(res["name"], _TEST_TUBE) 145 | check.equals(res["current-jobs-delayed"], "1") 146 | 147 | -- get system info 148 | local res = bean:stats() 149 | check.equals(res["current-jobs-ready"], "0") 150 | check.equals(res["current-jobs-delayed"], "1") 151 | 152 | -- list tubes 153 | local tubes = bean:listTubes() 154 | check.isTable(tubes) 155 | check.contains(tubes, _TEST_TUBE) 156 | 157 | -- list used tube 158 | local tube = bean:listTubeUsed() 159 | check.equals(tube, _TEST_TUBE) 160 | 161 | -- list watched tubes 162 | local tubes = bean:listTubesWatched() 163 | check.isTable(tubes) 164 | check.contains(tubes, _TEST_TUBE) 165 | 166 | return true 167 | end 168 | 169 | function BeanstalkdTestCase:tubeAction() 170 | local bean = self._beanstalkd 171 | local errors = bean.ERRORS 172 | 173 | -- use, watch, ignore 174 | check.equals({bean:use(_TEST_TUBE)}, {_TEST_TUBE}) 175 | 176 | local count = bean:watch(_TEST_TUBE) 177 | check.isInt(count) 178 | check.greaterThan(count, 0) 179 | 180 | local count2 = bean:ignore(_DEFAULT_TUBE) 181 | check.isInt(count2) 182 | 183 | return true 184 | end 185 | 186 | -- private 187 | 188 | _newbean = function(config) 189 | local beanstalkd = Beanstalkd:new() 190 | beanstalkd:connect(config.host, config.port) 191 | beanstalkd:use(_TEST_TUBE) 192 | beanstalkd:watch(_TEST_TUBE) 193 | beanstalkd:ignore(_DEFAULT_TUBE) 194 | return beanstalkd 195 | end 196 | 197 | _flush = function(bean) 198 | bean:kick(10000) 199 | 200 | while true do 201 | local data = bean:reserve(0) 202 | if not data then break end 203 | bean:delete(data.id) 204 | end 205 | end 206 | 207 | return BeanstalkdTestCase 208 | -------------------------------------------------------------------------------- /sites/sample/tests/actions/ComponentsAction.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | 26 | local Event = cc.import("#event") 27 | 28 | local helper = cc.import(".helper") 29 | local tests = cc.import("#tests") 30 | local check = tests.Check 31 | 32 | local ComponentsTestCase = cc.class("ComponentsTestCase", tests.TestCase) 33 | 34 | function ComponentsTestCase:setup() 35 | end 36 | 37 | function ComponentsTestCase:teardown() 38 | end 39 | 40 | function ComponentsTestCase:bindingAction() 41 | local Sheep = cc.class("Sheep") 42 | local sheep = Sheep:new() 43 | 44 | -- add component 45 | local eventComponent = cc.addComponent(sheep, Event) 46 | check.isTable(eventComponent) 47 | check.isFunction(eventComponent.bind) 48 | 49 | -- get component by class 50 | local eventComponent_ = cc.getComponent(sheep, Event) 51 | check.equals(tostring(eventComponent), tostring(eventComponent_)) 52 | 53 | -- get component by class name 54 | local eventComponent_ = cc.getComponent(sheep, Event.__cname) 55 | check.equals(tostring(eventComponent), tostring(eventComponent_)) 56 | 57 | -- bind listeners 58 | local results = {} 59 | 60 | local tag1 = eventComponent:bind("RUN", function(event) 61 | results[#results + 1] = {event.name, event.step} 62 | end) 63 | 64 | local tag2 = eventComponent:bind("RUN", function(event) 65 | results[#results + 1] = {event.name, event.step} 66 | end) -- add second listener for event "RUN" 67 | 68 | local tag3 = eventComponent:bind("WALK", function(event) 69 | results[#results + 1] = {event.name, event.step} 70 | end) 71 | 72 | -- trigger events 73 | local step1 = math.random(10000, 20000) 74 | local step2 = math.random(30000, 40000) 75 | local step3 = math.random(50000, 60000) 76 | 77 | eventComponent:trigger({name = "RUN", step = step1}) 78 | eventComponent:trigger({name = "WALK", step = step2}) 79 | 80 | -- unbind listener 81 | eventComponent:unbind(tag2) 82 | eventComponent:trigger({name = "RUN", step = step3}) 83 | 84 | -- check 85 | check.equals(results, { 86 | {"RUN", step1}, 87 | {"RUN", step1}, 88 | {"WALK", step2}, 89 | {"RUN", step3}, 90 | }) 91 | 92 | -- remove component by class 93 | cc.removeComponent(sheep, Event) 94 | check.isNil(cc.getComponent(sheep, Event)) 95 | 96 | -- remove component by class name 97 | cc.addComponent(sheep, Event) 98 | cc.removeComponent(sheep, Event.__cname) 99 | check.isNil(cc.getComponent(sheep, Event)) 100 | 101 | -- remove component by component object 102 | local eventComponent = cc.addComponent(sheep, Event) 103 | cc.removeComponent(sheep, eventComponent) 104 | check.isNil(cc.getComponent(sheep, Event)) 105 | 106 | return true 107 | end 108 | 109 | return ComponentsTestCase 110 | -------------------------------------------------------------------------------- /sites/sample/tests/actions/JobsAction.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local helper = cc.import(".helper") 26 | local tests = cc.import("#tests") 27 | local check = tests.Check 28 | 29 | local JobsTestCase = cc.class("JobsTestCase", tests.TestCase) 30 | 31 | local _TEST_REDIS_KEY = 'jobs.test.number' 32 | 33 | local _flush 34 | 35 | function JobsTestCase:setup() 36 | local instance = self:getInstance() 37 | self._jobs = instance:getJobs() 38 | self._redis = instance:getRedis() 39 | _flush(self._jobs, self._redis) 40 | end 41 | 42 | function JobsTestCase:teardown() 43 | _flush(self._jobs, self._redis) 44 | end 45 | 46 | function JobsTestCase:addAction() 47 | local number = math.random(1, 10000) 48 | local data = {number = number, key = _TEST_REDIS_KEY} 49 | 50 | local delay = 1 51 | local jobid = self._jobs:add({ 52 | action = 'jobs.trigging', 53 | data = data, 54 | delay = delay, 55 | }) 56 | check.isInt(jobid) 57 | helper.sleep(delay + 1) -- waiting for job done 58 | 59 | -- query job result from redis 60 | local res = tonumber(self._redis:get(_TEST_REDIS_KEY)) 61 | check.equals(res, number * 2) 62 | 63 | return true 64 | end 65 | 66 | function JobsTestCase:atAction() 67 | local number = math.random(20000, 30000) 68 | local data = {number = number, key = _TEST_REDIS_KEY} 69 | 70 | local time = os.time() + 1 71 | local jobid = self._jobs:at({ 72 | action = 'jobs.trigging', 73 | data = data, 74 | time = time, 75 | }) 76 | check.isInt(jobid) 77 | helper.sleep(2) -- waiting for job done 78 | 79 | -- query job result from redis 80 | local now = os.time() 81 | local res = tonumber(self._redis:get(_TEST_REDIS_KEY)) 82 | check.equals(res, number * 2) 83 | check.isTrue(math.abs(now - time) <= 1) 84 | 85 | return true 86 | end 87 | 88 | function JobsTestCase:getAction() 89 | local number = math.random(40000, 50000) 90 | local data = {number = number, key = _TEST_REDIS_KEY} 91 | 92 | local delay = 2 93 | local jobid = self._jobs:add({ 94 | action = 'jobs.trigging', 95 | data = data, 96 | delay = delay, 97 | }) 98 | check.isInt(jobid) 99 | 100 | -- query job 101 | local job = self._jobs:get(jobid) 102 | check.isTable(job) 103 | check.equals(job.id, jobid) 104 | check.equals(job.data, data) 105 | 106 | -- delete job 107 | local res = self._jobs:delete(jobid) 108 | check.isTrue(res) 109 | 110 | return true 111 | end 112 | 113 | -- remove all jobs 114 | function JobsTestCase:_flush() 115 | local states = {"ready", "delayed", "buried"} 116 | for _, state in ipairs(states) do 117 | while true do 118 | local job = self._jobs:queryNext(state) 119 | if not job then break end 120 | self._jobs:remove(job.id) 121 | end 122 | end 123 | end 124 | 125 | -- private 126 | 127 | _flush = function(jobs, redis) 128 | redis:del(_TEST_REDIS_KEY) 129 | end 130 | 131 | return JobsTestCase 132 | 133 | -------------------------------------------------------------------------------- /sites/sample/tests/actions/RedisAction.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local Redis = cc.import("#redis") 26 | local helper = cc.import(".helper") 27 | local tests = cc.import("#tests") 28 | local check = tests.Check 29 | 30 | local RedisTestCase = cc.class("RedisTestCase", tests.TestCase) 31 | 32 | local _TEST_DB_INDEX = 15 33 | local _CLEANUP_CODE = [[ 34 | local keys = redis.call('KEYS', '*') 35 | for _, key in ipairs(keys) do 36 | redis.call('DEL', key) 37 | end 38 | ]] 39 | 40 | local _newredis, _runcmds 41 | 42 | function RedisTestCase:setup() 43 | local config = self:getInstanceConfig() 44 | self._redis = _newredis(config.server.redis) 45 | end 46 | 47 | function RedisTestCase:teardown() 48 | local redis = self._redis 49 | redis:select(_TEST_DB_INDEX) 50 | redis:eval(_CLEANUP_CODE, 0) 51 | redis:close() 52 | self._redis = nil 53 | end 54 | 55 | function RedisTestCase:typesAction() 56 | local redis = self._redis 57 | 58 | -- check return types: 59 | -- Simple Strings 60 | local word = "hello world" 61 | check.equals(redis:echo(word), word) 62 | check.equals(redis:ping(), "PONG") 63 | 64 | -- Errors 65 | local ok, err = redis:auth("INVALID_PASSWORD") 66 | check.contains(string.lower(err), "no password is set") 67 | 68 | -- Integers 69 | check.equals(redis:del("NON_EXISTS_KEY"), 0) 70 | redis:del("TEST_KEY") 71 | check.equals(redis:set("TEST_KEY", word), "OK") 72 | check.equals(redis:del("TEST_KEY"), 1) 73 | 74 | -- Bulk Strings 75 | check.equals(redis:set("TEST_KEY", word), "OK") 76 | check.equals(redis:get("TEST_KEY"), word) 77 | 78 | -- Arrays 79 | check.equals(redis:set("TEST_KEY_1", word), "OK") 80 | check.equals(redis:set("TEST_KEY_2", word), "OK") 81 | local keys = redis:keys("TEST_KEY_*") 82 | check.isTable(keys) 83 | table.sort(keys) 84 | check.equals(keys, {"TEST_KEY_1", "TEST_KEY_2"}) 85 | 86 | check.equals(redis:mget("TEST_KEY", "NON_EXISTS_KEY"), {word, redis.null}) 87 | 88 | return true 89 | end 90 | 91 | function RedisTestCase:pipelineAction() 92 | local redis = self._redis 93 | 94 | local word = "hello world" 95 | local commands = { 96 | {"echo", word}, 97 | {"ping"}, 98 | {"del", "NON_EXISTS_KEY"}, 99 | {"set", "TEST_KEY", word}, 100 | {"del", "TEST_KEY"}, 101 | {"set", "TEST_KEY_1", word}, 102 | {"set", "TEST_KEY_2", word}, 103 | {"keys", "TEST_KEY_*"}, 104 | {"mget", "TEST_KEY", "TEST_KEY_1", "NON_EXISTS_KEY"}, 105 | {"eval", "return {redis.call('get', 'NON_EXISTS_KEY'), KEYS[1], ARGV[1]}", 1, "KEY_1", "ARG_1"}, 106 | {"auth", "NO_PASSWORD"}, -- get error in pipeline 107 | } 108 | 109 | local expected = { 110 | word, 111 | "PONG", 112 | 0, 113 | "OK", 114 | 1, 115 | "OK", 116 | "OK", 117 | {"TEST_KEY_1", "TEST_KEY_2"}, 118 | {redis.null, word, redis.null}, 119 | { 120 | redis.null, "KEY_1", "ARG_1", 121 | }, 122 | } 123 | 124 | local function _checkResult(vals) 125 | table.sort(vals[8]) 126 | local last = table.remove(vals) 127 | 128 | check.equals(vals, expected) 129 | check.isTable(last) 130 | check.isFalse(last[1]) 131 | check.contains(last[2], "no password is set") 132 | end 133 | 134 | -- test commit pipeline 135 | redis:initPipeline() 136 | _runcmds(redis, commands) 137 | local vals = redis:commitPipeline() 138 | check.isTable(vals) 139 | _checkResult(vals) 140 | 141 | -- test cancel pipeline 142 | redis:initPipeline() 143 | _runcmds(redis, commands) 144 | redis:cancelPipeline() -- cleanup pipeline 145 | 146 | redis:initPipeline() -- start again 147 | _runcmds(redis, commands) 148 | local vals = redis:commitPipeline() 149 | check.isTable(vals) 150 | _checkResult(vals) 151 | 152 | return true 153 | end 154 | 155 | function RedisTestCase:pubsubAction() 156 | local redis = self._redis 157 | 158 | local channel1 = "MSG_CHANNEL_1" 159 | local channel2 = "MSG_CHANNEL_2" 160 | local channel3 = "MSG_CHANNEL_3" 161 | local channel4 = "MSG_CHANNEL_4" 162 | 163 | -- use current instance to subscribe to channels 164 | check.equals(redis:subscribe(channel1, channel2), { 165 | "subscribe", channel1, 1 166 | }) 167 | check.equals(redis:readReply(), { 168 | "subscribe", channel2, 2 169 | }) 170 | 171 | -- use an other instance publish message to channels 172 | local config = self:getInstanceConfig() 173 | local redis2 = _newredis(config.server.redis) 174 | redis2:publish(channel1, "hello") 175 | 176 | check.equals(redis:readReply(), { 177 | "message", channel1, "hello" 178 | }) 179 | 180 | redis:subscribe(channel3, channel4) 181 | check.equals(redis:readReply(), { 182 | "subscribe", channel4, 4 183 | }) 184 | 185 | redis2:publish(channel2, "world") 186 | check.equals(redis:readReply(), { 187 | "message", channel2, "world" 188 | }) 189 | 190 | for i = 1, 10 do 191 | redis2:publish(channel2, "world_" .. i) 192 | end 193 | 194 | for i = 1, 10 do 195 | check.equals(redis:readReply(), { 196 | "message", channel2, "world_" .. i 197 | }) 198 | end 199 | 200 | redis2:close() 201 | 202 | -- unsubscribe from channels 203 | check.equals(redis:unsubscribe(channel1), { 204 | "unsubscribe", channel1, 3 205 | }) 206 | check.equals(redis:unsubscribe(channel2), { 207 | "unsubscribe", channel2, 2 208 | }) 209 | check.equals(redis:unsubscribe(channel3), { 210 | "unsubscribe", channel3, 1 211 | }) 212 | check.equals(redis:unsubscribe(channel4), { 213 | "unsubscribe", channel4, 0 214 | }) 215 | check.equals(redis:echo("hello"), "hello") 216 | 217 | return true 218 | end 219 | 220 | function RedisTestCase:loopAction() 221 | local redis = self._redis 222 | 223 | local channel1 = "MSG_CHANNEL_1" 224 | local channel2 = "MSG_CHANNEL_2" 225 | local channel3 = "MSG_CHANNEL_3" 226 | local channel4 = "MSG_CHANNEL_4" 227 | 228 | -- Loop will use an other redis instance 229 | local msgs = {} 230 | local loop, err = redis:makeSubscribeLoop() 231 | if not loop then 232 | check.equals(self:getInstance():getRequestType(), "cli") 233 | return true 234 | end 235 | 236 | local cmdchannel = "_TEST_CMD_CHANNEL" 237 | loop:start(function(channel, msg) 238 | msgs[#msgs + 1] = {channel, msg} 239 | end, cmdchannel) 240 | 241 | loop:subscribe(channel1, channel2) 242 | 243 | -- publish message to channels 244 | redis:publish(channel1, "hello1") 245 | redis:publish(channel2, "hello2") 246 | 247 | loop:unsubscribe(channel2) 248 | redis:publish(channel2, "hello2") -- skip 249 | 250 | loop:psubscribe("MSG_CHANNEL_*") 251 | redis:publish(channel2, "hello2") 252 | redis:publish(channel3, "hello3") 253 | redis:publish(channel4, "hello4") 254 | 255 | loop:stop() -- read all messages and stop loop 256 | 257 | check.equals(msgs, { 258 | {channel1, "hello1"}, 259 | {channel2, "hello2"}, 260 | -- 261 | {channel2, "hello2"}, 262 | {channel3, "hello3"}, 263 | {channel4, "hello4"}, 264 | }) 265 | 266 | return true 267 | end 268 | 269 | -- private 270 | 271 | _newredis = function(config) 272 | local redis, err = helper.newredis(config) 273 | check.isNil(err, err) 274 | redis:select(_TEST_DB_INDEX) 275 | redis:eval(_CLEANUP_CODE, 0) 276 | return redis 277 | end 278 | 279 | _runcmds = function(redis, commands) 280 | local res = {} 281 | for i, args in ipairs(commands) do 282 | res[i] = redis:doCommand(unpack(args)) 283 | end 284 | return res 285 | end 286 | 287 | return RedisTestCase 288 | -------------------------------------------------------------------------------- /sites/sample/tests/actions/SessionAction.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | 26 | local Session = cc.import("#session") 27 | 28 | local helper = cc.import(".helper") 29 | local tests = cc.import("#tests") 30 | local check = tests.Check 31 | 32 | local SessionTestCase = cc.class("SessionTestCase", tests.TestCase) 33 | 34 | local _KEYS = table.readonly({ 35 | NON_EXISTS_KEY = "NON_EXISTS_KEY", 36 | STRING_KEY = "STRING_KEY", 37 | NUMBER_KEY = "NUMBER_KEY", 38 | }) 39 | 40 | local _newredis 41 | 42 | function SessionTestCase:setup() 43 | local config = self:getInstanceConfig() 44 | self._redis = _newredis(config.server.redis) 45 | end 46 | 47 | function SessionTestCase:teardown() 48 | end 49 | 50 | function SessionTestCase:createAction() 51 | local redis = self._redis 52 | local session = Session:new(redis) 53 | session:start() 54 | 55 | local sid = session:getSid() 56 | check.isString(sid) 57 | 58 | check.isNil(session:get(_KEYS.NON_EXISTS_KEY)) 59 | 60 | local number = math.random(1, 100000) 61 | session:set(_KEYS.NUMBER_KEY, number) 62 | check.equals(session:get(_KEYS.NUMBER_KEY), number) 63 | 64 | local word = "hello " .. tostring(number) 65 | session:set(_KEYS.STRING_KEY, word) 66 | check.equals(session:get(_KEYS.STRING_KEY), word) 67 | 68 | -- save session 69 | session:save() 70 | 71 | -- create an other session use same sid 72 | local session2 = Session:new(redis) 73 | session2:start(sid) 74 | 75 | check.equals(session:get(_KEYS.NUMBER_KEY), number) 76 | check.equals(session:get(_KEYS.STRING_KEY), word) 77 | 78 | -- destroy first session 79 | session:destroy() 80 | 81 | -- second session should is destroyed also 82 | check.isFalse(session2:isAlive()) 83 | 84 | return true 85 | end 86 | 87 | function SessionTestCase:expiredAction() 88 | local redis = self._redis 89 | local session = Session:new(redis, {expired = 1}) 90 | session:start() 91 | 92 | local number = math.random(1, 100000) 93 | session:set(_KEYS.NUMBER_KEY, number) 94 | check.isFalse(session:isAlive()) 95 | check.equals(session:get(_KEYS.NUMBER_KEY), number) 96 | 97 | session:save() 98 | check.isTrue(session:isAlive()) 99 | 100 | local expired = 2 101 | session:setKeepAlive(expired) 102 | check.equals(session:getExpired(), expired) 103 | helper.sleep(1) 104 | check.isTrue(session:isAlive()) 105 | check.equals(session:get(_KEYS.NUMBER_KEY), number) 106 | 107 | helper.sleep(2) 108 | check.isFalse(session:isAlive()) 109 | 110 | return true 111 | end 112 | 113 | -- private 114 | 115 | _newredis = function(config) 116 | local redis, err = helper.newredis(config) 117 | check.isNil(err, err) 118 | redis:select(_TEST_DB_INDEX) 119 | redis:eval(_CLEANUP_CODE, 0) 120 | return redis 121 | end 122 | 123 | return SessionTestCase 124 | -------------------------------------------------------------------------------- /sites/sample/tests/actions/helper.lua: -------------------------------------------------------------------------------- 1 | 2 | local Redis = cc.import("#redis") 3 | local Beanstalkd = cc.import("#beanstalkd") 4 | 5 | local _M = {} 6 | 7 | _M.newredis = function(config) 8 | local redis = Redis:new() 9 | local ok, err 10 | if config.socket then 11 | ok, err = redis:connect(config.socket) 12 | else 13 | ok, err = redis:connect(config.host, config.port) 14 | end 15 | if not ok then 16 | return nil, err 17 | end 18 | return redis 19 | end 20 | 21 | _M.newbeanstalkd = function(config) 22 | local bean = Beanstalkd:new() 23 | local ok, err = bean:connect(config.host, config.port) 24 | if not ok then 25 | return nil, err 26 | end 27 | return bean 28 | end 29 | 30 | _M.sleep = function(n) 31 | os.execute("sleep " .. tonumber(n)) 32 | end 33 | 34 | return _M 35 | -------------------------------------------------------------------------------- /sites/sample/tests/conf/app_config.lua: -------------------------------------------------------------------------------- 1 | 2 | local config = { 3 | numOfJobWorkers = 1, 4 | } 5 | 6 | return config 7 | -------------------------------------------------------------------------------- /sites/sample/tests/conf/app_entry.conf: -------------------------------------------------------------------------------- 1 | 2 | location /tests/ { 3 | content_by_lua 'nginxBootstrap:runapp("_APP_ROOT_")'; 4 | } 5 | -------------------------------------------------------------------------------- /sites/sample/tests/jobs/JobsAction.lua: -------------------------------------------------------------------------------- 1 | 2 | local gbc = cc.import("#gbc") 3 | local JobsAction = cc.class("JobsAction", gbc.ActionBase) 4 | 5 | JobsAction.ACCEPTED_REQUEST_TYPE = "worker" 6 | 7 | function JobsAction:triggingAction(job) 8 | local key = job.data.key 9 | local number = job.data.number 10 | 11 | local redis = self:getInstance():getRedis() 12 | redis:set(key, number * 2) 13 | end 14 | 15 | return JobsAction 16 | -------------------------------------------------------------------------------- /sites/sample/tests/shells/run_tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CUR_DIR=$(cd "$(dirname $0)" && pwd) 4 | ROOT_DIR=$(dirname "$CUR_DIR") 5 | ROOT_DIR=$(dirname "$ROOT_DIR") 6 | ROOT_DIR=$(dirname "$ROOT_DIR") 7 | source "$ROOT_DIR/bin/shell_func.sh" 8 | 9 | if [ $? -ne 0 ]; then echo "Terminating..." >&2; exit 1; fi 10 | 11 | DEBUG=1 12 | 13 | $LUA_BIN -e "ROOT_DIR='$ROOT_DIR'; DEBUG=$DEBUG; dofile('$CUR_DIR/run_tests_func.lua'); runTests('$*')" 14 | -------------------------------------------------------------------------------- /sites/sample/tests/shells/run_tests_func.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local os_execute = os.execute 26 | local os_remove = os.remove 27 | local string_format = string.format 28 | local string_lower = string.lower 29 | local string_sub = string.sub 30 | 31 | package.path = ROOT_DIR .. "/src/?.lua;" .. ROOT_DIR .. "/apps/tests/?.lua;" .. package.path 32 | require("framework.init") 33 | cc.DEBUG = cc.DEBUG_VERBOSE 34 | 35 | local json = cc.import("#json") 36 | local Factory = cc.import("#gbc").Factory 37 | 38 | -- declare Tests class 39 | 40 | local Tests = cc.class("Tests") 41 | 42 | local _CURL_PATTERN = "curl -s --no-keepalive -o - '%s'" 43 | 44 | local _parseargs, _findtests 45 | local _testsrv, _testcli 46 | local _help 47 | 48 | function Tests:ctor(appConfig, appRootPath) 49 | self._url = string_format("http://localhost:%s/tests/?action=%%s", tostring(appConfig.server.nginx.port)) 50 | self._config = appConfig 51 | self._root = appRootPath 52 | end 53 | 54 | function Tests:run(args) 55 | local opts, err = _parseargs(args) 56 | if not opts then 57 | _help() 58 | return 59 | end 60 | 61 | if #opts.tests == 0 then 62 | local casesDir = self._root .. "/actions" 63 | opts.tests = _findtests(casesDir) 64 | end 65 | 66 | local pass 67 | for _, casename in ipairs(opts.tests) do 68 | if string_sub(casename, -6) ~= "Action" then 69 | -- casename passed from command line arguments 70 | casename = string.ucfirst(string.lower(casename)) .. "Action" 71 | end 72 | 73 | local ok, testCaseClass = pcall(require, "actions." .. casename) 74 | if not ok then 75 | -- testCaseClass is error message 76 | cc.printf("ERR: not found test '%s'\n\n%s", casename, testCaseClass) 77 | break 78 | end 79 | if type(testCaseClass) ~= "table" then 80 | cc.printf("ERR: '%s' isn't module", casename) 81 | break 82 | end 83 | 84 | local actionPackageName = string_lower(string_sub(casename, 1, -7)) 85 | local tests = {} 86 | for methodName, _2 in pairs(testCaseClass) do 87 | if string_sub(methodName, -6) == "Action" then 88 | tests[#tests + 1] = actionPackageName .. "." .. string_lower(string_sub(methodName, 1, -7)) 89 | end 90 | end 91 | 92 | table.sort(tests) 93 | 94 | print(string_format("## Test Case : %s", actionPackageName)) 95 | 96 | for _3, action in ipairs(tests) do 97 | if opts.testsrv then 98 | pass = self:_runtest(_testsrv, {action}, "SERVER " .. action) 99 | if (not pass) and (not opts.continue) then 100 | break 101 | end 102 | end 103 | 104 | if opts.testcli then 105 | pass = self:_runtest(_testcli, {action}, "CLI " .. action) 106 | if (not pass) and (not opts.continue) then 107 | break 108 | end 109 | end 110 | end 111 | 112 | print("") 113 | 114 | if (not pass) and (not opts.continue) then 115 | break 116 | end 117 | 118 | end 119 | end 120 | 121 | function Tests:_runtest(testfun, arg, action) 122 | local result 123 | local err 124 | 125 | local ok, contents = xpcall(function() 126 | return testfun(self, unpack(arg)) 127 | end, function(_err) 128 | err = _err .. debug.traceback("", 4) 129 | end) 130 | 131 | if contents == true then 132 | result = {ok = true} 133 | elseif type(contents) == "table" then 134 | result = contents 135 | else 136 | result = json.decode(tostring(contents)) 137 | if type(result) ~= "table" then 138 | contents = tostring(contents) 139 | contents = string.gsub(contents, "\\n", "\n") 140 | contents = string.gsub(contents, "\\\"", '"') 141 | result = {err = err} 142 | end 143 | end 144 | 145 | if result.err then 146 | print(string_format("[%s] \27[31mfailed\27[0m: %s", action, result.err)) 147 | elseif tostring(result.ok) == "true" or tostring(result.result) == "true" then 148 | print(string_format("[%s] \27[32mok\27[0m", action)) 149 | return true 150 | else 151 | print(string_format("[%s] \27[33minvalid result\27[0m: %s", action, contents)) 152 | end 153 | end 154 | 155 | -- private 156 | 157 | _parseargs = function(args) 158 | local opts = { 159 | continue = false, 160 | testsrv = true, 161 | testcli = true, 162 | tests = {} 163 | } 164 | for _, arg in ipairs(string.split(args, " ")) do 165 | if arg == "-h" then 166 | return 167 | elseif arg == "-c" then 168 | opts.continue = true 169 | elseif arg == "-ns" then 170 | opts.testsrv = false 171 | elseif arg == "-nc" then 172 | opts.testcli = false 173 | elseif string.sub(arg, 1, 1) == "-" then 174 | print("Invalid options") 175 | return 176 | else 177 | opts.tests[#opts.tests + 1] = arg 178 | end 179 | end 180 | 181 | return opts 182 | end 183 | 184 | _findtests = function(rootdir) 185 | local cmd = string_format('ls "%s"', rootdir) 186 | local h = io.popen(cmd) 187 | local res = h:read("*a") 188 | h:close() 189 | 190 | local cases = {} 191 | for _, file in ipairs(string.split(res, "\n")) do 192 | if string.sub(file, -10) == "Action.lua" then 193 | cases[#cases + 1] = string.sub(file, 1, -5) 194 | end 195 | end 196 | 197 | table.sort(cases) 198 | 199 | return cases 200 | end 201 | 202 | _testsrv = function(self, action) 203 | local url = string_format(self._url, action) 204 | local cmd = string_format(_CURL_PATTERN, url) 205 | local h = io.popen(cmd) 206 | local res = h:read("*a") 207 | h:close() 208 | return res 209 | end 210 | 211 | _testcli = function(self, action) 212 | local config = table.copy(self._config) 213 | config.app.package = "actions" 214 | local cmd = Factory.create(config, "CommandLineInstance", arg) 215 | return cmd:runAction(action) 216 | end 217 | 218 | _help = function() 219 | print [[ 220 | 221 | $ run_tests.sh [options] [test case name ...] 222 | 223 | options: 224 | -h: show help 225 | -c: continue when test failed 226 | -ns: skip server tests 227 | -nc: skip cli tests 228 | 229 | examples: 230 | 231 | # run JobsTestCase and RedisTestCase 232 | run_tests.sh jobs redis 233 | 234 | ]] 235 | 236 | end 237 | 238 | -- bootstrap 239 | 240 | local appKeys = dofile(ROOT_DIR .. "/tmp/app_keys.lua") 241 | local globalConfig = dofile(ROOT_DIR .. "/tmp/config.lua") 242 | local appConfigs = Factory.makeAppConfigs(appKeys, globalConfig, package.path) 243 | local appRootPath = ROOT_DIR .. "/apps/tests" 244 | local appConfig = appConfigs[appRootPath] 245 | 246 | cc.exports.runTests = function(arg) 247 | local tests = Tests:new(appConfig, appRootPath) 248 | tests:run(arg) 249 | end 250 | -------------------------------------------------------------------------------- /sites/sample/tests/shells/show_nginx_error_log: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CUR_DIR=$(cd "$(dirname $0)" && pwd) 4 | ROOT_DIR=$(dirname "$CUR_DIR") 5 | ROOT_DIR=$(dirname "$ROOT_DIR") 6 | ROOT_DIR=$(dirname "$ROOT_DIR") 7 | source "$ROOT_DIR/bin/shell_func.sh" 8 | 9 | if [ $? -ne 0 ] ; then echo "Terminating..." >&2; exit 1; fi 10 | 11 | multitail -csn -cS Apache -ke '^[0-9/]+ [0-9][0-9]:' -ke ' [0-9]+#[0-9]+: ' -ke ' debug\.lua:[0-9]+: _?print[a-z]+\(\):' -ke ', client: .+$' -ke '\[lua\] debug.lua:[0-9]+: dump\(\)' "$ROOT_DIR/logs/nginx-error.log" 12 | -------------------------------------------------------------------------------- /sites/sample/welcome/WebSocketInstance.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local Online = cc.import("#online") 26 | local Session = cc.import("#session") 27 | 28 | local gbc = cc.import("#gbc") 29 | local WebSocketInstance = cc.class("WebSocketInstance", gbc.WebSocketInstanceBase) 30 | 31 | function WebSocketInstance:ctor(config) 32 | WebSocketInstance.super.ctor(self, config) 33 | self._event:bind(WebSocketInstance.EVENT.CONNECTED, cc.handler(self, self.onConnected)) 34 | self._event:bind(WebSocketInstance.EVENT.DISCONNECTED, cc.handler(self, self.onDisconnected)) 35 | end 36 | 37 | function WebSocketInstance:onConnected() 38 | local redis = self:getRedis() 39 | 40 | -- load session 41 | local sid = self:getConnectToken() -- token is session id 42 | local session = Session:new(redis) 43 | session:start(sid) 44 | 45 | -- add user to online users list 46 | local online = Online:new(self) 47 | local username = session:get("username") 48 | online:add(username, self:getConnectId()) 49 | 50 | -- send all usernames to current client 51 | local users = online:getAll() 52 | online:sendMessage(username, {name = "LIST_ALL_USERS", users = users}) 53 | -- subscribe online users event 54 | self:subscribe(online:getChannel()) 55 | 56 | self._username = username 57 | self._session = session 58 | self._online = online 59 | end 60 | 61 | function WebSocketInstance:onDisconnected(event) 62 | if event.reason ~= gbc.Constants.CLOSE_CONNECT then 63 | -- connection interrupted unexpectedly, remove user from online list 64 | cc.printwarn("[websocket:%s] connection interrupted unexpectedly", self:getConnectId()) 65 | local username = self._session:get("username") 66 | self._online:remove(username) 67 | end 68 | end 69 | 70 | function WebSocketInstance:heartbeat() 71 | -- refresh session 72 | self._session:setKeepAlive() 73 | end 74 | 75 | function WebSocketInstance:getUsername() 76 | return self._username 77 | end 78 | 79 | function WebSocketInstance:getSession() 80 | return self._session 81 | end 82 | 83 | function WebSocketInstance:getOnline() 84 | return self._online 85 | end 86 | 87 | return WebSocketInstance 88 | -------------------------------------------------------------------------------- /sites/sample/welcome/actions/ChatAction.lua: -------------------------------------------------------------------------------- 1 | 2 | local gbc = cc.import("#gbc") 3 | local ChatAction = cc.class("ChatAction", gbc.ActionBase) 4 | 5 | ChatAction.ACCEPTED_REQUEST_TYPE = "websocket" 6 | 7 | function ChatAction:sendmessageAction(arg) 8 | local recipient = arg.recipient 9 | if not recipient then 10 | cc.throw("not set argument: \"recipient\"") 11 | end 12 | 13 | local message = arg.message 14 | if not message then 15 | cc.throw("not set argument: \"message\"") 16 | end 17 | 18 | -- forward message to other client 19 | local instance = self:getInstance() 20 | instance:getOnline():sendMessage(recipient, { 21 | name = "MESSAGE", 22 | sender = instance:getUsername(), 23 | recipient = recipient, 24 | body = message, 25 | }) 26 | end 27 | 28 | function ChatAction:sendmessagetoallAction(arg) 29 | local message = arg.message 30 | if not message then 31 | cc.throw("not set argument: \"message\"") 32 | end 33 | 34 | -- forward message to all clients 35 | local instance = self:getInstance() 36 | instance:getOnline():sendMessageToAll({ 37 | name = "MESSAGE", 38 | sender = instance:getUsername(), 39 | recipient = recipient, 40 | body = message, 41 | }) 42 | end 43 | 44 | return ChatAction 45 | -------------------------------------------------------------------------------- /sites/sample/welcome/actions/UserAction.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of chargse, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local Online = cc.import("#online") 26 | local Session = cc.import("#session") 27 | 28 | local gbc = cc.import("#gbc") 29 | local UserAction = cc.class("UserAction", gbc.ActionBase) 30 | 31 | local _opensession 32 | 33 | function UserAction:signinAction(args) 34 | local username = args.username 35 | if not username then 36 | cc.throw("not set argsument: \"username\"") 37 | end 38 | 39 | -- start session 40 | local session = Session:new(self:getInstance():getRedis()) 41 | session:start() 42 | session:set("username", username) 43 | session:set("count", 0) 44 | session:save() 45 | 46 | -- return result 47 | return {sid = session:getSid(), count = 0} 48 | end 49 | 50 | function UserAction:signoutAction(args) 51 | -- remove user from online list 52 | local session = _opensession(self:getInstance(), args) 53 | local online = Online:new(self:getInstance()) 54 | online:remove(session:get("username")) 55 | -- delete session 56 | session:destroy() 57 | return {ok = "ok"} 58 | end 59 | 60 | function UserAction:countAction(args) 61 | -- update count value in session 62 | local session = _opensession(self:getInstance(), args) 63 | local count = session:get("count") 64 | count = count + 1 65 | session:set("count", count) 66 | session:save() 67 | 68 | return {count = count} 69 | end 70 | 71 | function UserAction:addjobAction(args) 72 | local sid = args.sid 73 | if not sid then 74 | cc.throw("not set argsument: \"sid\"") 75 | end 76 | 77 | local instance = self:getInstance() 78 | local redis = instance:getRedis() 79 | local session = Session:new(redis) 80 | if not session:start(sid) then 81 | cc.throw("session is expired, or invalid session id") 82 | end 83 | 84 | local delay = cc.checkint(args.delay) 85 | if delay <= 0 then 86 | delay = 1 87 | end 88 | local message = args.message 89 | if not message then 90 | cc.throw("not set argument: \"message\"") 91 | end 92 | 93 | -- send message to job 94 | local jobs = instance:getJobs() 95 | local job = { 96 | action = "/jobs/jobs.echo", 97 | delay = delay, 98 | data = { 99 | username = session:get("username"), 100 | message = message, 101 | } 102 | } 103 | local ok, err = jobs:add(job) 104 | if not ok then 105 | return {err = err} 106 | else 107 | return {ok = "ok"} 108 | end 109 | end 110 | 111 | -- private 112 | 113 | _opensession = function(instance, args) 114 | local sid = args.sid 115 | if not sid then 116 | cc.throw("not set argsument: \"sid\"") 117 | end 118 | 119 | local session = Session:new(instance:getRedis()) 120 | if not session:start(sid) then 121 | cc.throw("session is expired, or invalid session id") 122 | end 123 | 124 | return session 125 | end 126 | 127 | return UserAction 128 | -------------------------------------------------------------------------------- /sites/sample/welcome/conf/app_entry.conf: -------------------------------------------------------------------------------- 1 | 2 | location / { 3 | root '_APP_ROOT_/public_html'; 4 | index index.html; 5 | } 6 | 7 | location /welcome/ { 8 | content_by_lua 'nginxBootstrap:runapp("_APP_ROOT_")'; 9 | } 10 | -------------------------------------------------------------------------------- /sites/sample/welcome/jobs/JobsAction.lua: -------------------------------------------------------------------------------- 1 | 2 | local Online = cc.import("#online") 3 | 4 | local gbc = cc.import("#gbc") 5 | local JobsAction = cc.class("JobsAction", gbc.ActionBase) 6 | 7 | JobsAction.ACCEPTED_REQUEST_TYPE = "worker" 8 | 9 | function JobsAction:echoAction(job) 10 | local username = job.data.username 11 | local message = job.data.message 12 | 13 | local online = Online:new(self:getInstance()) 14 | online:sendMessage(username, { 15 | name = "MESSAGE", 16 | sender = username, 17 | body = string.format("'%s' do a job, message is '%s', delay is %d", username, message, job.delay), 18 | }) 19 | end 20 | 21 | return JobsAction 22 | -------------------------------------------------------------------------------- /sites/sample/welcome/packages/online/online.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local string_format = string.format 26 | 27 | local json = cc.import("#json") 28 | local gbc = cc.import("#gbc") 29 | 30 | local Online = cc.class("Online") 31 | 32 | local _ONLINE_SET = "_ONLINE_USERS" 33 | local _ONLINE_CHANNEL = "_ONLINE_CHANNEL" 34 | local _EVENT = table.readonly({ 35 | ADD_USER = "ADD_USER", 36 | REMOVE_USER = "REMOVE_USER", 37 | }) 38 | local _CONNECT_TO_USERNAME = "_CONNECT_TO_USERNAME" 39 | local _USERNAME_TO_CONNECT = "_USERNAME_TO_CONNECT" 40 | 41 | function Online:ctor(instance) 42 | self._instance = instance 43 | self._redis = instance:getRedis() 44 | self._broadcast = gbc.Broadcast:new(self._redis, instance.config.app.websocketMessageFormat) 45 | end 46 | 47 | function Online:getAll() 48 | return self._redis:smembers(_ONLINE_SET) 49 | end 50 | 51 | function Online:add(username, connectId) 52 | local redis = self._redis 53 | redis:initPipeline() 54 | -- map username <-> connect id 55 | redis:hset(_CONNECT_TO_USERNAME, connectId, username) 56 | redis:hset(_USERNAME_TO_CONNECT, username, connectId) 57 | -- add username to set 58 | redis:sadd(_ONLINE_SET, username) 59 | -- send event to all clients 60 | redis:publish(_ONLINE_CHANNEL, json.encode({name = _EVENT.ADD_USER, username = username})) 61 | return redis:commitPipeline() 62 | end 63 | 64 | function Online:remove(username) 65 | local redis = self._redis 66 | local connectId, err = redis:hget(_USERNAME_TO_CONNECT, username) 67 | if not connectId then 68 | return nil, err 69 | end 70 | if connectId == redis.null then 71 | return nil, string_format("not found username '%s'", username) 72 | end 73 | 74 | redis:initPipeline() 75 | -- remove map 76 | redis:hdel(_CONNECT_TO_USERNAME, connectId) 77 | redis:hdel(_USERNAME_TO_CONNECT, username) 78 | -- remove username from set 79 | redis:srem(_ONLINE_SET, username) 80 | redis:publish(_ONLINE_CHANNEL, json.encode({name = _EVENT.REMOVE_USER, username = username})) 81 | local res, err = redis:commitPipeline() 82 | if not res then 83 | return nil, err 84 | end 85 | 86 | return self._broadcast:sendControlMessage(connectId, gbc.Constants.CLOSE_CONNECT) 87 | end 88 | 89 | function Online:getChannel() 90 | return _ONLINE_CHANNEL 91 | end 92 | 93 | function Online:sendMessage(recipient, event) 94 | local redis = self._redis 95 | -- query connect id by recipient 96 | local connectId, err = redis:hget(_USERNAME_TO_CONNECT, recipient) 97 | if not connectId then 98 | return nil, err 99 | end 100 | 101 | if connectId == redis.null then 102 | return nil, string_format("not found recipient '%s'", recipient) 103 | end 104 | 105 | -- send message to connect id 106 | return self._broadcast:sendMessage(connectId, event) 107 | end 108 | 109 | function Online:sendMessageToAll(event) 110 | return self._broadcast:sendMessageToAll(event) 111 | end 112 | 113 | return Online 114 | -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/sites/sample/welcome/public_html/android-chrome-192x192.png -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/sites/sample/welcome/public_html/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/css/custom.css: -------------------------------------------------------------------------------- 1 | 2 | pre { 3 | white-space: pre-wrap; /* CSS 3 */ 4 | white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ 5 | white-space: -pre-wrap; /* Opera 4-6 */ 6 | white-space: -o-pre-wrap; /* Opera 7 */ 7 | word-wrap: break-word; /* Internet Explorer 5.5+ */ 8 | } 9 | 10 | article { 11 | margin-top: 3em; 12 | font-size: 110%; 13 | line-height: 150% 14 | } 15 | 16 | article li { 17 | margin-bottom: 1em; 18 | } 19 | 20 | article blockquote { 21 | font-size: 85%; 22 | } 23 | -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/css/notify.almost-flat.min.css: -------------------------------------------------------------------------------- 1 | /*! UIkit 2.24.2 | http://www.getuikit.com | (c) 2014 YOOtheme | MIT License */ 2 | .uk-notify{position:fixed;top:10px;left:10px;z-index:1040;box-sizing:border-box;width:350px}.uk-notify-bottom-right,.uk-notify-top-right{left:auto;right:10px}.uk-notify-bottom-center,.uk-notify-top-center{left:50%;margin-left:-175px}.uk-notify-bottom-center,.uk-notify-bottom-left,.uk-notify-bottom-right{top:auto;bottom:10px}@media (max-width:479px){.uk-notify{left:10px;right:10px;width:auto;margin:0}}.uk-notify-message{position:relative;margin-bottom:10px;padding:15px;background:#444;color:#fff;font-size:16px;line-height:22px;cursor:pointer;border:1px solid #444;border-radius:4px}.uk-notify-message>.uk-close{visibility:hidden;float:right}.uk-notify-message:hover>.uk-close{visibility:visible}.uk-notify-message-primary{background:#ebf7fd;color:#2d7091;border-color:rgba(45,112,145,.3)}.uk-notify-message-success{background:#f2fae3;color:#659f13;border-color:rgba(101,159,19,.3)}.uk-notify-message-warning{background:#fffceb;color:#e28327;border-color:rgba(226,131,39,.3)}.uk-notify-message-danger{background:#fff1f0;color:#d85030;border-color:rgba(216,80,48,.3)} -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | GameBox Cloud Core Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 42 | 43 |
44 |
45 | 46 |
47 |

Sign in GameBox Cloud Core

48 |
49 |
50 |
51 | 52 | 53 |
54 | 55 |
56 | 57 | 58 | 59 |
60 | 61 |
62 | 63 | 64 | 65 |
66 |
67 |
68 |
69 | 70 |
71 |

Send message to user

72 |
73 |
74 |
75 | 76 | 77 |
78 | 79 |
80 | 81 | 82 | 83 | 84 |
85 |
86 |
87 |
88 | 89 |
90 |

Send message to Delay Job

91 |
92 |
93 |
94 | 95 | 96 | 97 |     98 | 99 | 100 |     101 | 102 | 103 |
104 | 105 |
106 | 107 | 108 | 109 |
110 |
111 |
112 |
113 | 114 |
115 |

Logs

116 | 117 |

118 |                 
119 |                 
120 |             
121 | 122 |
123 | 124 |
125 | 126 |
127 |
128 | 129 |
130 |

GameBox Cloud Core Demo

131 |
132 |

133 | 134 | 135 |

136 |
137 |
138 | 139 |
140 |
141 | 152 |
153 |
154 | 155 |
156 | 157 | 163 | 164 | -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/sites/sample/welcome/public_html/favicon.ico -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/sites/sample/welcome/public_html/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/sites/sample/welcome/public_html/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/sites/sample/welcome/public_html/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/sites/sample/welcome/public_html/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dualface/gbc-core/b751e7a7e6aa6b6f2dfd3ca9270463709d59983a/sites/sample/welcome/public_html/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | GameBox Cloud Core 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 39 | 40 |
41 |
42 |
43 | 44 |
45 | 46 |

Welcome to GameBox Cloud Core

47 | 48 |

GameBox Cloud Core 为开发者提供一个稳定可靠,可伸缩的服务端架构,让开发者可以使用 Lua 脚本语言快速完成服务端的功能开发。

49 | 50 |

主要特征:

51 | 52 |
    53 |
  • 54 |

    稳定可靠、经过验证的高性能游戏服务端架构

    55 | 56 |

    基于 OpenResty_ 和 LuaJIT_ 架构,得到了包括 CloudFlare 等大型机构的应用,无论是稳定性还是性能都得到了验证。

    57 | 58 |

    GameBox Cloud Core 在 OpenResty 之上封装了一个 Lua Server Framework,为开发者创建游戏服务端功能提供了一个容易学习、容易扩展的基础架构。

    59 | 60 | 64 |
  • 65 | 66 |
  • 67 |

    使用 Lua 脚本语言开发服务端功能

    68 | 69 |

    也许您认为在服务端使用 Lua 脚本显得有点不务正业,但 NodeJS 的流行却证明了合适的基础架构可以让一种语言突破原本的应用场景。更何况相比 NodeJS,OpenResty 提供的同步非阻塞编程模型,可以避免写出大量的嵌套 callback,不管是从开发效率还是维护成本上来说都更胜 NodeJS。

    70 | 71 |

    用 Lua 脚本语言开发服务端功能还有一个巨大的好处,那就是可以和使用 Cocos2d-Lua(quick-cocos2d-x)的客户端共享大量代码。比如数据 Schema 定义、数据对象、游戏逻辑等等,都可以在客户端和服务端之间共享同一份代码。做过网络游戏的同学一定对如何保持客户端和服务端代码在数据接口上的一致头疼过。现在使用 GameBox Cloud Core,这些问题统统消失不见。

    72 |
  • 73 | 74 |
  • 75 |

    支持短连接和长连接,满足从异步网络到实时网络的各种需求

    76 | 77 |

    GameBox Cloud Core 支持 HTTP 和 WebSocket 两种连接方式,分别对应短连接和长连接,满足了异步和实时网络游戏的需求。

    78 | 79 |
    80 |

    WebSocket 是一种通讯协议。在连接时通过 HTTP 协议进行。在客户端和服务端连接成功后,则变成标准的 TCP Socket 通讯。

    81 | 82 |

    而相比自己实现 TCP Socket,WebSocket 已经内部处理了数据包的拼合、拆分等问题,极大简化了服务端底层的复杂度。而在传输性能、带宽消耗上,WebSocket 相比传统 TCP Socket 没有任何区别。

    83 |
    84 | 85 | 89 |
  • 90 |
91 | 92 |

Get Started

93 | 94 | 103 | 104 |
105 | 106 |
107 |
108 |
109 | 110 |
111 |
112 | 123 |
124 |
125 | 126 |
127 | 128 | 129 | -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/js/notify.min.js: -------------------------------------------------------------------------------- 1 | /*! UIkit 2.24.2 | http://www.getuikit.com | (c) 2014 YOOtheme | MIT License */ 2 | !function(t){var e;window.UIkit&&(e=t(UIkit)),"function"==typeof define&&define.amd&&define("uikit-notify",["uikit"],function(){return e||t(UIkit)})}(function(t){"use strict";var e={},i={},s=function(e){return"string"==t.$.type(e)&&(e={message:e}),arguments[1]&&(e=t.$.extend(e,"string"==t.$.type(arguments[1])?{status:arguments[1]}:arguments[1])),new n(e).show()},o=function(t,e){var s;if(t)for(s in i)t===i[s].group&&i[s].close(e);else for(s in i)i[s].close(e)},n=function(s){this.options=t.$.extend({},n.defaults,s),this.uuid=t.Utils.uid("notifymsg"),this.element=t.$(['
','',"
","
"].join("")).data("notifyMessage",this),this.content(this.options.message),this.options.status&&(this.element.addClass("uk-notify-message-"+this.options.status),this.currentstatus=this.options.status),this.group=this.options.group,i[this.uuid]=this,e[this.options.pos]||(e[this.options.pos]=t.$('
').appendTo("body").on("click",".uk-notify-message",function(){var e=t.$(this).data("notifyMessage");e.element.trigger("manualclose.uk.notify",[e]),e.close()}))};return t.$.extend(n.prototype,{uuid:!1,element:!1,timout:!1,currentstatus:"",group:!1,show:function(){if(!this.element.is(":visible")){var t=this;e[this.options.pos].show().prepend(this.element);var i=parseInt(this.element.css("margin-bottom"),10);return this.element.css({opacity:0,"margin-top":-1*this.element.outerHeight(),"margin-bottom":0}).animate({opacity:1,"margin-top":0,"margin-bottom":i},function(){if(t.options.timeout){var e=function(){t.close()};t.timeout=setTimeout(e,t.options.timeout),t.element.hover(function(){clearTimeout(t.timeout)},function(){t.timeout=setTimeout(e,t.options.timeout)})}}),this}},close:function(t){var s=this,o=function(){s.element.remove(),e[s.options.pos].children().length||e[s.options.pos].hide(),s.options.onClose.apply(s,[]),s.element.trigger("close.uk.notify",[s]),delete i[s.uuid]};this.timeout&&clearTimeout(this.timeout),t?o():this.element.animate({opacity:0,"margin-top":-1*this.element.outerHeight(),"margin-bottom":0},function(){o()})},content:function(t){var e=this.element.find(">div");return t?(e.html(t),this):e.html()},status:function(t){return t?(this.element.removeClass("uk-notify-message-"+this.currentstatus).addClass("uk-notify-message-"+t),this.currentstatus=t,this):this.currentstatus}}),n.defaults={message:"",status:"",timeout:5e3,group:null,pos:"top-center",onClose:function(){}},t.notify=s,t.notify.message=n,t.notify.closeAll=o,s}); -------------------------------------------------------------------------------- /sites/sample/welcome/public_html/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GameBox Cloud Core Welcome", 3 | "icons": [ 4 | { 5 | "src": "\/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image\/png", 8 | "density": "4.0" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/framework/class.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local assert = assert 26 | local error = error 27 | local getmetatable = getmetatable 28 | local rawget = rawget 29 | local setmetatable = setmetatable 30 | local string_format = string.format 31 | local type = type 32 | 33 | local _iskindofinternal = function(mt, classname) 34 | if not mt then return false end 35 | 36 | local index = rawget(mt, "__index") 37 | if not index then return false end 38 | 39 | local cname = rawget(index, "__cname") 40 | if cname == classname then return true end 41 | 42 | return _iskindofinternal(getmetatable(index), classname) 43 | end 44 | 45 | function cc.iskindof(target, classname) 46 | local targetType = type(target) 47 | if targetType ~= "table" then 48 | return false 49 | end 50 | return _iskindofinternal(getmetatable(target), classname) 51 | end 52 | 53 | local _new = function(cls, ...) 54 | local instance = {} 55 | setmetatable(instance, {__index = cls}) 56 | instance.class = cls 57 | instance:ctor(...) 58 | return instance 59 | end 60 | 61 | function cc.class(classname, super) 62 | assert(type(classname) == "string", string_format("cc.class() - invalid class name \"%s\"", tostring(classname))) 63 | 64 | -- create class 65 | local cls = {__cname = classname, new = _new} 66 | 67 | -- set super class 68 | local superType = type(super) 69 | if superType == "table" then 70 | assert(type(super.__cname) == "string", string_format("cc.class() - create class \"%s\" used super class isn't declared by cc.class()", classname)) 71 | cls.super = super 72 | setmetatable(cls, {__index = cls.super}) 73 | elseif superType ~= "nil" then 74 | error(string_format("cc.class() - create class \"%s\" with invalid super type \"%s\"", classname, superType)) 75 | end 76 | 77 | if not cls.ctor then 78 | cls.ctor = function() end -- add default constructor 79 | end 80 | 81 | return cls 82 | end 83 | 84 | function cc.addComponent(target, cls) 85 | if not target.__components then 86 | target.__components = {} 87 | end 88 | local components = target.__components 89 | local name = cls.__cname 90 | if not components[name] then 91 | components[name] = cls:new(target) 92 | end 93 | return components[name] 94 | end 95 | 96 | function cc.getComponent(target, name) 97 | if type(name) == "table" then 98 | name = name.__cname 99 | end 100 | if not target.__components then 101 | return 102 | end 103 | return target.__components[name] 104 | end 105 | 106 | -- name is Class name or Class or Component object 107 | function cc.removeComponent(target, name) 108 | if type(name) == "table" then 109 | -- name is class or object 110 | if name.__cname then 111 | name = name.__cname 112 | else 113 | local mt = getmetatable(name) 114 | local __index = rawget(mt, "__index") 115 | if __index then 116 | name = rawget(__index, "__cname") 117 | end 118 | end 119 | end 120 | if target.__components then 121 | target.__components[name] = nil 122 | end 123 | end 124 | 125 | function cc.handler(target, method) 126 | return function(...) 127 | return method(target, ...) 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /src/framework/ctype.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local math_floor = math.floor 26 | 27 | function cc.checknumber(value, base) 28 | return tonumber(value, base) or 0 29 | end 30 | 31 | function cc.checkint(value) 32 | value = tonumber(value) or 0 33 | return math_floor(value + 0.5) 34 | end 35 | 36 | function cc.checkbool(value) 37 | return (value ~= nil and value ~= false) 38 | end 39 | 40 | function cc.checktable(value) 41 | if type(value) ~= "table" then value = {} end 42 | return value 43 | end 44 | -------------------------------------------------------------------------------- /src/framework/debug.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local ngx = ngx 26 | local ngx_log = nil 27 | if ngx then 28 | ngx_log = ngx.log 29 | end 30 | 31 | local cc = cc 32 | local debug_traceback = debug.traceback 33 | local error = error 34 | local print = print 35 | local string_format = string.format 36 | local string_rep = string.rep 37 | local string_upper = string.upper 38 | local table_concat = table.concat 39 | local tostring = tostring 40 | 41 | function cc.throw(fmt, ...) 42 | local msg 43 | if #{...} == 0 then 44 | msg = fmt 45 | else 46 | msg = string_format(fmt, ...) 47 | end 48 | if cc.DEBUG > cc.DEBUG_WARN then 49 | error(msg, 2) 50 | else 51 | error(msg, 0) 52 | end 53 | end 54 | 55 | local function _dump_value(v) 56 | if type(v) == "string" then 57 | v = "\"" .. v .. "\"" 58 | end 59 | return tostring(v) 60 | end 61 | 62 | function cc.dump(value, desciption, nesting, _print) 63 | if type(nesting) ~= "number" then nesting = 3 end 64 | _print = _print or print 65 | 66 | local lookup = {} 67 | local result = {} 68 | local traceback = string.split(debug_traceback("", 2), "\n") 69 | _print("dump from: " .. string.trim(traceback[2])) 70 | 71 | local function _dump(value, desciption, indent, nest, keylen) 72 | desciption = desciption or "" 73 | local spc = "" 74 | if type(keylen) == "number" then 75 | spc = string_rep(" ", keylen - string.len(_dump_value(desciption))) 76 | end 77 | if type(value) ~= "table" then 78 | result[#result +1 ] = string_format("%s%s%s = %s", indent, _dump_value(desciption), spc, _dump_value(value)) 79 | elseif lookup[tostring(value)] then 80 | result[#result +1 ] = string_format("%s%s%s = *REF*", indent, _dump_value(desciption), spc) 81 | else 82 | lookup[tostring(value)] = true 83 | if nest > nesting then 84 | result[#result +1 ] = string_format("%s%s = *MAX NESTING*", indent, _dump_value(desciption)) 85 | else 86 | result[#result +1 ] = string_format("%s%s = {", indent, _dump_value(desciption)) 87 | local indent2 = indent.." " 88 | local keys = {} 89 | local keylen = 0 90 | local values = {} 91 | for k, v in pairs(value) do 92 | keys[#keys + 1] = k 93 | local vk = _dump_value(k) 94 | local vkl = string.len(vk) 95 | if vkl > keylen then keylen = vkl end 96 | values[k] = v 97 | end 98 | table.sort(keys, function(a, b) 99 | if type(a) == "number" and type(b) == "number" then 100 | return a < b 101 | else 102 | return tostring(a) < tostring(b) 103 | end 104 | end) 105 | for i, k in ipairs(keys) do 106 | _dump(values[k], k, indent2, nest + 1, keylen) 107 | end 108 | result[#result +1] = string_format("%s}", indent) 109 | end 110 | end 111 | end 112 | _dump(value, desciption, "- ", 1) 113 | 114 | for i, line in ipairs(result) do 115 | _print(line) 116 | end 117 | end 118 | 119 | function cc.printf(fmt, ...) 120 | print(string_format(tostring(fmt), ...)) 121 | end 122 | 123 | function cc.printlog(tag, fmt, ...) 124 | fmt = tostring(fmt) 125 | if ngx_log then 126 | if tag == "ERR" and cc.DEBUG > cc.DEBUG_WARN then 127 | ngx_log(ngx.ERR, string_format(fmt, ...) .. "\n" .. debug_traceback("", 3)) 128 | else 129 | ngx_log(ngx[tag], string_format(fmt, ...)) 130 | end 131 | return 132 | end 133 | 134 | local t = { 135 | "[", 136 | string_upper(tostring(tag)), 137 | "] ", 138 | string_format(fmt, ...) 139 | } 140 | if tag == "ERR" then 141 | table_insert(t, debug_traceback("", 2)) 142 | end 143 | print(table.concat(t)) 144 | end 145 | 146 | local _printlog = cc.printlog 147 | 148 | function cc.printerror(fmt, ...) 149 | _printlog("ERR", fmt, ...) 150 | end 151 | 152 | function cc.printdebug(fmt, ...) 153 | if cc.DEBUG >= cc.DEBUG_VERBOSE then 154 | _printlog("DEBUG", fmt, ...) 155 | end 156 | end 157 | 158 | function cc.printinfo(fmt, ...) 159 | if cc.DEBUG >= cc.DEBUG_INFO then 160 | _printlog("INFO", fmt, ...) 161 | end 162 | end 163 | 164 | function cc.printwarn(fmt, ...) 165 | if cc.DEBUG >= cc.DEBUG_WARN then 166 | _printlog("WARN", fmt, ...) 167 | end 168 | end 169 | -------------------------------------------------------------------------------- /src/framework/init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local debug_getlocal = debug.getlocal 26 | local string_byte = string.byte 27 | local string_find = string.find 28 | local string_format = string.format 29 | local string_lower = string.lower 30 | local string_sub = string.sub 31 | local table_concat = table.concat 32 | 33 | cc = cc or {} 34 | 35 | socket = {} -- avoid require("socket") warning 36 | -- export global variable 37 | local _g = _G 38 | cc.exports = {} 39 | setmetatable(cc.exports, { 40 | __newindex = function(_, name, value) 41 | rawset(_g, name, value) 42 | end, 43 | 44 | __index = function(_, name) 45 | return rawget(_g, name) 46 | end 47 | }) 48 | 49 | -- disable create unexpected global variable 50 | setmetatable(_g, { 51 | __newindex = function(_, name, value) 52 | local msg = string_format("USE \"cc.exports.%s = \" INSTEAD OF SET GLOBAL VARIABLE", name) 53 | print(debug.traceback(msg, 2)) 54 | if not ngx then print("") end 55 | end 56 | }) 57 | 58 | -- 59 | 60 | cc.DEBUG_ERROR = 0 61 | cc.DEBUG_WARN = 1 62 | cc.DEBUG_INFO = 2 63 | cc.DEBUG_VERBOSE = 3 64 | cc.DEBUG = cc.DEBUG_DEBUG 65 | 66 | local _loaded = {} 67 | -- loader 68 | function cc.import(name, current) 69 | local _name = name 70 | local first = string_byte(name) 71 | if first ~= 46 and _loaded[name] then 72 | return _loaded[name] 73 | end 74 | 75 | if first == 35 --[[ "#" ]] then 76 | name = string_sub(name, 2) 77 | name = string_format("packages.%s.%s", name, name) 78 | end 79 | 80 | if first ~= 46 --[[ "." ]] then 81 | _loaded[_name] = require(name) 82 | return _loaded[_name] 83 | end 84 | 85 | if not current then 86 | local _, v = debug_getlocal(3, 1) 87 | current = v 88 | end 89 | 90 | _name = current .. name 91 | if not _loaded[_name] then 92 | local pos = string_find(current, "%.[^%.]*$") 93 | if pos then 94 | current = string_sub(current, 1, pos - 1) 95 | end 96 | 97 | _loaded[_name] = require(current .. name) 98 | end 99 | return _loaded[_name] 100 | end 101 | 102 | -- load basics modules 103 | require("framework.class") 104 | require("framework.table") 105 | require("framework.string") 106 | require("framework.debug") 107 | require("framework.math") 108 | require("framework.ctype") 109 | require("framework.os") 110 | require("framework.io") 111 | -------------------------------------------------------------------------------- /src/framework/io.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local string_byte = string.byte 26 | local string_len = string.len 27 | local string_sub = string.sub 28 | 29 | function io.exists(path) 30 | local file = io.open(path, "rb") 31 | if file then 32 | io.close(file) 33 | return true 34 | end 35 | return false 36 | end 37 | 38 | function io.readfile(path) 39 | local file = io.open(path, "rb") 40 | if file then 41 | local content = file:read("*a") 42 | io.close(file) 43 | return content 44 | end 45 | return nil 46 | end 47 | 48 | function io.writefile(path, content, mode) 49 | mode = mode or "w+b" 50 | local file = io.open(path, mode) 51 | if file then 52 | if file:write(content) == nil then 53 | return false 54 | end 55 | io.close(file) 56 | return true 57 | else 58 | return false 59 | end 60 | end 61 | 62 | function io.pathinfo(path) 63 | local pos = string_len(path) 64 | local extpos = pos + 1 65 | while pos > 0 do 66 | local b = string_byte(path, pos) 67 | if b == 46 then -- 46 = char "." 68 | extpos = pos 69 | elseif b == 47 then -- 47 = char "/" 70 | break 71 | end 72 | pos = pos - 1 73 | end 74 | 75 | local dirname = string_sub(path, 1, pos) 76 | local filename = string_sub(path, pos + 1) 77 | 78 | extpos = extpos - pos 79 | local basename = string_sub(filename, 1, extpos - 1) 80 | local extname = string_sub(filename, extpos) 81 | 82 | return { 83 | dirname = dirname, 84 | filename = filename, 85 | basename = basename, 86 | extname = extname 87 | } 88 | end 89 | 90 | function io.filesize(path) 91 | local size = false 92 | local file = io.open(path, "r") 93 | if file then 94 | local current = file:seek() 95 | size = file:seek("end") 96 | file:seek("set", current) 97 | io.close(file) 98 | end 99 | return size 100 | end 101 | -------------------------------------------------------------------------------- /src/framework/math.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local math_ceil = math.ceil 26 | local math_floor = math.floor 27 | local ok, socket = pcall(function() 28 | return require("socket") 29 | end) 30 | 31 | function math.round(value) 32 | value = tonumber(value) or 0 33 | return math_floor(value + 0.5) 34 | end 35 | 36 | function math.trunc(x) 37 | if x <= 0 then 38 | return math_ceil(x) 39 | end 40 | if math_ceil(x) == x then 41 | x = math_ceil(x) 42 | else 43 | x = math_ceil(x) - 1 44 | end 45 | return x 46 | end 47 | 48 | function math.newrandomseed() 49 | if socket then 50 | math.randomseed(socket.gettime() * 1000) 51 | else 52 | math.randomseed(os.time()) 53 | end 54 | 55 | math.random() 56 | math.random() 57 | math.random() 58 | math.random() 59 | end 60 | -------------------------------------------------------------------------------- /src/framework/os.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | function os.gettimezone() 26 | local now = os.time() 27 | return os.difftime(now, os.time(os.date("!*t", now))) / 3600 28 | end 29 | 30 | function os.gettime(date, utc) 31 | local time = os.time({ 32 | year = date[1], 33 | month = date[2], 34 | day = date[3], 35 | hour = date[4], 36 | min = date[5], 37 | sec = date[6], 38 | }) 39 | if utc ~= false then 40 | local now = os.time() 41 | local offset = os.difftime(now, os.time(os.date("!*t", now))) 42 | time = time + offset 43 | end 44 | return time 45 | end 46 | -------------------------------------------------------------------------------- /src/framework/string.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local string_find = string.find 26 | local string_gsub = string.gsub 27 | local string_len = string.len 28 | local string_sub = string.sub 29 | local string_upper = string.upper 30 | local table_insert = table.insert 31 | local tostring = tostring 32 | 33 | function string.split(input, delimiter) 34 | input = tostring(input) 35 | delimiter = tostring(delimiter) 36 | if (delimiter == "") then return false end 37 | local pos,arr = 1, {} 38 | for st, sp in function() return string_find(input, delimiter, pos, true) end do 39 | local str = string_sub(input, pos, st - 1) 40 | if str ~= "" then 41 | table_insert(arr, str) 42 | end 43 | pos = sp + 1 44 | end 45 | if pos <= string_len(input) then 46 | table_insert(arr, string_sub(input, pos)) 47 | end 48 | return arr 49 | end 50 | 51 | local _TRIM_CHARS = " \t\n\r" 52 | 53 | function string.ltrim(input, chars) 54 | chars = chars or _TRIM_CHARS 55 | local pattern = "^[" .. chars .. "]+" 56 | return string_gsub(input, pattern, "") 57 | end 58 | 59 | function string.rtrim(input, chars) 60 | chars = chars or _TRIM_CHARS 61 | local pattern = "[" .. chars .. "]+$" 62 | return string_gsub(input, pattern, "") 63 | end 64 | 65 | function string.trim(input, chars) 66 | chars = chars or _TRIM_CHARS 67 | local pattern = "^[" .. chars .. "]+" 68 | input = string_gsub(input, pattern, "") 69 | pattern = "[" .. chars .. "]+$" 70 | return string_gsub(input, pattern, "") 71 | end 72 | 73 | function string.ucfirst(input) 74 | return string_upper(string_sub(input, 1, 1)) .. string_sub(input, 2) 75 | end 76 | -------------------------------------------------------------------------------- /src/framework/table.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local string_format = string.format 26 | local pairs = pairs 27 | 28 | local ok, table_new = pcall(require, "table.new") 29 | if not ok or type(table_new) ~= "function" then 30 | function table:new() 31 | return {} 32 | end 33 | end 34 | 35 | local _copy 36 | _copy = function(t, lookup) 37 | if type(t) ~= "table" then 38 | return t 39 | elseif lookup[t] then 40 | return lookup[t] 41 | end 42 | local n = {} 43 | lookup[t] = n 44 | for key, value in pairs(t) do 45 | n[_copy(key, lookup)] = _copy(value, lookup) 46 | end 47 | return n 48 | end 49 | 50 | function table.copy(t) 51 | local lookup = {} 52 | return _copy(t, lookup) 53 | end 54 | 55 | function table.keys(hashtable) 56 | local keys = {} 57 | for k, v in pairs(hashtable) do 58 | keys[#keys + 1] = k 59 | end 60 | return keys 61 | end 62 | 63 | function table.values(hashtable) 64 | local values = {} 65 | for k, v in pairs(hashtable) do 66 | values[#values + 1] = v 67 | end 68 | return values 69 | end 70 | 71 | function table.merge(dest, src) 72 | for k, v in pairs(src) do 73 | dest[k] = v 74 | end 75 | end 76 | 77 | function table.map(t, fn) 78 | local n = {} 79 | for k, v in pairs(t) do 80 | n[k] = fn(v, k) 81 | end 82 | return n 83 | end 84 | 85 | function table.walk(t, fn) 86 | for k,v in pairs(t) do 87 | fn(v, k) 88 | end 89 | end 90 | 91 | function table.filter(t, fn) 92 | local n = {} 93 | for k, v in pairs(t) do 94 | if fn(v, k) then 95 | n[k] = v 96 | end 97 | end 98 | return n 99 | end 100 | 101 | function table.length(t) 102 | local count = 0 103 | for _, __ in pairs(t) do 104 | count = count + 1 105 | end 106 | return count 107 | end 108 | 109 | function table.readonly(t, name) 110 | name = name or "table" 111 | setmetatable(t, { 112 | __newindex = function() 113 | error(string_format("<%s:%s> is readonly table", name, tostring(t))) 114 | end, 115 | __index = function(_, key) 116 | error(string_format("<%s:%s> not found key: %s", name, tostring(t), key)) 117 | end 118 | }) 119 | return t 120 | end 121 | -------------------------------------------------------------------------------- /src/packages/event/event.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local cc = cc 26 | local string_upper = string.upper 27 | local tostring = tostring 28 | local type = type 29 | 30 | local Event = cc.class("Event") 31 | 32 | local _newtag = 0 33 | 34 | function Event:ctor(target) 35 | self._target = target 36 | self._listeners = {} 37 | end 38 | 39 | function Event:bind(name, listener, tag) 40 | local listeners = self._listeners 41 | name = string_upper(tostring(name)) 42 | if not listeners[name] then 43 | listeners[name] = {} 44 | end 45 | 46 | if not tag then 47 | _newtag = _newtag + 1 48 | tag = "#" .. tostring(_newtag) 49 | end 50 | 51 | listeners[name][tag] = listener 52 | 53 | -- cc.printinfo("[Event:%s] bind event '%s' with listener '%s'", tostring(self._target), name, tag) 54 | 55 | return tag 56 | end 57 | 58 | function Event:unbind(tag) 59 | local listeners = self._listeners 60 | for name, listenersForEvent in pairs(listeners) do 61 | for _tag, _ in pairs(listenersForEvent) do 62 | if tag == _tag then 63 | listenersForEvent[tag] = nil 64 | -- cc.printinfo("[Event:%s] unbind event '%s' listener '%s'", tostring(self._target), name, tag) 65 | return 66 | end 67 | end 68 | end 69 | end 70 | 71 | function Event:trigger(event) 72 | if type(event) ~= "table" then 73 | event = {name = event} 74 | end 75 | event.name = string_upper(tostring(event.name)) 76 | 77 | -- cc.printinfo("[Event:%s] dispatch event '%s'", tostring(self), event.name) 78 | 79 | local listeners = self._listeners 80 | if not listeners[event.name] then 81 | return 82 | end 83 | 84 | event._stop = false 85 | event.target = self._target 86 | event.stop = function() 87 | event._stop = true 88 | end 89 | 90 | for tag, listener in pairs(listeners[event.name]) do 91 | -- cc.printinfo("[Event:%s] trigger event '%s' to listener '%s'", tostring(self._target), event.name, tag) 92 | listener(event) 93 | if event._stop then 94 | cc.printinfo("[Event:%s] break dispatching event '%s'", tostring(self._target), event.name) 95 | break 96 | end 97 | end 98 | end 99 | 100 | function Event:remove(name) 101 | name = string_upper(tostring(name)) 102 | self._listeners[name] = nil 103 | -- cc.printinfo("[Event:%s] remove event '%s' all listeners", tostring(self._target), name) 104 | end 105 | 106 | function Event:removeAll() 107 | self._listeners = {} 108 | -- cc.printinfo("[Event:%s] remove all listeners", tostring(self._target)) 109 | end 110 | 111 | function Event:dump() 112 | cc.printinfo("[Event:%s] dump all listeners", tostring(self._target)) 113 | for name, listeners in pairs(self._listeners) do 114 | cc.printf(" event: %s", name) 115 | for tag, listener in pairs(listeners) do 116 | cc.printf(" %s: %s", tag, tostring(listener)) 117 | end 118 | end 119 | end 120 | 121 | return Event 122 | -------------------------------------------------------------------------------- /src/packages/gbc/ActionBase.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local ActionBase = cc.class("ActionBase") 26 | 27 | function ActionBase:ctor(instance) 28 | self._instance = instance 29 | self:init() 30 | end 31 | 32 | function ActionBase:getInstance() 33 | return self._instance 34 | end 35 | 36 | function ActionBase:getInstanceConfig() 37 | return self._instance.config 38 | end 39 | 40 | function ActionBase:init() 41 | end 42 | 43 | return ActionBase 44 | -------------------------------------------------------------------------------- /src/packages/gbc/Broadcast.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local tostring = tostring 26 | local type = type 27 | 28 | local json = cc.import("#json") 29 | local Constants = cc.import(".Constants") 30 | 31 | local json_encode = json.encode 32 | 33 | local Broadcast = cc.class("Broadcast") 34 | 35 | local _formatmsg 36 | 37 | function Broadcast:ctor(redis, messageType, websocketInstance) 38 | self._redis = redis 39 | self._messageType = messageType 40 | if websocketInstance and websocketInstance.getConnectId then 41 | self._websocketInstance = websocketInstance 42 | end 43 | end 44 | 45 | function Broadcast:sendMessage(connectId, message, format) 46 | format = format or self._messageType 47 | message = _formatmsg(message, format) 48 | 49 | if self._websocketInstance and self._websocketInstance:getConnectId() == connectId then 50 | return self._websocketInstance._socket:send_text(tostring(message)) 51 | else 52 | local connectChannel = Constants.CONNECT_CHANNEL_PREFIX .. connectId 53 | local ok, err = self._redis:publish(connectChannel, message) 54 | if not ok then 55 | return nil, err 56 | end 57 | return 1 58 | end 59 | end 60 | 61 | function Broadcast:sendMessageToAll(message, format) 62 | format = format or self._messageType 63 | message = _formatmsg(message, format) 64 | return self._redis:publish(Constants.BROADCAST_ALL_CHANNEL, message) 65 | end 66 | 67 | function Broadcast:sendControlMessage(connectId, message) 68 | local controlChannel = Constants.CONTROL_CHANNEL_PREFIX .. connectId 69 | local ok, err = self._redis:publish(controlChannel, tostring(message)) 70 | if not ok then 71 | return nil, err 72 | end 73 | return 1 74 | end 75 | 76 | -- private 77 | 78 | _formatmsg = function(message, format) 79 | format = format or Constants.MESSAGE_FORMAT_JSON 80 | if type(message) == "table" then 81 | if format == Constants.MESSAGE_FORMAT_JSON then 82 | message = json_encode(message) 83 | else 84 | -- TODO: support more message formats 85 | end 86 | end 87 | return tostring(message) 88 | end 89 | 90 | return Broadcast 91 | -------------------------------------------------------------------------------- /src/packages/gbc/CommandLineBootstrap.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local Factory = cc.import(".Factory") 26 | 27 | local CommandLineBootstrap = cc.class("CommandLineBootstrap") 28 | 29 | function CommandLineBootstrap:ctor(appKeys, globalConfig) 30 | self._configs = Factory.makeAppConfigs(appKeys, globalConfig, package.path) 31 | end 32 | 33 | function CommandLineBootstrap:runapp(appRootPath) 34 | local appConfig = self._configs[appRootPath] 35 | local cli = Factory.create(appConfig, "CommandLineInstance") 36 | cli:run() 37 | end 38 | 39 | return CommandLineBootstrap 40 | -------------------------------------------------------------------------------- /src/packages/gbc/CommandLineInstanceBase.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local string_format = string.format 26 | 27 | local Constants = cc.import(".Constants") 28 | 29 | local InstanceBase = cc.import(".InstanceBase") 30 | local CommandLineInstanceBase = cc.class("CommandLineInstanceBase", InstanceBase) 31 | 32 | function CommandLineInstanceBase:ctor(config, arg) 33 | CommandLineInstanceBase.super.ctor(self, config, Constants.CLI_REQUEST_TYPE) 34 | self._requestParameters = cc.checktable(arg) 35 | end 36 | 37 | function CommandLineInstanceBase:run() 38 | local actionName = self._requestParameters.action or "" 39 | local result = self:runAction(actionName, self._requestParameters) 40 | if type(result) ~= "table" then 41 | print(result) 42 | else 43 | cc.dump(result) 44 | end 45 | end 46 | 47 | return CommandLineInstanceBase 48 | -------------------------------------------------------------------------------- /src/packages/gbc/Constants.lua: -------------------------------------------------------------------------------- 1 | 2 | local _M = {} 3 | 4 | -- request type 5 | _M.HTTP_REQUEST_TYPE = "http" 6 | _M.WEBSOCKET_REQUEST_TYPE = "websocket" 7 | _M.CLI_REQUEST_TYPE = "cli" 8 | _M.WORKER_REQUEST_TYPE = "worker" 9 | 10 | -- message type 11 | _M.MESSAGE_FORMAT_JSON = "json" 12 | _M.MESSAGE_FORMAT_TEXT = "text" 13 | _M.DEFAULT_MESSAGE_FORMAT = _M.MESSAGE_FORMAT_JSON 14 | 15 | -- redis keys 16 | _M.NEXT_CONNECT_ID_KEY = "_NEXT_CONNECT_ID" 17 | _M.CONNECT_CHANNEL_PREFIX = "_CN_" 18 | _M.CONTROL_CHANNEL_PREFIX = "_CT_" 19 | _M.BROADCAST_ALL_CHANNEL = "_CN_ALL" 20 | 21 | -- beanstalkd 22 | _M.BEANSTALKD_JOB_TUBE_PATTERN = "job-%s" -- job- 23 | 24 | -- websocket 25 | _M.WEBSOCKET_TEXT_MESSAGE_TYPE = "text" 26 | _M.WEBSOCKET_BINARY_MESSAGE_TYPE = "binary" 27 | _M.WEBSOCKET_SUBPROTOCOL_PATTERN = "gbc%-auth%-([%w%d%-]+)" -- token 28 | _M.WEBSOCKET_DEFAULT_TIME_OUT = 10 * 1000 -- 10s 29 | _M.WEBSOCKET_DEFAULT_MAX_PAYLOAD_LEN = 16 * 1024 -- 16KB 30 | 31 | -- misc 32 | _M.STRIP_LUA_PATH_PATTERN = "[/%.%a%-]+/([%a%-]+%.lua:%d+: )" 33 | 34 | -- control message 35 | _M.CLOSE_CONNECT = "SEND_CLOSE" 36 | 37 | return table.readonly(_M) 38 | -------------------------------------------------------------------------------- /src/packages/gbc/Factory.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local _CUR = ... 26 | 27 | local string_find = string.find 28 | local string_format = string.format 29 | local string_sub = string.sub 30 | 31 | local Factory = cc.class("Factory") 32 | 33 | function Factory.create(config, classname, ...) 34 | if config.app.packagePath then 35 | package.path = config.app.packagePath 36 | end 37 | 38 | local ok, cls = pcall(require, classname) 39 | if not ok then 40 | local err = cls 41 | local pos = string_find(err, "not found:", 1, true) 42 | if not pos then 43 | cc.throw(err) 44 | end 45 | 46 | if not string_find(err, string_format("module '%s' not found:", classname), 1, true) then 47 | cc.throw(err) 48 | end 49 | 50 | cls = nil 51 | end 52 | 53 | if not cls then 54 | cls = cc.import("." .. classname .. "Base", _CUR) 55 | end 56 | 57 | return cls:new(config, ...) 58 | end 59 | 60 | function Factory.makeAppConfigs(appKeys, serverConfig, defaultPackagePath) 61 | local appConfigs = {} 62 | 63 | for appRootPath, opts in pairs(appKeys) do 64 | local config = table.copy(serverConfig) 65 | local appConfig = config.app 66 | appConfig.rootPath = appRootPath 67 | appConfig.appKey = opts.key 68 | appConfig.appIndex = opts.index 69 | appConfig.appName = opts.name 70 | 71 | local appPackagePath = appRootPath .. "/?.lua;" 72 | local pattern = string.gsub(appPackagePath, "([.?-])", "%%%1") 73 | defaultPackagePath = string.gsub(defaultPackagePath, pattern, "") 74 | appConfig.packagePath = appPackagePath .. defaultPackagePath 75 | 76 | local appConfigPath = appRootPath .. "/conf/app_config.lua" 77 | if io.exists(appConfigPath) then 78 | local appCustomConfig = dofile(appConfigPath) 79 | table.merge(appConfig, appCustomConfig) 80 | end 81 | 82 | appConfigs[appRootPath] = config 83 | end 84 | 85 | return appConfigs 86 | end 87 | 88 | return Factory 89 | -------------------------------------------------------------------------------- /src/packages/gbc/HttpInstanceBase.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local ngx = ngx 26 | local ngx_say = ngx.say 27 | local req_get_body_data = ngx.req.get_body_data 28 | local req_get_headers = ngx.req.get_headers 29 | local req_get_method = ngx.req.get_method 30 | local req_get_post_args = ngx.req.get_post_args 31 | local req_get_uri_args = ngx.req.get_uri_args 32 | local req_read_body = ngx.req.read_body 33 | local string_format = string.format 34 | local string_gsub = string.gsub 35 | local string_ltrim = string.ltrim 36 | local table_merge = table.merge 37 | 38 | local json = cc.import("#json") 39 | local Constants = cc.import(".Constants") 40 | 41 | local InstanceBase = cc.import(".InstanceBase") 42 | local HttpInstanceBase = cc.class("HttpInstanceBase", InstanceBase) 43 | 44 | function HttpInstanceBase:ctor(config) 45 | HttpInstanceBase.super.ctor(self, config, Constants.HTTP_REQUEST_TYPE) 46 | 47 | if config.app.httpMessageFormat then 48 | self.config.app.messageFormat = config.app.httpMessageFormat 49 | end 50 | 51 | self._requestMethod = req_get_method() 52 | self._requestParameters = req_get_uri_args() 53 | 54 | if self._requestMethod == "POST" then 55 | req_read_body() 56 | -- handle json body 57 | local headers = req_get_headers() 58 | if headers["Content-Type"] == "application/json" then 59 | local body = req_get_body_data() 60 | --[[ 61 | This function returns nil if 62 | 63 | - the request body has not been read, 64 | - the request body has been read into disk temporary files, 65 | - or the request body has zero size. 66 | 67 | If the request body has not been read yet, call ngx.req.read_body first 68 | (or turned on lua_need_request_body to force this module to read the 69 | request body. This is not recommended however). 70 | 71 | If the request body has been read into disk files, try calling 72 | the ngx.req.get_body_file function instead. 73 | 74 | To force in-memory request bodies, try setting client_body_buffer_size 75 | to the same size value in client_max_body_size. 76 | ]] 77 | if body then 78 | local data, err = json.decode(body) 79 | if err then 80 | cc.printwarn("HttpInstanceBase:ctor() - invalid JSON content, %s", err) 81 | else 82 | table_merge(self._requestParameters, data) 83 | end 84 | end 85 | else 86 | table_merge(self._requestParameters, req_get_post_args()) 87 | end 88 | end 89 | end 90 | 91 | function HttpInstanceBase:run() 92 | local result, err = self:runEventLoop() 93 | result, err = self:_genOutput(result, err) 94 | if err then 95 | -- return an error page with custom contents 96 | ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR 97 | ngx_say(err) 98 | ngx.exit(ngx.HTTP_OK) 99 | else 100 | ngx.status = ngx.HTTP_OK 101 | if result then ngx_say(result) end 102 | end 103 | end 104 | 105 | -- actually it is not a loop, since it is based on HTTP. 106 | function HttpInstanceBase:runEventLoop() 107 | local actionName = self._requestParameters.action or "" 108 | actionName = tostring(actionName) 109 | if cc.DEBUG > cc.DEBUG_WARN then 110 | cc.printinfo("HTTP action: %s, data: %s", actionName, json.encode(self._requestParameters)) 111 | end 112 | 113 | local err = nil 114 | local ok, result = xpcall(function() 115 | return self:runAction(actionName, self._requestParameters) 116 | end, function(_err) 117 | err = _err 118 | if cc.DEBUG > cc.DEBUG_WARN then 119 | err = debug.traceback(err, 3) 120 | cc.printwarn(err) 121 | end 122 | end) 123 | if err then 124 | return nil, self:_formatError(actionName, err) 125 | end 126 | return result 127 | end 128 | 129 | function HttpInstanceBase:_formatError(actionName, err) 130 | return string_format("run action \"%s\" error, %s", actionName, err) 131 | end 132 | 133 | function HttpInstanceBase:_genOutput(result, err) 134 | local rtype = type(result) 135 | if self.config.app.messageFormat == Constants.MESSAGE_FORMAT_JSON then 136 | if err then 137 | result = {err = err} 138 | elseif rtype == "nil" then 139 | result = {} 140 | elseif rtype ~= "table" then 141 | result = {result = tostring(result)} 142 | end 143 | return json.encode(result) 144 | elseif self.config.app.messageFormat == Constants.MESSAGE_FORMAT_TEXT then 145 | if err then 146 | return nil, err 147 | elseif rtype == "nil" then 148 | return "" 149 | else 150 | return tostring(result) 151 | end 152 | end 153 | end 154 | 155 | return HttpInstanceBase 156 | -------------------------------------------------------------------------------- /src/packages/gbc/InstanceBase.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local pcall = pcall 26 | local string_byte = string.byte 27 | local string_find = string.find 28 | local string_format = string.format 29 | local string_gsub = string.gsub 30 | local string_lower = string.lower 31 | local string_sub = string.sub 32 | local string_trim = string.trim 33 | local string_ucfirst = string.ucfirst 34 | local table_concat = table.concat 35 | local table_remove = table.remove 36 | local type = type 37 | 38 | local sleep 39 | 40 | if ngx then 41 | sleep = ngx.sleep 42 | else 43 | local socket = require("socket") 44 | sleep = socket.sleep 45 | end 46 | 47 | local Redis = cc.import("#redis") 48 | local Beanstalkd = cc.import("#beanstalkd") 49 | local Jobs = cc.import("#jobs") 50 | local Event = cc.import("#event") 51 | local Constants = cc.import(".Constants") 52 | 53 | local InstanceBase = cc.class("InstanceBase") 54 | 55 | local _MODULE_SUFFIX = 'Action' 56 | local _MEHTOD_SUFFIX = 'Action' 57 | local _CLI_PACKAGE_NAME = 'commands' 58 | local _WORKER_PACKAGE_NAME = 'jobs' 59 | local _HTTP_PACKAGE_NAME = 'actions' 60 | 61 | local _normalize, _getpath, _loadmodule, _checkreqtype 62 | 63 | function InstanceBase:ctor(config, requestType) 64 | self.config = table.copy(cc.checktable(config)) 65 | local appConfig = self.config.app 66 | appConfig.messageFormat = appConfig.messageFormat or Constants.MESSAGE_FORMAT 67 | 68 | self._requestType = requestType 69 | self._package = appConfig.package 70 | if not self._package then 71 | if requestType == Constants.CLI_REQUEST_TYPE then 72 | self._package = _CLI_PACKAGE_NAME 73 | elseif requestType == Constants.WORKER_REQUEST_TYPE then 74 | self._package = _WORKER_PACKAGE_NAME 75 | else 76 | self._package = _HTTP_PACKAGE_NAME 77 | end 78 | end 79 | 80 | self._requestParameters = nil 81 | self._modules = {} 82 | self._event = cc.addComponent(self, Event) 83 | end 84 | 85 | function InstanceBase:getRequestType() 86 | return self._requestType 87 | end 88 | 89 | function InstanceBase:runAction(actionName, args) 90 | local appConfig = self.config.app 91 | 92 | local moduleName, methodName, folder = _normalize(actionName) 93 | methodName = methodName .. _MEHTOD_SUFFIX 94 | 95 | local actionModulePath = _getpath(moduleName, folder, self._package) 96 | local action = self._modules[actionModulePath] 97 | if not action then 98 | local actionModule = _loadmodule(actionModulePath) 99 | local acceptedRequestType = actionModule.ACCEPTED_REQUEST_TYPE or appConfig.defaultAcceptedRequestType 100 | local currentRequestType = self:getRequestType() 101 | if not _checkreqtype(currentRequestType, acceptedRequestType) then 102 | cc.throw("can't access this action via request type \"%s\"", currentRequestType) 103 | end 104 | 105 | action = actionModule:new(self) 106 | self._modules[actionModulePath] = action 107 | end 108 | 109 | local method = action[methodName] 110 | if type(method) ~= "function" then 111 | cc.throw("invalid action method \"%s:%s()\"", moduleName, methodName) 112 | end 113 | 114 | if not args then 115 | args = self._requestParameters or {} 116 | end 117 | 118 | return method(action, args) 119 | end 120 | 121 | function InstanceBase:getRedis() 122 | local redis = self._redis 123 | if not redis then 124 | local config = self.config.server.redis 125 | redis = Redis:new() 126 | 127 | local ok, err 128 | if config.socket then 129 | ok, err = redis:connect(config.socket) 130 | else 131 | ok, err = redis:connect(config.host, config.port) 132 | end 133 | if not ok then 134 | cc.throw("InstanceBase:getRedis() - %s", err) 135 | end 136 | 137 | redis:select(self.config.app.appIndex) 138 | self._redis = redis 139 | end 140 | return redis 141 | end 142 | 143 | function InstanceBase:getJobs(opts) 144 | local jobs = self._jobs 145 | if not jobs then 146 | local bean = Beanstalkd:new() 147 | local config = self.config.server.beanstalkd 148 | local try = 3 149 | while true do 150 | local ok, err = bean:connect(config.host, config.port) 151 | if ok then break end 152 | try = try - 1 153 | if try == 0 then 154 | cc.throw("InstanceBase:getJobs() - connect to beanstalkd, %s", err) 155 | else 156 | sleep(1.0) 157 | end 158 | end 159 | 160 | local tube = string_format(Constants.BEANSTALKD_JOB_TUBE_PATTERN, tostring(self.config.app.appIndex)) 161 | bean:use(tube) 162 | bean:watch(tube) 163 | bean:ignore("default") 164 | 165 | jobs = Jobs:new(bean, self:getRedis()) 166 | self._jobs = jobs 167 | end 168 | return jobs 169 | end 170 | 171 | -- private 172 | 173 | _normalize = function(actionName) 174 | if not actionName or actionName == "" then 175 | actionName = "index.index" 176 | end 177 | 178 | local folder = "" 179 | if string_byte(actionName) == 47 --[[ / ]] then 180 | local pos = 1 181 | local offset = 2 182 | while true do 183 | pos = string_find(actionName, "/", offset) 184 | if not pos then 185 | pos = offset - 1 186 | break 187 | else 188 | offset = offset + 1 189 | end 190 | end 191 | 192 | folder = string_trim(string_sub(actionName, 1, pos), "/") 193 | actionName = string_sub(actionName, pos + 1) 194 | end 195 | 196 | actionName = string_lower(actionName) 197 | actionName = string_gsub(actionName, "[^%a./]", "") 198 | actionName = string_gsub(actionName, "^[.]+", "") 199 | actionName = string_gsub(actionName, "[.]+$", "") 200 | 201 | -- demo.hello.say --> {"demo", "hello", "say"] 202 | local parts = string.split(actionName, ".") 203 | local c = #parts 204 | if c == 1 then 205 | return string_ucfirst(parts[1]), "index", folder 206 | end 207 | -- method = "say" 208 | local method = parts[c] 209 | table_remove(parts, c) 210 | c = c - 1 211 | -- mdoule = "demo.Hello" 212 | parts[c] = string_ucfirst(parts[c]) 213 | return table_concat(parts, "."), method, folder 214 | end 215 | 216 | _getpath = function(moduleName, folder, package) 217 | moduleName = moduleName .. _MODULE_SUFFIX 218 | if folder ~= "" then 219 | return string_format("%s.%s", string.gsub(folder, "/", "."), moduleName) 220 | else 221 | return string_format("%s.%s", package, moduleName) 222 | end 223 | end 224 | 225 | _loadmodule = function(actionModulePath) 226 | if cc.DEBUG >= cc.DEBUG_INFO then 227 | package.loaded[actionModulePath] = nil 228 | end 229 | local ok, actionModule = pcall(require, actionModulePath) 230 | if not ok then 231 | cc.throw("%s", actionModule) -- actionModule is error message 232 | end 233 | return actionModule 234 | end 235 | 236 | _checkreqtype = function(currentRequestType, acceptedRequestType) 237 | if type(acceptedRequestType) == "table" then 238 | for _, v in ipairs(acceptedRequestType) do 239 | if string_lower(v) == currentRequestType then 240 | return true 241 | end 242 | end 243 | elseif type(acceptedRequestType) == "string" then 244 | return currentRequestType == string_lower(acceptedRequestType) 245 | else 246 | cc.throw("invalid ACCEPTED_REQUEST_TYPE of the action.") 247 | end 248 | 249 | return false 250 | end 251 | 252 | return InstanceBase 253 | -------------------------------------------------------------------------------- /src/packages/gbc/NginxBootstrap.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local Factory = cc.import(".Factory") 26 | 27 | local NginxBootstrap = cc.class("NginxBootstrap") 28 | 29 | function NginxBootstrap:ctor(appKeys, globalConfig) 30 | self._configs = Factory.makeAppConfigs(appKeys, globalConfig, package.path) 31 | end 32 | 33 | function NginxBootstrap:runapp(appRootPath) 34 | local headers = ngx.req.get_headers() 35 | local upgrade = headers.upgrade 36 | if type(upgrade) == "table" then 37 | upgrade = upgrade[1] 38 | end 39 | 40 | local classNamePrefix = "HttpInstance" 41 | local appConfig = self._configs[appRootPath] 42 | if upgrade and string.lower(upgrade) == "websocket" then 43 | classNamePrefix = "WebSocketInstance" 44 | if not appConfig.app.websocketEnabled then 45 | ngx.exit(ngx.HTTP_FORBIDDEN) 46 | end 47 | else 48 | if not appConfig.app.httpEnabled then 49 | ngx.exit(ngx.HTTP_FORBIDDEN) 50 | end 51 | end 52 | 53 | local connect = Factory.create(appConfig, classNamePrefix) 54 | connect:run() 55 | end 56 | 57 | return NginxBootstrap 58 | -------------------------------------------------------------------------------- /src/packages/gbc/WorkerBootstrap.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local process = cc.import("process") 26 | local Factory = cc.import(".Factory") 27 | 28 | local WorkerBootstrap = cc.class("WorkerBootstrap") 29 | 30 | function WorkerBootstrap:ctor(appKeys, globalConfig) 31 | self._configs = Factory.makeAppConfigs(appKeys, globalConfig, package.path) 32 | self._pid = tostring(process.getpid()) 33 | end 34 | 35 | function WorkerBootstrap:runapp(appRootPath) 36 | local appConfig = self._configs[appRootPath] 37 | local worker = Factory.create(appConfig, "WorkerInstance", nil, self._pid) 38 | return worker:run() 39 | end 40 | 41 | return WorkerBootstrap 42 | -------------------------------------------------------------------------------- /src/packages/gbc/WorkerInstanceBase.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local io_flush = io.flush 26 | local os_date = os.date 27 | local os_time = os.time 28 | local string_format = string.format 29 | local string_lower = string.lower 30 | local tostring = tostring 31 | local type = type 32 | 33 | local json = cc.import("#json") 34 | local Constants = cc.import(".Constants") 35 | 36 | local InstanceBase = cc.import(".InstanceBase") 37 | local WorkerInstanceBase = cc.class("WorkerInstanceBase", InstanceBase) 38 | 39 | function WorkerInstanceBase:ctor(config, args, tag) 40 | WorkerInstanceBase.super.ctor(self, config, Constants.WORKER_REQUEST_TYPE) 41 | self._tag = tag or "worker" 42 | end 43 | 44 | function WorkerInstanceBase:run() 45 | return self:runEventLoop() 46 | end 47 | 48 | function WorkerInstanceBase:runEventLoop() 49 | local jobWorkerRequests = self.config.app.jobWorkerRequests 50 | local jobs = self:getJobs({try = 3}) 51 | local bean = jobs:getBeanstalkd() 52 | local beanerrs = bean.ERRORS 53 | local appname = self.config.app.appName 54 | local tag = string_format("%s:%s", appname, self._tag) 55 | local running = true 56 | 57 | cc.printinfo("[%s] ready, waiting for job", tag) 58 | 59 | while running do 60 | while true do 61 | io_flush() 62 | 63 | jobWorkerRequests = jobWorkerRequests - 1 64 | if jobWorkerRequests < 0 then 65 | cc.printinfo("[%s] job worker is done", tag) 66 | running = false -- stop loop 67 | break 68 | end 69 | 70 | local job, err = jobs:getready() 71 | if not job then 72 | if err == beanerrs.TIMED_OUT then 73 | break -- wait next job 74 | end 75 | if err == beanerrs.DEADLINE_SOON then 76 | cc.printinfo("[%s] deadline soon", tag) 77 | break -- wait next job 78 | end 79 | 80 | cc.printwarn("[%s] reserve job failed, %s", tag, err) running = false -- stop loop 81 | break 82 | end 83 | 84 | if not job.id then 85 | cc.printinfo("[%s] get a invalid job", tag) 86 | break -- wait next job 87 | else 88 | cc.printinfo("[%s] get a job %s, action: %s", tag, job.id, job.action) 89 | end 90 | 91 | -- handle the job 92 | local actionName = job.action 93 | local _, res = xpcall(function() 94 | return self:runAction(actionName, job) 95 | end, function(err) 96 | if cc.DEBUG > cc.DEBUG_WARN then 97 | err = debug.traceback(err, 3) 98 | cc.printwarn(err) 99 | end 100 | end) 101 | if res ~= false then 102 | -- delete job 103 | local ok, err = jobs:delete(job.id) 104 | if not ok then 105 | cc.printwarn("[%s] delete job %s failed, %s", tag, job.id, err) 106 | else 107 | cc.printinfo("[%s] job %s done", tag, job.id) 108 | end 109 | end 110 | end -- wait next job 111 | end -- loop 112 | 113 | io_flush() 114 | return 1 115 | end 116 | 117 | return WorkerInstanceBase 118 | -------------------------------------------------------------------------------- /src/packages/gbc/gbc.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local _CUR = ... 26 | 27 | local _M = { 28 | VERSION = "0.8.0", 29 | 30 | Constants = cc.import(".Constants", _CUR), 31 | Factory = cc.import(".Factory", _CUR), 32 | 33 | ActionBase = cc.import(".ActionBase", _CUR), 34 | InstanceBase = cc.import(".InstanceBase", _CUR), 35 | 36 | CommandLineInstanceBase = cc.import(".CommandLineInstanceBase", _CUR), 37 | CommandLineBootstrap = cc.import(".CommandLineBootstrap", _CUR), 38 | 39 | WorkerInstanceBase = cc.import(".WorkerInstanceBase", _CUR), 40 | WorkerBootstrap = cc.import(".WorkerBootstrap", _CUR), 41 | 42 | Broadcast = cc.import(".Broadcast", _CUR), 43 | } 44 | 45 | if ngx then 46 | _M.HttpInstanceBase = cc.import(".HttpInstanceBase", _CUR) 47 | _M.WebSocketInstanceBase = cc.import(".WebSocketInstanceBase", _CUR) 48 | _M.NginxBootstrap = cc.import(".NginxBootstrap", _CUR) 49 | end 50 | 51 | return _M 52 | -------------------------------------------------------------------------------- /src/packages/jobs/jobs.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local json = cc.import("#json") 26 | 27 | local Jobs = cc.class("Jobs") 28 | 29 | Jobs.DEFAULT_DELAY = 1 30 | Jobs.DEFAULT_TTR = 10 31 | 32 | Jobs.DEFAULT_PRIORITY = 2048 33 | Jobs.NORMAL_PRIORITY = Jobs.DEFAULT_PRIORITY 34 | Jobs.HIGH_PRIORITY = 512 35 | Jobs.LOW_PRIORITY = 4096 36 | 37 | function Jobs:ctor(bean) 38 | self._bean = bean 39 | return 1 40 | end 41 | 42 | function Jobs:getBeanstalkd() 43 | return self._bean 44 | end 45 | 46 | function Jobs:add(job) 47 | local action = job.action 48 | local data = job.data 49 | local delay = job.delay or Jobs.DEFAULT_DELAY 50 | local pri = job.pri or Jobs.DEFAULT_PRIORITY 51 | local ttr = job.ttr or Jobs.DEFAULT_TTR 52 | 53 | local job = { 54 | action = action, 55 | data = data, 56 | delay = delay, 57 | pri = pri, 58 | ttr = ttr, 59 | } 60 | 61 | return self._bean:put(json.encode(job), pri, delay, ttr) 62 | end 63 | 64 | function Jobs:at(job) 65 | local now = os.time() 66 | local at = job.time 67 | if type(at) ~= "number" then 68 | at = now 69 | end 70 | local delay = at - now 71 | job.delay = delay 72 | job.time = nil 73 | return self:add(job) 74 | end 75 | 76 | function Jobs:get(id) 77 | local bean = self._bean 78 | 79 | local jobraw, err = bean:peek(id) 80 | if not jobraw then 81 | return nil, err 82 | end 83 | 84 | local job = json.decode(jobraw.data) 85 | if type(job) ~= "table" then 86 | self:delete(jobraw.id) 87 | return nil, string.format("invalid job %s", jobraw.id) 88 | end 89 | 90 | job.id = jobraw.id 91 | return job 92 | end 93 | 94 | function Jobs:getready(timeout) 95 | local bean = self._bean 96 | 97 | local jobraw, err = bean:reserve(timeout) 98 | if not jobraw then 99 | return nil, err 100 | end 101 | 102 | local id = jobraw.id 103 | local job = json.decode(jobraw.data) 104 | if type(job) ~= "table" or not job.action then 105 | bean:delete(id) 106 | return {id = nil} 107 | end 108 | 109 | job.id = id 110 | return job 111 | end 112 | 113 | function Jobs:delete(id) 114 | return self._bean:delete(id) 115 | end 116 | 117 | return Jobs 118 | 119 | -------------------------------------------------------------------------------- /src/packages/json/json.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local pcall = pcall 26 | local cjson = require("cjson") 27 | 28 | local cjson_encode = cjson.encode 29 | local cjson_decode = cjson.decode 30 | 31 | local _M = { 32 | null = cjson.null 33 | } 34 | 35 | function _M.encode(var) 36 | local ok, res = pcall(cjson_encode, var) 37 | if ok then return res end 38 | return nil, res -- res is error 39 | end 40 | 41 | function _M.decode(text) 42 | local ok, res = pcall(cjson_decode, text) 43 | if ok then return res end 44 | return nil, res -- res is error 45 | end 46 | 47 | return _M 48 | -------------------------------------------------------------------------------- /src/packages/luamd5/luamd5.lua: -------------------------------------------------------------------------------- 1 | 2 | local _md5 = { 3 | _VERSION = "md5.lua 1.0.2", 4 | _DESCRIPTION = "MD5 computation in Lua (5.1-3, LuaJIT)", 5 | _URL = "https://github.com/kikito/md5.lua", 6 | _LICENSE = [[ 7 | MIT LICENSE 8 | 9 | Copyright (c) 2013 Enrique García Cota + Adam Baldwin + hanzao + Equi 4 Software 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a 12 | copy of this software and associated documentation files (the 13 | "Software"), to deal in the Software without restriction, including 14 | without limitation the rights to use, copy, modify, merge, publish, 15 | distribute, sublicense, and/or sell copies of the Software, and to 16 | permit persons to whom the Software is furnished to do so, subject to 17 | the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included 20 | in all copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 23 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 25 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 26 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 27 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 28 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | ]] 30 | } 31 | 32 | 33 | local char, byte, format, rep, sub = 34 | string.char, string.byte, string.format, string.rep, string.sub 35 | local bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift 36 | 37 | local ok, bit = pcall(require, 'bit') 38 | bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift = bit.bor, bit.band, bit.bnot, bit.bxor, bit.rshift, bit.lshift 39 | 40 | -- convert little-endian 32-bit int to a 4-char string 41 | local function lei2str(i) 42 | local f=function (s) return char( bit_and( bit_rshift(i, s), 255)) end 43 | return f(0)..f(8)..f(16)..f(24) 44 | end 45 | 46 | -- convert raw string to big-endian int 47 | local function str2bei(s) 48 | local v=0 49 | for i=1, #s do 50 | v = v * 256 + byte(s, i) 51 | end 52 | return v 53 | end 54 | 55 | -- convert raw string to little-endian int 56 | local function str2lei(s) 57 | local v=0 58 | for i = #s,1,-1 do 59 | v = v*256 + byte(s, i) 60 | end 61 | return v 62 | end 63 | 64 | -- cut up a string in little-endian ints of given size 65 | local function cut_le_str(s,...) 66 | local o, r = 1, {} 67 | local args = {...} 68 | for i=1, #args do 69 | table.insert(r, str2lei(sub(s, o, o + args[i] - 1))) 70 | o = o + args[i] 71 | end 72 | return r 73 | end 74 | 75 | local swap = function (w) return str2bei(lei2str(w)) end 76 | 77 | local function hex2binaryaux(hexval) 78 | return char(tonumber(hexval, 16)) 79 | end 80 | 81 | local function hex2binary(hex) 82 | local result, _ = hex:gsub('..', hex2binaryaux) 83 | return result 84 | end 85 | 86 | -- An MD5 mplementation in Lua, requires bitlib (hacked to use LuaBit from above, ugh) 87 | -- 10/02/2001 jcw@equi4.com 88 | 89 | local CONSTS = { 90 | 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 91 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 92 | 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 93 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 94 | 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 95 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 96 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 97 | 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 98 | 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 99 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 100 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 101 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 102 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 103 | 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 104 | 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 105 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, 106 | 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 107 | } 108 | 109 | local f=function (x,y,z) return bit_or(bit_and(x,y),bit_and(-x-1,z)) end 110 | local g=function (x,y,z) return bit_or(bit_and(x,z),bit_and(y,-z-1)) end 111 | local h=function (x,y,z) return bit_xor(x,bit_xor(y,z)) end 112 | local i=function (x,y,z) return bit_xor(y,bit_or(x,-z-1)) end 113 | local z=function (f,a,b,c,d,x,s,ac) 114 | a=bit_and(a+f(b,c,d)+x+ac,0xFFFFFFFF) 115 | -- be *very* careful that left shift does not cause rounding! 116 | return bit_or(bit_lshift(bit_and(a,bit_rshift(0xFFFFFFFF,s)),s),bit_rshift(a,32-s))+b 117 | end 118 | 119 | local function transform(A,B,C,D,X) 120 | local a,b,c,d=A,B,C,D 121 | local t=CONSTS 122 | 123 | a=z(f,a,b,c,d,X[ 0], 7,t[ 1]) 124 | d=z(f,d,a,b,c,X[ 1],12,t[ 2]) 125 | c=z(f,c,d,a,b,X[ 2],17,t[ 3]) 126 | b=z(f,b,c,d,a,X[ 3],22,t[ 4]) 127 | a=z(f,a,b,c,d,X[ 4], 7,t[ 5]) 128 | d=z(f,d,a,b,c,X[ 5],12,t[ 6]) 129 | c=z(f,c,d,a,b,X[ 6],17,t[ 7]) 130 | b=z(f,b,c,d,a,X[ 7],22,t[ 8]) 131 | a=z(f,a,b,c,d,X[ 8], 7,t[ 9]) 132 | d=z(f,d,a,b,c,X[ 9],12,t[10]) 133 | c=z(f,c,d,a,b,X[10],17,t[11]) 134 | b=z(f,b,c,d,a,X[11],22,t[12]) 135 | a=z(f,a,b,c,d,X[12], 7,t[13]) 136 | d=z(f,d,a,b,c,X[13],12,t[14]) 137 | c=z(f,c,d,a,b,X[14],17,t[15]) 138 | b=z(f,b,c,d,a,X[15],22,t[16]) 139 | 140 | a=z(g,a,b,c,d,X[ 1], 5,t[17]) 141 | d=z(g,d,a,b,c,X[ 6], 9,t[18]) 142 | c=z(g,c,d,a,b,X[11],14,t[19]) 143 | b=z(g,b,c,d,a,X[ 0],20,t[20]) 144 | a=z(g,a,b,c,d,X[ 5], 5,t[21]) 145 | d=z(g,d,a,b,c,X[10], 9,t[22]) 146 | c=z(g,c,d,a,b,X[15],14,t[23]) 147 | b=z(g,b,c,d,a,X[ 4],20,t[24]) 148 | a=z(g,a,b,c,d,X[ 9], 5,t[25]) 149 | d=z(g,d,a,b,c,X[14], 9,t[26]) 150 | c=z(g,c,d,a,b,X[ 3],14,t[27]) 151 | b=z(g,b,c,d,a,X[ 8],20,t[28]) 152 | a=z(g,a,b,c,d,X[13], 5,t[29]) 153 | d=z(g,d,a,b,c,X[ 2], 9,t[30]) 154 | c=z(g,c,d,a,b,X[ 7],14,t[31]) 155 | b=z(g,b,c,d,a,X[12],20,t[32]) 156 | 157 | a=z(h,a,b,c,d,X[ 5], 4,t[33]) 158 | d=z(h,d,a,b,c,X[ 8],11,t[34]) 159 | c=z(h,c,d,a,b,X[11],16,t[35]) 160 | b=z(h,b,c,d,a,X[14],23,t[36]) 161 | a=z(h,a,b,c,d,X[ 1], 4,t[37]) 162 | d=z(h,d,a,b,c,X[ 4],11,t[38]) 163 | c=z(h,c,d,a,b,X[ 7],16,t[39]) 164 | b=z(h,b,c,d,a,X[10],23,t[40]) 165 | a=z(h,a,b,c,d,X[13], 4,t[41]) 166 | d=z(h,d,a,b,c,X[ 0],11,t[42]) 167 | c=z(h,c,d,a,b,X[ 3],16,t[43]) 168 | b=z(h,b,c,d,a,X[ 6],23,t[44]) 169 | a=z(h,a,b,c,d,X[ 9], 4,t[45]) 170 | d=z(h,d,a,b,c,X[12],11,t[46]) 171 | c=z(h,c,d,a,b,X[15],16,t[47]) 172 | b=z(h,b,c,d,a,X[ 2],23,t[48]) 173 | 174 | a=z(i,a,b,c,d,X[ 0], 6,t[49]) 175 | d=z(i,d,a,b,c,X[ 7],10,t[50]) 176 | c=z(i,c,d,a,b,X[14],15,t[51]) 177 | b=z(i,b,c,d,a,X[ 5],21,t[52]) 178 | a=z(i,a,b,c,d,X[12], 6,t[53]) 179 | d=z(i,d,a,b,c,X[ 3],10,t[54]) 180 | c=z(i,c,d,a,b,X[10],15,t[55]) 181 | b=z(i,b,c,d,a,X[ 1],21,t[56]) 182 | a=z(i,a,b,c,d,X[ 8], 6,t[57]) 183 | d=z(i,d,a,b,c,X[15],10,t[58]) 184 | c=z(i,c,d,a,b,X[ 6],15,t[59]) 185 | b=z(i,b,c,d,a,X[13],21,t[60]) 186 | a=z(i,a,b,c,d,X[ 4], 6,t[61]) 187 | d=z(i,d,a,b,c,X[11],10,t[62]) 188 | c=z(i,c,d,a,b,X[ 2],15,t[63]) 189 | b=z(i,b,c,d,a,X[ 9],21,t[64]) 190 | 191 | return A+a,B+b,C+c,D+d 192 | end 193 | 194 | ---------------------------------------------------------------- 195 | 196 | function _md5.sumhexa(s) 197 | local msgLen = #s 198 | local padLen = 56 - msgLen % 64 199 | 200 | if msgLen % 64 > 56 then padLen = padLen + 64 end 201 | 202 | if padLen == 0 then padLen = 64 end 203 | 204 | s = s .. char(128) .. rep(char(0),padLen-1) .. lei2str(8*msgLen) .. lei2str(0) 205 | 206 | assert(#s % 64 == 0) 207 | 208 | local t = CONSTS 209 | local a,b,c,d = t[65],t[66],t[67],t[68] 210 | 211 | for i=1,#s,64 do 212 | local X = cut_le_str(sub(s,i,i+63),4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4) 213 | assert(#X == 16) 214 | X[0] = table.remove(X,1) -- zero based! 215 | a,b,c,d = transform(a,b,c,d,X) 216 | end 217 | 218 | return format("%08x%08x%08x%08x",swap(a),swap(b),swap(c),swap(d)) 219 | end 220 | 221 | function _md5.sum(s) 222 | return hex2binary(md5.sumhexa(s)) 223 | end 224 | 225 | return _md5 226 | -------------------------------------------------------------------------------- /src/packages/redis/NginxRedisLoop.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local semaphore = require "ngx.semaphore" 26 | 27 | local ipairs = ipairs 28 | local ngx_thread_kill = ngx.thread.kill 29 | local ngx_thread_spawn = ngx.thread.spawn 30 | local string_byte = string.byte 31 | local string_split = string.split 32 | local string_sub = string.sub 33 | local table_concat = table.concat 34 | local table_remove = table.remove 35 | local tostring = tostring 36 | local unpack = unpack 37 | 38 | local NginxRedisLoop = cc.class("NginxRedisLoop") 39 | 40 | local _loop, _cleanup, _onmessage, _onerror 41 | 42 | function NginxRedisLoop:ctor(redis, subredis, id) 43 | self._redis = redis 44 | self._subredis = subredis 45 | self._subredis:setTimeout(5) -- check client connect abort quickly 46 | self._sema = semaphore.new() 47 | 48 | id = id or "" 49 | self._id = id .. "_" .. string_sub(tostring(self), 10) 50 | end 51 | 52 | function NginxRedisLoop:start(onmessage, cmdchannel, ...) 53 | if not self._subredis then 54 | return nil, "not initialized" 55 | end 56 | local onmessage = onmessage or _onmessage 57 | local onerror = _onerror 58 | self._cmdchannel = cmdchannel 59 | 60 | local res, err = self._subredis:subscribe(cmdchannel, ...) 61 | if not res then 62 | return nil, err 63 | end 64 | cc.printinfo("[RedisSub:%s] %s", self._id, table_concat(res, " ")) 65 | 66 | self._thread = ngx_thread_spawn(_loop, self, onmessage, onerror) 67 | return 1 68 | end 69 | 70 | function NginxRedisLoop:stop() 71 | self._redis:publish(self._cmdchannel, "!STOP") 72 | self._sema:wait(1) 73 | _cleanup(self) 74 | end 75 | 76 | -- add methods 77 | 78 | local _COMMANDS = { 79 | "subscribe", "unsubscribe", 80 | "psubscribe", "punsubscribe", 81 | } 82 | 83 | for _, cmd in ipairs(_COMMANDS) do 84 | NginxRedisLoop[cmd] = function(self, ...) 85 | local args = {"!REDIS", cmd} 86 | for _, arg in ipairs({...}) do 87 | args[#args + 1] = tostring(arg) 88 | end 89 | -- cc.printinfo("[RedisSub:%s] CMD: %s", self._id, table_concat(args, " ")) 90 | local res, err = self._redis:publish(self._cmdchannel, table_concat(args, " ")) 91 | -- wait for command completed 92 | self._sema:wait(1) 93 | return res, err 94 | end 95 | end 96 | 97 | -- private 98 | 99 | local _skipmsgtypes = { 100 | subscribe = true, 101 | unsubscribe = true, 102 | psubscribe = true, 103 | punsubscribe = true, 104 | } 105 | 106 | _loop = function(self, onmessage, onerror) 107 | local cmdchannel = self._cmdchannel 108 | local subredis = self._subredis 109 | local id = self._id 110 | local running = true 111 | local sema = self._sema 112 | local DEBUG = cc.DEBUG > cc.DEBUG_WARN 113 | 114 | local msgtype, channel, msg, pchannel 115 | 116 | cc.printinfo("[RedisSub:%s] ", id) 117 | 118 | while running do 119 | -- cc.printinfo("[RedisSub:%s] ", id) 120 | local res, err = subredis:readReply() 121 | if not res then 122 | if err ~= "timeout" then 123 | onerror(err, id) 124 | running = false -- stop loop 125 | break 126 | end 127 | end 128 | 129 | while res do -- process message 130 | -- cc.printinfo("[RedisSub:%s] %s", id, table_concat(res, " ")) 131 | 132 | msgtype = res[1] 133 | channel = res[2] 134 | msg = res[3] 135 | 136 | if _skipmsgtypes[msgtype] then 137 | -- cc.printinfo("[RedisSub:%s] %s", id, table_concat(res, " ")) 138 | break -- read reply 139 | end 140 | 141 | if channel ~= cmdchannel then 142 | -- general message 143 | if msgtype == "message" then 144 | -- msgtype, channel, msg 145 | onmessage(channel, msg, nil, id) 146 | elseif msgtype == "pmessage" then 147 | pchannel = res[2] 148 | channel = res[3] 149 | msg = res[4] 150 | onmessage(channel, msg, pchannel, id) 151 | else 152 | cc.printwarn("[RedisSub:%s] invalid message, %s", id, table_concat(res, " ")) 153 | end 154 | break -- read reply 155 | end 156 | 157 | if string_byte(msg) ~= 33 --[[ ! ]] then 158 | -- forward control message 159 | onmessage(channel, msg, nil, id) 160 | break -- read reply 161 | end 162 | 163 | -- control message 164 | local parts = string_split(msg, " ") 165 | local cmd = parts[1] 166 | if cmd == "!STOP" then 167 | running = false -- stop loop 168 | break 169 | elseif cmd == "!REDIS" then 170 | table_remove(parts, 1) 171 | res, err = subredis:doCommand(unpack(parts)) 172 | if not res then 173 | cc.printwarn("[RedisSub:%s] redis failed, %s", id, err) 174 | break -- read reply 175 | else 176 | -- cc.printinfo("[RedisSub:%s] %s", id, table_concat(parts, " ")) 177 | -- cc.printinfo("[RedisSub:%s] %s", id, table_concat(res, " ")) 178 | sema:post(1) -- release lock 179 | end 180 | else 181 | -- unknown control message 182 | cc.printwarn("[RedisSub:%s] unknown control message, %s", id, msg) 183 | break -- read reply 184 | end 185 | 186 | end -- read reply 187 | end -- loop 188 | 189 | cc.printinfo("[RedisSub:%s] ", id) 190 | 191 | subredis:unsubscribe() 192 | subredis:setKeepAlive() 193 | 194 | sema:post(1) 195 | end 196 | 197 | _cleanup = function(self) 198 | ngx_thread_kill(self._thread) 199 | self._thread = nil 200 | self._redis = nil 201 | self._subredis = nil 202 | self._id = nil 203 | end 204 | 205 | _onmessage = function(channel, msg, pchannel, id) 206 | if pchannel then 207 | cc.printinfo("[RedisSub:%s] <%s> <%s> %s", id, pchannel, channel, msg) 208 | else 209 | cc.printinfo("[RedisSub:%s] <%s> %s", id, channel, msg) 210 | end 211 | end 212 | 213 | _onerror = function(err, id) 214 | cc.printwarn("[RedisSub:%s] onerror: %s", id, err) 215 | end 216 | 217 | return NginxRedisLoop 218 | -------------------------------------------------------------------------------- /src/packages/session/session.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local checkint = cc.checkint 26 | local checktable = cc.checktable 27 | local clone = clone 28 | local string_format = string.format 29 | local tostring = tostring 30 | local type = type 31 | local md5 32 | if ngx then 33 | md5 = ngx.md5 34 | else 35 | local luamd5 = cc.import("#luamd5") 36 | md5 = luamd5.sumhexa 37 | end 38 | 39 | local json = cc.import("#json") 40 | 41 | local Session = cc.class("Session") 42 | 43 | local _DEFAULT_EXPIRED = 60 * 5 -- 5m 44 | local _DEFAULT_SID_KEY_PREFIX = "_SID_" 45 | local _DEFAULT_SECRET = "1b876ea6" 46 | 47 | local _gensid 48 | 49 | function Session:ctor(redis, config) 50 | config = config or {} 51 | self._expired = config.expired or _DEFAULT_EXPIRED 52 | self._prefix = config.prefix or _DEFAULT_SID_KEY_PREFIX 53 | self._secret = config.secret or _DEFAULT_SECRET 54 | self._redis = redis 55 | end 56 | 57 | function Session:start(sid) 58 | local create = sid == nil 59 | if type(sid) == "nil" then 60 | sid = _gensid(self._secret) 61 | elseif type(sid) ~= "string" or sid == "" then 62 | cc.throw("[Session] invalid sid '%s'", tostring(sid)) 63 | end 64 | 65 | local key = self._prefix .. sid 66 | 67 | if create then 68 | self._values = {} 69 | self._sid = sid 70 | self._key = key 71 | else 72 | local redis = self._redis 73 | local res, err = self._redis:get(key) 74 | if not res then 75 | return false, err 76 | end 77 | if res == redis.null then 78 | return false, string_format("not found session '%s'", sid) 79 | end 80 | 81 | self._values = checktable(json.decode(res)) 82 | self._sid = sid 83 | self._key = key 84 | self:setKeepAlive() 85 | end 86 | 87 | return true 88 | end 89 | 90 | function Session:getSid() 91 | return self._sid 92 | end 93 | 94 | function Session:getExpired() 95 | return self._expired 96 | end 97 | 98 | function Session:get(key) 99 | if not self._values then 100 | cc.throw("[Session] get key '%s' failed, not initialized", key) 101 | end 102 | 103 | if type(key) ~= "string" or key == "" then 104 | cc.throw("[Session] invalid get key '%s'", tostring(key)) 105 | end 106 | 107 | return self._values[key] 108 | end 109 | 110 | function Session:set(key, value) 111 | if not self._values then 112 | cc.throw("[Session] set key '%s' failed, not initialized", key) 113 | end 114 | 115 | if type(key) ~= "string" or key == "" then 116 | cc.throw("[Session] invalid set key '%s'", tostring(key)) 117 | end 118 | 119 | self._values[key] = value 120 | end 121 | 122 | function Session:save() 123 | if not self._values then 124 | cc.throw("[Session] save failed, not initialized") 125 | end 126 | 127 | local jsonstr = json.encode(self._values) 128 | if type(jsonstr) ~= "string" then 129 | return false, "serializing failed" 130 | end 131 | 132 | local ok, err = self._redis:set(self._key, jsonstr, "EX", self._expired) 133 | if not ok then 134 | return false, err 135 | end 136 | 137 | return true 138 | end 139 | 140 | function Session:setKeepAlive(expired) 141 | if not self._values then 142 | cc.throw("[Session] set keep alive failed, not initialized") 143 | end 144 | 145 | if expired then 146 | self._expired = expired 147 | end 148 | local ok, err = self._redis:expire(self._key, self._expired) 149 | if not ok then 150 | return false, err 151 | end 152 | 153 | return true 154 | end 155 | 156 | function Session:isAlive() 157 | if not self._values then 158 | cc.throw("[Session] check alive failed, not initialized") 159 | end 160 | 161 | local res, err = self._redis:exists(self._key) 162 | if not res then 163 | return false, err 164 | end 165 | 166 | if tostring(res) == "1" then 167 | return true 168 | end 169 | 170 | return false, string_format("not found session '%s'", self._sid) 171 | end 172 | 173 | function Session:destroy() 174 | if not self._values then 175 | cc.throw("[Session] destroy failed, not initialized") 176 | end 177 | 178 | local ok, err = self._redis:del(self._key) 179 | self._values = nil 180 | self._redis = nil 181 | self._sid = nil 182 | self._key = nil 183 | 184 | if not ok then 185 | return false, err 186 | end 187 | return true 188 | end 189 | 190 | -- private 191 | 192 | _gensid = function(secret) 193 | math.newrandomseed() 194 | 195 | local random = math.random() * 100000000000000 196 | local now 197 | local addr 198 | if ngx then 199 | addr = ngx.var.remote_addr 200 | now = ngx.now() 201 | else 202 | addr = "127.0.0.1" 203 | now = os.time() 204 | end 205 | 206 | local mask = string.format("%0.5f|%0.10f|%s", now, random, secret) 207 | local origin = string.format("%s|%s", addr, mask) 208 | return md5(origin) 209 | end 210 | 211 | return Session 212 | -------------------------------------------------------------------------------- /src/packages/tests/Check.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local rawget = rawget 26 | local rawequal = rawequal 27 | local string_format = string.format 28 | local _empty 29 | local _equals 30 | local _contains, _containsintable 31 | local _dumpresult 32 | local _dumpresultarr 33 | local _formatmsg 34 | 35 | local _M = {} 36 | 37 | -- nil, "", {} is empty 38 | function _M.empty(v, msg) 39 | if _empty(v) then return end 40 | cc.throw("expected is empty, actual is '%s'%s", tostring(v), _formatmsg(msg)) 41 | end 42 | 43 | function _M.notEmpty(v, msg) 44 | if not _empty(v) then return end 45 | cc.throw("expected is not empty, actual is '%s'%s", tostring(v), _formatmsg(msg)) 46 | end 47 | 48 | function _M.isTrue(v, msg) 49 | if v == true then return end 50 | cc.throw("expected is true, actual is '%s'%s", tostring(v), _formatmsg(msg)) 51 | end 52 | 53 | function _M.isFalse(v, msg) 54 | if v == false then return end 55 | cc.throw("expected is false, actual is '%s'%s", tostring(v), _formatmsg(msg)) 56 | end 57 | 58 | function _M.isNil(v, msg) 59 | if type(v) == "nil" then return end 60 | cc.throw("expected is nil, actual is '%s'%s", type(v), _formatmsg(msg)) 61 | end 62 | 63 | function _M.isFunction(v, msg) 64 | if type(v) == "function" then return end 65 | cc.throw("expected is function, actual is '%s'%s", type(v), _formatmsg(msg)) 66 | end 67 | 68 | function _M.isTable(v, msg) 69 | if type(v) == "table" then return end 70 | cc.throw("expected is table, actual is '%s'%s", type(v), _formatmsg(msg)) 71 | end 72 | 73 | function _M.isInt(v, msg) 74 | if type(v) == "number" and math.floor(v) == v then return end 75 | cc.throw("expected is integer, actual is '%s'%s", tostring(v), _formatmsg(msg)) 76 | end 77 | 78 | function _M.isPosInt(v, msg) 79 | if type(v) == "number" and math.floor(v) == v and v >= 0 then return end 80 | cc.throw("expected is positive integer, actual is '%s'%s", tostring(v), _formatmsg(msg)) 81 | end 82 | 83 | function _M.isString(v, msg) 84 | if type(v) == "string" then return end 85 | cc.throw("expected is string, actual is '%s'%s", tostring(v), _formatmsg(msg)) 86 | end 87 | 88 | function _M.greaterThan(actual, expected, msg) 89 | if type(actual) == "number" and type(expected) == "number" and actual > expected then return end 90 | cc.throw("expected is '%s' > '%s'%s", tostring(actual), tostring(expected), _formatmsg(msg)) 91 | end 92 | 93 | function _M.equals(actual, expected, msg) 94 | if _equals(actual, expected) then return end 95 | local msgs = { 96 | "should be equals" .. _formatmsg(msg), 97 | _dumpresult(actual, "actual"), 98 | _dumpresult(expected, "expected"), 99 | } 100 | cc.throw(table.concat(msgs, "\n")) 101 | end 102 | 103 | function _M.notEquals(actual, expected, msg) 104 | if not _equals(actual, expected) then return end 105 | local msgs = { 106 | "should be not equals" .. _formatmsg(msg), 107 | _dumpresult(actual, "actual"), 108 | _dumpresult(expected, "expected"), 109 | } 110 | cc.throw(table.concat(msgs, "\n")) 111 | end 112 | 113 | function _M.contains(actual, expected, msg) 114 | if _contains(actual, expected) then return end 115 | local msgs = { 116 | string_format("expected contains '%s'%s", tostring(expected), _formatmsg(msg)), 117 | _dumpresult(actual, "actual"), 118 | _dumpresult(expected, "expected"), 119 | } 120 | cc.throw(table.concat(msgs, "\n")) 121 | end 122 | 123 | function _M.notContains(actual, expected, msg) 124 | if not _contains(actual, expected) then return end 125 | local msgs = { 126 | string_format("expected not contains '%s'%s", tostring(needle), _formatmsg(msg)), 127 | _dumpresult(actual, "actual"), 128 | _dumpresult(expected, "expected"), 129 | } 130 | cc.throw(table.concat(msgs, "\n")) 131 | end 132 | 133 | -- private 134 | 135 | _empty = function(v) 136 | local t = type(v) 137 | local test = true 138 | while true do 139 | if t == "nil" then break end 140 | if t == "string" and v == "" then break end 141 | if t ~= "table" then 142 | test = false 143 | break 144 | end 145 | 146 | for k, v in pairs(v) do 147 | test = false 148 | break 149 | end 150 | 151 | break 152 | end 153 | 154 | return test 155 | end 156 | 157 | _equals = function(actual, expected) 158 | local at = type(actual) 159 | local et = type(expected) 160 | if at ~= et then return false end 161 | 162 | if at == "table" then 163 | local akeys = {} 164 | -- check all values in actual exists in expected 165 | for k, v in pairs(actual) do 166 | akeys[k] = true 167 | if not _equals(v, rawget(expected, k)) then return false end 168 | end 169 | -- check expected not have more keys 170 | for k, v in pairs(expected) do 171 | if akeys[k] ~= true then return false end 172 | end 173 | return true 174 | elseif at == "number" then 175 | return tostring(actual) == tostring(expected) 176 | else 177 | return actual == expected 178 | end 179 | end 180 | 181 | _contains = function(actual, expected) 182 | if type(actual) == "table" then 183 | return _containsintable(actual, expected) 184 | end 185 | return string.find(tostring(actual), tostring(expected), 1, true) 186 | end 187 | 188 | _containsintable = function(arr, needle) 189 | for _, v in pairs(arr) do 190 | if needle == v then return true end 191 | end 192 | return false 193 | end 194 | 195 | _dumpresult = function(value, label) 196 | return table.concat(_dumpresultarr(value, label), "\n") 197 | end 198 | 199 | _dumpresultarr = function(value, label) 200 | local result = {} 201 | local first = true 202 | cc.dump(value, label, 99, function(s) 203 | if first then 204 | first = false 205 | else 206 | result[#result + 1] = s 207 | end 208 | end) 209 | return result 210 | end 211 | 212 | _formatmsg = function(msg) 213 | if msg then 214 | return ", " .. tostring(msg) 215 | else 216 | return "" 217 | end 218 | end 219 | 220 | return _M 221 | -------------------------------------------------------------------------------- /src/packages/tests/TestCase.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local string_sub = string.sub 26 | 27 | local gbc = cc.import("#gbc") 28 | 29 | local TestCase = cc.class("TestCase", gbc.ActionBase) 30 | 31 | TestCase.ACCEPTED_REQUEST_TYPE = {"http", "cli"} 32 | 33 | function TestCase:init() 34 | local mt = getmetatable(self) 35 | for name, method in pairs(mt.__index) do 36 | if type(method) == "function" and string_sub(name, -6) == "Action" then 37 | self[name] = function(...) 38 | self:setup() 39 | local res = {method(...)} 40 | self:teardown() 41 | return unpack(res) 42 | end 43 | end 44 | end 45 | 46 | math.newrandomseed() 47 | end 48 | 49 | function TestCase:setup() 50 | end 51 | 52 | function TestCase:teardown() 53 | end 54 | 55 | return TestCase 56 | -------------------------------------------------------------------------------- /src/packages/tests/tests.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Copyright (c) 2015 gameboxcloud.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | ]] 24 | 25 | local _M = {} 26 | 27 | _M.TestCase = cc.import(".TestCase") 28 | _M.Check = cc.import(".Check") 29 | 30 | return _M 31 | -------------------------------------------------------------------------------- /start_server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function showHelp() 4 | { 5 | echo "Usage: [sudo] ./start_server.sh [OPTIONS]" 6 | echo "Options:" 7 | echo -e "\t -v , --version \t\t show GameBox Cloud Core version" 8 | echo -e "\t -h , --help \t\t show this help" 9 | echo -e "\t --debug \t\t start GameBox Cloud Core in debug mode." 10 | echo "In default, GameBox Cloud Core will start in release mode, or else it will start in debug mode when you specified \"--debug\"." 11 | echo "" 12 | } 13 | 14 | ROOT_DIR=$(cd "$(dirname $0)" && pwd) 15 | source $ROOT_DIR/bin/shell_func.sh 16 | 17 | if [ $? -ne 0 ] ; then echo "Terminating..." >&2; exit 1; fi 18 | 19 | if [ $OS_TYPE == "MACOS" ]; then 20 | ARGS=$($ROOT_DIR/bin/getopt_long "$@") 21 | else 22 | ARGS=$(getopt -o vh --long debug,version,help -n 'Start GameBox Cloud Core' -- "$@") 23 | fi 24 | 25 | eval set -- "$ARGS" 26 | 27 | declare -i DEBUG=0 28 | 29 | while true ; do 30 | case "$1" in 31 | --debug) 32 | DEBUG=1 33 | shift 34 | ;; 35 | 36 | -v|--version) 37 | echo $VERSION 38 | echo "" 39 | exit 0 40 | ;; 41 | 42 | -h|--help) 43 | showHelp; 44 | echo "" 45 | exit 0 46 | ;; 47 | 48 | --) shift; break ;; 49 | 50 | *) 51 | echo "invalid option. $1" 52 | exit 1 53 | ;; 54 | esac 55 | done 56 | 57 | echo -e "\033[33mStart GameBox Cloud Core $VERSION\033[0m" 58 | echo "" 59 | 60 | updateConfigs 61 | 62 | startSupervisord 63 | sleep 3s 64 | 65 | checkStatus 66 | 67 | -------------------------------------------------------------------------------- /stop_server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ROOT_DIR=$(cd "$(dirname $0)" && pwd) 4 | source $ROOT_DIR/bin/shell_func.sh 5 | 6 | stopSupervisord 7 | -------------------------------------------------------------------------------- /vagrant-support/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROJECT_ROOT_DIR=/vagrant 4 | BOOTSTRAP_DIR=/vagrant/vagrant-bootstrap 5 | 6 | function setup() 7 | { 8 | # setup packages 9 | cd /etc/apt/ 10 | cp sources.list sources.list.origin 11 | cat sources.list.origin | sed s/archive.ubuntu.com/mirrors.aliyun.com/ > sources.list 12 | 13 | debconf-set-selections <<< 'mysql-server mysql-server/root_password password gamebox' 14 | debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password gamebox' 15 | 16 | apt-get update -y 17 | apt-get upgrade -y 18 | apt-get install -y mysql-server mysql-client python-minimal 19 | 20 | # fix locale warnings 21 | apt-get install -y language-pack-en 22 | echo "" >> /home/vagrant/.profile 23 | echo "export LC_CTYPE=\"en_US.UTF-8\"" >> /home/vagrant/.profile 24 | echo "export LANG=\"en_US.UTF-8\"" >> /home/vagrant/.profile 25 | chown vagrant:vagrant /home/vagrant/.profile 26 | 27 | export LC_CTYPE="en_US.UTF-8" 28 | export LANG="en_US.UTF-8" 29 | 30 | cd /vagrant/ 31 | ./make.sh --prefix=/opt/gbc-core 32 | 33 | rm -fr /opt/gbc-core/apps 34 | rm -fr /opt/gbc-core/conf 35 | rm -fr /opt/gbc-core/src 36 | 37 | rm -f /opt/gbc-core/start_server 38 | rm -f /opt/gbc-core/stop_server 39 | rm -f /opt/gbc-core/check_server 40 | rm -f /opt/gbc-core/restart_server 41 | 42 | rm -f /opt/gbc-core/bin/start_worker.lua 43 | rm -f /opt/gbc-core/bin/shell_func.sh 44 | rm -f /opt/gbc-core/bin/shell_func.lua 45 | 46 | ln -s /vagrant/apps /opt/gbc-core/apps 47 | ln -s /vagrant/conf /opt/gbc-core/conf 48 | ln -s /vagrant/src /opt/gbc-core/src 49 | 50 | ln -s /vagrant/start_server /opt/gbc-core/start_server 51 | ln -s /vagrant/stop_server /opt/gbc-core/stop_server 52 | ln -s /vagrant/check_server /opt/gbc-core/check_server 53 | ln -s /vagrant/restart_server /opt/gbc-core/restart_server 54 | 55 | ln -s /vagrant/bin/start_worker.lua /opt/gbc-core/bin/start_worker.lua 56 | ln -s /vagrant/bin/shell_func.sh /opt/gbc-core/bin/shell_func.sh 57 | ln -s /vagrant/bin/shell_func.lua /opt/gbc-core/bin/shell_func.lua 58 | 59 | echo "" 60 | ls -lh /opt/gbc-core 61 | 62 | echo "" 63 | echo "INSTALL COMPLETED." 64 | echo "" 65 | echo "" 66 | } 67 | 68 | if [ ! -f /opt/gbc-core/start_server ]; then 69 | setup 70 | fi 71 | 72 | # done 73 | /opt/gbc-core/start_server --debug 74 | 75 | echo "" 76 | echo "" 77 | echo ALL DONE. please use browser open http://localhost:8088/ 78 | echo "" 79 | 80 | --------------------------------------------------------------------------------