koa的设计理念:就是koa只提供核心组件,其他自己组装,本系统也是基于koa之上使用了一些第三方的依赖而建立的系统,下图是根据理解所画的流程图: 以下全是基于流程的分析。
入口文件app.js
webserver
整个系统入口文件是app.js,先把核心代码贴出来:
var config = require('./server/config/config'),
koaConfig = require('./server/config/koa'),
koa = require('koa'),
gzip = require('koa-gzip'),
etag = require('koa-etag'),
fresh = require('koa-fresh'),
http = require('http'),
app = koa();
//app.use(gzip());
//app.use(fresh());
//app.use(etag());
//koaConfig(app);
app.server = app.listen(config.app.port);
console.log('server is runing on port: ' + config.app.port);
这部分代码主要是监听配置的端口以,koa建立一个web server。
抛开次数直接使用的几个中间件不说,koaConfig是整个server的核心处理模块,此处已函数方式引入运行。
压缩
app.use(gzip()) //依赖koa-gzip
koa-gzip这个中间件主要是进行response压缩,这样能大大节省带宽。根据koa的执行顺序这也是,所有的请求都会最后走一次这个中间件,这也是为什么放在第一个。
缓存策略
app.use(fresh()); //koa-fresh
app.use(etag()); //koa-etag
koa-fresh主要是检查响应头(具体检查略不说)是否新鲜,如果新鲜则直接返回304,是客户端使用浏览器缓存。
koa-etag用于设置相应head的tag标签,以为了能判定相应的过期时间。
核心模块/server/koa.js
// == koa核心配置
module.exports = function (app) {
// 是否开启调试
if (config.app.env.indexOf('dev') == -1) {
app.use(logger());
}
// cors配置
app.use(cors({
origin:'*',
maxAge: config.app.cacheTime / 1000,
credentials: true,
methods: 'GET',
headers: 'Origin'
}));
var sendOpts = {root: config.app.view};
app.use(function *(next) {
if(config.app.update){
yield send(this, '/update.html', sendOpts);
return;
}
// post处理
if(this.method === 'POST'){
yield next;
}
// get处理
if(this.method === 'GET'){
var pathName = this.path.split('/')[1];
if(this.path.indexOf('.') > -1) { /*是否为静态资源*/
yield send(this, this.path, sendOpts);
return;
}else if(pathName === 'auth'){ // 如果是授权则进行相应跳转
yield next;
} else if(config.app.h5Router) { /*h5路由处理*/
var startRouter = this.path || config.app.startRouter,
indexName = config.app.index || 'index',
subIndexName = 'index',
isSubapp = config.subapp && pathName in config.subapp;//是否为子应用
this.body = isSubapp
? yield view.showRoot('/subapp/' + pathName + '/' + subIndexName, {startRouter: startRouter})
: yield view.showRoot('/' + indexName, {startRouter: startRouter});
return;
}else{
yield next;
}
}
});
// == session作用域
var sessionDomain =
config.app.view.indexOf('dev') >-1 || config.app.view.indexOf('test') >-1
? ''
: '.' + config.app.domain; /*测试*/
app.keys = ['***']; /*session key*/
app.use(session(app,{
key:'***',
httpOnly:true,
expries:'session',
domain:sessionDomain
}));
// == 加载公共路由
fs.readdirSync('./server/routers/public').forEach(function (file) {
require('../routers/public/' + file)(app);
});
// == 加载模块路由
loadRouters(config.app.router);
function loadRouters(path) {
fs.readdirSync('./server/routers/' + path).forEach(function (file) {
if(file.indexOf('.js') >-1){
require('../routers/'+ path +'/' + file)(app);
}else if(file.indexOf('.DS_Store') == -1 && file){
loadRouters(path + '/' + file);
}
});
}
};
以上代码便是核心业务代码,其中一些因为放到公开部分,稍微做了下处理,先看下配置。
配置
主要的配置文件是/server/config/config.js与config.json。
var platformConfig = JSON.parse(fs.readFileSync('config.json',{encoding:'utf-8'}));
module.exports = _.merge(baseConfig, platformConfig[baseConfig.app.env || (baseConfig.app.env = 'wapdev')]);
可以看过,基于NODE_ENV的值(默认为wapdev)来读取config.json的配置来与config.js的值进行合并和输出。
日志
if (config.app.env.indexOf('dev') == -1) {
app.use(logger());
}
根据NODE_ENV的值是否含有dev也就是说其否是开发环境来开启日志,如果是开发则直接打印日志。
跨域配置
app.use(cors({
origin:'*',
maxAge: config.app.cacheTime / 1000,
credentials: true,
methods: 'GET',
headers: 'Origin'
}));
配置后,前端可以跨域进行访问。
(其实本站目前发现完全可以不用进行跨域设置,因为h5所有请求都是本站下的,猜测可能是为了app中请求?)
session
// == session作用域
var sessionDomain =
config.app.view.indexOf('dev') >-1 || config.app.view.indexOf('test') >-1
? ''
: '.' + config.app.domain; /*测试*/
app.keys = ['***']; /*session key*/
app.use(session(app,{
key:'***',
httpOnly:true,
expries:'session',
domain:sessionDomain
}));
正常sessio配置,需要注意的是,开发环境与生产环境的域不一样。
GET请求
if(this.method === 'GET'){
var pathName = this.path.split('/')[1];
if(this.path.indexOf('.') > -1) { /*是否为静态资源*/
yield send(this, this.path, sendOpts);
return;
}else if(pathName === 'auth'){ // 如果是授权则进行相应跳转
yield next;
} else if(config.app.h5Router) { /*h5路由处理*/
var startRouter = this.path || config.app.startRouter,
indexName = config.app.index || 'index',
subIndexName = 'index',
isSubapp = config.subapp && pathName in config.subapp;//是否为子应用
this.body = isSubapp
? yield view.showRoot('/subapp/' + pathName + '/' + subIndexName, {startRouter: startRouter})
: yield view.showRoot('/' + indexName, {startRouter: startRouter});
return;
}else{
yield next;
}
}
如果是get请求则还分三种情况:
- 通过检测url中是否有“.”来判断是否请求的是静态资源,如果是则直接命中对应的静态资源。
- 如果url表达为鉴权,则交出执行权。
- 根据路径是否有/subapp/来渲染不同路径下的页面。
路由
路由部分也是命中的post请求。
// == 加载公共路由
fs.readdirSync('./server/routers/public').forEach(function (file) {
require('../routers/public/' + file)(app);
});
// == 加载模块路由
loadRouters(config.app.router);
function loadRouters(path) {
fs.readdirSync('./server/routers/' + path).forEach(function (file) {
if(file.indexOf('.js') >-1){
require('../routers/'+ path +'/' + file)(app);
}else if(file.indexOf('.DS_Store') == -1 && file){
loadRouters(path + '/' + file);
}
});
}
此处是先扫面路由下的文件,然后一个个去执行。以/server/routers/public/monitor.js为例其实相当于在此处这样写:
var monitor = require('../routers/public/monitor.js');
monitor(app)
每个路由下的文件都如上执行了一次,把所有的路由都挂在koa上。
mock与请求转发
我们继续以/server/routers/public/monitor.js路由为例来分析下命中post请求后,如何去获取数据及处理的。
// wap端监控接口
router.post('/log/tranCore/BuriedPointWxService',composition.noToken(),controller());
app.use(router.routes());
其实可以参考koa-router的使用api。
此处router.post请求的三个参数。第一个是url,第二个是一个中间件,第三个是请求完成后的回调。
在第二个中间件中,根据环境配置与参数的不同,来决定是读取mock数据还是转发请求到mserver。
篇幅编辑问题,详细解释,后续展开。