应用程序配置,如何配置Pomelo框架?
Pomelo可以配置各个组件的选项,加载配置文件,开启Pomelo的特性等,这些配置都是在game-server/app.js
文件中进行的。实际上在Pomelo的应用中有两个app.js
,一个是在game-server
目录下,一个是在web-server
目录下。game-server
下的app.js
是整个游戏服务器的入口和配置点。web-server
下的app.js
是Web服务器入口。
应用程序配置
app.js
是运行Pomelo项目的入口,在app.js
文件中首先会创建一个app
实例,这个app
作为整个框架的配置上下文来使用,用户可以通过这个上下文,设置一些全局变量,加载配置信息等操作。
$ vim game-server/app.js
//加载pomelo
let pomelo = require("pomelo");
//创建app实例
let app = pomelo.createApp();
//通过app这个上下文对框架的配置以及一些初始化操作
app.configure(<env>, <serverType>, function(){});
app.configure(...);
app.set(...);
app.route(...);
//启动应用
app.start();
configure
使用app.configure
调用来配置
服务器的配置主要由configure()
方法完成,完整的app.configure
配置参数格式:
app.configure([env], [serverType], [function]);
参数 | 描述 |
---|---|
env | 运行环境,可设置为development 、production 、development|production 。 |
serverType | 服务器类型,设置后只会对当前参数类型服务器做初始化,不设置则对所有服务器执行初始化的function 。比如gate 、connector 、chat ... |
function | 具体的初始化操作,内部可以些任何对框架的配置操作逻辑。 |
Application.configure = function (env, type, fn) {
var args = [].slice.call(arguments);
fn = args.pop();
env = type = Constants.RESERVED.ALL;
if(args.length > 0) {
env = args[0];
}
if(args.length > 1) {
type = args[1];
}
if (env === Constants.RESERVED.ALL || contains(this.settings.env, env)) {
if (type === Constants.RESERVED.ALL || contains(this.settings.serverType, type)) {
fn.call(this);
}
}
return this;
};
更多地可以在configure
中针对不同的服务器、不同的环境,对框架进行不同的配置。这些配置包括设置一个上下文变量供应用使用,开启一些功能选项,配置加载一个自定义的组件component
,针对不同的服务器,配置过滤器filter
等配置操作。
loadConfig
例如:全局配置MySQL参数
$ vim game-server/config/mysql.json
{
"development":
{
"host": "127.0.0.1",
"port": "3306",
"username": "root",
"password": "root",
"database": "pomelo"
}
}
加载配置文件,用户通过loadConfig()
加载配置文件后,加载后文件中的参数将会直接挂载到app
对象上,可直接通过app
对象访问具体的配置参数。
$ vim game-server/app.js
const path = require('path');
//全局配置
app.configure('production|development', function(){
//加载MySQL数据库
app.loadConfig("mysql", path.join(app.getBase(), "config/mysql.json"));
const host = app.get("mysql").host;//获取配置
console.log("mysql config: host = %s",host);
});
用户可以使用loadConfig()
的调用加载任何JSON
格式的配置文件,用于其它的目的,并能通过app
进行访问。需要注意的是所有的JSON
配置文件中都需要指定具体的模式,也就是development
或production
。
/**
* Load Configure json file to settings.
*
* @param {String} key environment key
* @param {String} val environment value
* @return {Server|Mixed} for chaining, or the setting value
* @memberOf Application
*/
Application.loadConfig = function(key, val) {
var env = this.get(Constants.RESERVED.ENV);
val = require(val);
if (val[env]) {
val = val[env];
}
this.set(key, val);
};
上下文变量存取
上下文对象app
提供了设置和获取应用变量的方法,签名为:
set
设置应用变量
app.set(name, value, [isAttach]);
参数 | 描述 |
---|---|
name | 变量名 |
value | 变量值 |
isAttach | 可选,默认为false,附加属性,若isAttach为true则将变量attach到app对象上作为属性。此后对此变量的访问,可直接通过app.name 。 |
/**
* Assign `setting` to `val`, or return `setting`'s value.
*
* Example:
*
* app.set('key1', 'value1');
* app.get('key1'); // 'value1'
* app.key1; // undefined
*
* app.set('key2', 'value2', true);
* app.get('key2'); // 'value2'
* app.key2; // 'value2'
*
* @param {String} setting the setting of application
* @param {String} val the setting's value
* @param {Boolean} attach whether attach the settings to application
* @return {Server|Mixed} for chaining, or the setting value
* @memberOf Application
*/
Application.set = function (setting, val, attach) {
if (arguments.length === 1) {
return this.settings[setting];
}
this.settings[setting] = val;
if(attach) {
this[setting] = val;
}
return this;
};
例如:
app.set("name", "project_name");
const name = app.get("name);//project_name
app.set("name", name, true);
const name = app.name;
get
获取应用变量
app.get(name);
/**
* Get property from setting
*
* @param {String} setting application setting
* @return {String} val
* @memberOf Application
*/
Application.get = function (setting) {
return this.settings[setting];
};
route
route主要负责请求路由信息的维护,路由计算,路由结果缓存等工作,并根据需要切换路由策略,更新路由信息等。
/**
* Set the route function for the specified server type.
*
* Examples:
*
* app.route('area', routeFunc);
*
* var routeFunc = function(session, msg, app, cb) {
* // all request to area would be route to the first area server
* var areas = app.getServersByType('area');
* cb(null, areas[0].id);
* };
*
* @param {String} serverType server type string
* @param {Function} routeFunc route function. routeFunc(session, msg, app, cb)
* @return {Object} current application instance for chain invoking
* @memberOf Application
*/
Application.route = function(serverType, routeFunc) {
var routes = this.get(Constants.KEYWORDS.ROUTE);
if(!routes) {
routes = {};
this.set(Constants.KEYWORDS.ROUTE, routes);
}
routes[serverType] = routeFunc;
return this;
};
用户可自定义不同服务器的不同路由规则,然后进行配置即可。在路由函数中,通过最后的回调函数中返回服务器的ID即可。
$ vim game-server/app.js
//聊天服务器配置
app.configure("production|development", "chat",function(){
//路由配置
app.route("chat", function(session, msg, app, cb){
const servers = app.getServersByType("chat");
if(!servers || servers.length===0){
cb(new Error("can not find chat servers"));
return;
}
const val = session.get("rid");
if(!val){
cb(new Error("session rid is not find"));
return;
}
const index = Math.abs(crc.crc32(val)) % servers.length;
const server = servers[index];
cb(null, server.id);
});
//过滤配置
app.filter(pomelo.timeout());
});
filter
实际应用中,往往需要在逻辑服务器处理请求之前对用户请求做一些前置处理,当请求被处理后又需要做一些善后处理,由于这是一种常见的情形。Pomelo对其进行了抽象,也就是filter。在Pomelo中filter分为before filter和after filter。在一个请求到达Handler被处理之前,可以经过多个before filter组成的filter链进行一些前置处理,比如对请求进行排队,超时处理。当请求被Handler处理完成后,又可以通过after filter链进行一些善后处理。这里需要注意的是在after filter中一般只做一些清理处理,而不应该再去修改到客户端的响应内容。因为此时,对客户端的响应内容已经发送给了客户端。
filter链
filter
分为before
和after
两类,每个filter
都可以注册多个形成一个filter
链,所有客户端请求都会经过filter
链进行处理。before filter
会对请求做一些前置处理,如检查当前玩家是否已经登录,打印统计日志等。after filter
是进行请求后置处理的地方,比如释放请求上下文的资源,记录请求总耗时等。after filter
中不应该再出现修改响应内容的代码,因为在进入after filter
前响应就已经被发送给客户端。
配置filter
当一个客户端请求到达服务器后,经过filter链和handler处理,最后生成响应返回给客户端。handler是业务逻辑实现的地方,filter则是执行业务前进行预处理和业务处理后清理的地方。为了开发者方便,系统内建提供了一些filter。比如serialFilter、timerFilter、timeOutFilter等,另外,用户可以根据应用的需要自定义filter。
app.filter(pomelo.filters.serial());
如果仅仅是before filter,那么调用app.before。
/**
* Add before filter.
*
* @param {Object|Function} bf before fileter, bf(msg, session, next)
* @memberOf Application
*/
Application.before = function (bf) {
addFilter(this, Constants.KEYWORDS.BEFORE_FILTER, bf);
};
如果是after filter,则调用app.after。
/**
* Add after filter.
*
* @param {Object|Function} af after filter, `af(err, msg, session, resp, next)`
* @memberOf Application
*/
Application.after = function (af) {
addFilter(this, Constants.KEYWORDS.AFTER_FILTER, af);
};
如果即定义了before filter,又定义了after filter,可以使用app.filter调用。
/**
* add a filter to before and after filter
*
* @param {Object} filter provide before and after filter method.
* A filter should have two methods: before and after.
* @memberOf Application
*/
Application.filter = function (filter) {
this.before(filter);
this.after(filter);
};
用户可以自定义filter,然后通过app.filter调用,将其配置进框架。
filter对象
filter是一个对象,定义filter大致代码如下:
let Filter = function(){};
/**
* 前置过滤器
* @param msg 用户请求原始内容或经前面filter链处理后的内容
* @param session 若在后端服务器上则是BackendSession,若在前端服务器则是FrontendSession
* @param next
*/
Filter.prototype.before = function(msg, session, next){
};
/**
* 后置过滤器
* @param err 错误信息
* @param msg
* @param session
* @param resp 对客户端的响应内容
* @param next
*/
Filter.prototype.after = function(err, msg, session, resp, next){
};
module.exports = function(){
return new Filter();
};