概述nodeJS企业级框架egg
Author:zhoulujun Date:
https://eggjs.org/ 官方文档,应付日常开发足够了。本篇还是来分析下egg
回顾express和koa
使用express.js启动一个简单的服务器
const express = require('express')
const app = express()
const router = express.Router()
app.use(async (req, res, next) => {
console.log('I am the first middleware')
next()
console.log('first middleware end calling')
})
app.use((req, res, next) => {
if (err) {
console.log('second middleware catch error', err)
res.status(500).send('server Error')
return
}
console.log('I am the second middleware')
next()
console.log('second middleware end calling')
})
router.get('/api/test1', async(req, res, next) => {
console.log('I am the router middleware => /api/test1')
res.status(200).send('hello')
})
app.use('/', router)
app.listen(3000)
换算成等价的koa2
const koa = require('koa')
const Router = require('koa-router')
const app = new koa()
const router = Router()
app.use(async(ctx, next) => {
console.log('I am the first middleware')
await next()
console.log('first middleware end calling')
})
app.use(async (ctx, next) => {
console.log('I am the second middleware')
await next()
console.log('second middleware end calling')
})
router.get('/api/test1', async(ctx, next) => {
console.log('I am the router middleware => /api/test1')
ctx.body = 'hello'
})
app.use(router.routes())
app.listen(3000)
二者的使用区别通过表格展示如
日常一些简单的开发需求,两个框架都能应付,但是中间,让我头痛
koa简化了,但是还是没有egg理想
//- router.js
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
};
//- app/controller/home.js
const { Controller } = require('egg');
class HomeController extends Controller {
async index() {
this.ctx.body = 'Hello world';
}
}
module.exports = HomeController;
整体上,简洁干练
egg 简介
egg 是阿里开源的一个强约束的Node框架,为企业级框架和应用而生,相较于 express 和 koa ,有更加严格的目录结构和规范,使得团队可以在基于 egg 定制化自己的需求或者根据 egg 封装出适合自己团队业务的更上层框架
可以看到 egg 处于的是一个中间层的角色,基于 koa ,不同于 koa 以 middleware 为主要生态,egg 根据不同的业务需求和场景,加入了 plugin , extends 等这些功能,可以让开发者摆脱在使用 middleware 功能时无法控制使用顺序的被动状态,而且还可以增加一些请求无关的一些功能。
egg 中内置插件系列:
onerror 统一异常处理
Session Session 实现
i18n 多语言
watcher 文件和文件夹监控
multipart 文件流式上传
security 安全
development 开发环境配置
logrotator 日志切分
schedule 定时任务
static 静态服务器
jsonp jsonp 支持
view 模板引擎
加载插件的逻辑是在 egg-core 里面的 plugin.js 文件
扩展内置对象
在对内置对象进行扩展的时候,实质上执行的是 extend.js 文件,扩展的对象包括如下几个:
Application
Context
Request
Response
Helper
通过阅读 extend.js 文件可以知道,其实最后每个对象的扩展都是直接调用的 loadExtends 这个函数。
egg功能加载
加载中间件:对中间件的加载主要是执行的 egg-core 中的 middleware.js 文件,里面的代码思想也是和上面加载内置对象是一样的,也是将插件中的中间件和应用中的中间件路径全部获取到,然后进行遍历。
遍历完成之后执行中间件就和 koa 一样了,调用 co 进行包裹遍历。
加载控制器 :对控制器的加载主要是执行的 egg-core 中的 controller.js 文件
egg 的官方文档中,插件的开发这一节提到:
插件没有独立的 router 和 controller
所以在加载 controller 的时候,主要是 load 应用里面的 controller 即可。
加载 service:加载 service 的逻辑是 egg-core 中的 service.js
加载路由 :加载路由的逻辑主要是 egg-core 中的 router.js 文件
加载配置:直接加载配置文件并提供可配置的方法。
框架结构概述
app:核心目录,app目录下又按照设计模式分为了数个更细粒度的子目录。
controller:存放controller层的处理文件的位置
extend:存放继承一些自定义公共方法的位置,这个在本节的下面详细说下
middleware:存放自定义中间件文件,所谓的appMiddleware
public:存放项目静态资源的位置
service:Egg框架抽象出来的一个概念,可以认为是带有逻辑处理的model层
view:存放页面模板文件的位置
router.js:编写路由的位置
config:核心目录,配置文件相关,其中config.default.js中存放的是和当前Node环境无关的配置;config.[env].js文件则存放和Node执行环境相关的配置;plugin.js存放的则是各个插件的package名称和是否开启的配置。这里的Node执行环境,后面会说明。
logs:日志文件输出的目录。
index.js:项目的入口文件。
egg开发
用比较简单的伪代码表示如下
const app = express();
// nodejs启动时,app函数内部被express增加了能力,如中间件的调用
app.use(middleware); // 中间件
app.use(router); // 路由
app.engine('ejs'); // 模板引擎
app.statifc('public') // 静态文件服务
// ... 还有代理以及其他许多属性与方法
const server = http.createServer(
function app(req, res){ // 此app函数即为express所构造
// http请求时,req, res被混入许多属性与方法,做了很多处理
// 串行匹配运行按顺序注册的各注册的中间件如:
// 1、日志、cookie、bodyparser等开发者自己注册的中间件
// 2、router中间件
// 3、静态文件服务
// 4、模板引擎处理
// 经过匹配的中间件处理后输出返回
}
);
server.listen(8000);
上面的1、2、3、4顺序即为开发者注册时的顺序(故我们平时在开发时express注册中间件时是有先后顺序的)。express最主管理与运行中间件的能力,接下来深入内部看看connect这个中间件机制是怎么实现的。
最为核心的中间件框架
//connect.js 的简要内容
function createServer(){
// app是用于http.createServer的回调函数
function app(req, res, next){
// 运行时调用handle函数
app.handle(req, res, next);
}
mixin(app, proto, false);
// 初始化一个stack数组
app.stack = [];
return app;
}
// use调用时往app的stack数组中push一个对象(中间件),标识path与回调函数
proto.use = function(route, fn){
var path = route,
handle = fn;
//... 省略其他
this.stack.push({
route: path,
handle
});
};
// handle方法,串行取出stack数组中的中间件,逐个运行
proto.handle = function(req, res, out){
var index = 0;
var stack = this.stack;
var done = out || finalhandler(req, res, { onerror: logerror });
// 遍历stack,逐个取出中间件运行
function next(err){
var layer = stack[index++];
// 遍历完成为止
if(layer === undefined){
return done();
}
var route = pathFormat(layer.route);
var pathname = pathFormat(urlParser(req.url).pathname || '/');
// 匹配中间件,不匹配的不运行
if(route !== '' && pathname !== route){
next(err);
return;
}
// 调用中间件
call(layer.handle, err, req, res, next);
}
next();
};
不难看出,app.use中间件时,只是把它放入一个数组中。当http请求时,app会从数组中逐个取出,进行匹配过滤,逐个运行。遍历完成后,运行finalhandler,结束一个http请求。可以从http请求的角度思考,一次请求它经历经历了多少东西。express的这个中间件架构就是负责管理与调用这些注册的中间件。中间件顺序执行,通过next来继续下一个,一旦没有继续next,则流程结束。
异步串行流程控制
为了用串行化流程控制让几个异步任务按顺序执行,需要先把这些任务按预期的执行顺序放 到一个数组中。如图,所示,这个数组将起到队列的作用:完成一个任务后按顺序从数组中取 出下一个
数组中的每个任务都是一个函数。任务完成后应该调用一个处理器函数,告诉它错误状态和 结果。如果有错误,处理器函数会终止执行;如果没有错误,处理器就从队列中取出下一个任务 执行它
Router是一个内置在app函数上的中间件
来看下简化后的router.js
//express创建时运行
app.init = function(){
// ... 省略其它代码
this._router = new Router();
this.usedRouter = false;
// app调用router时初始化router中间件
Object.defineProperty(this, 'router', {
configurable : true,
enumerable : true,
get: function () {
this.usedRouter = true;
return this._router.middlewareInit.bind(this._router);
}
})
};
// methods是一个数组,['get','post','put','delete',...]
methods.forEach(method => {
app[method] = function (path) {
// 如果首次调用则放入路由中间价
if(!this.usedRouter){
this.use(this.router);
}
// 加入stack
this._router.addRoute(method, path, Array.prototype.slice.call(arguments, 1))
}
});
usedRouter是个开关,未开启则不加入router中间件,因为应用理论上也是可能不用到router的。当app[method] 如app.get('/user', fn)调用后,则触发this.use(this.router) 使用router中间件,同时把usedRouter设置为true。之后往router对象中加入fn回调函数。
router实际上也是一个异步串行流程控制,简化版的代码如下
Router.prototype.addRoute = function(method, path, handles){
let layer = {
path,
handles
};
this.map[method] = this.map[method] || [];
this.map[method].push(layer);
};
Router.prototype.middlewareInit = function(req, res, out){
let index = 0;
let method = req.method.toLowerCase() || 'get';
let stack = this.map[method];
function next(err) {
let layer = stack[index++];
let hasError = Boolean(err);
// 如果没有了则结束中间件,走下一个中间件
if(!layer){
return hasError ? out(err) : out();
}
let route = utils.pathFormat(layer.path);
let pathname = utils.pathFormat(urlParser(req.url).pathname || '/');
// 进行过滤
if(route!== '' && route !== pathname){
return next(err);
}
executeHandles(layer.handles, err, req, res, next);
}
next();
};
router跟connect非常类似,上述理解了connect,router就很清晰了。一图以蔽之:
实际上router还有细分,某个router还是可以继续做类似的串行流程控制;与中间件相同,每个router一旦停止了next,流程就结束了。
request经过router可以请求一个数据,或者一个网页;网页的话是怎么返回的呢,接下来看下view的render;
参考内容:
NodeJS express框架核心原理全揭秘 https://zhuanlan.zhihu.com/p/56947560
结合源码解密 egg 运行原理 https://zhuanlan.zhihu.com/p/29102746
深夜放毒——阿里开源的企业级Node框架Egg使用指南 https://cnodejs.org/topic/580a6a7e541dfb7b50f40a60
再也不怕面试官问你express和koa的区别了 https://zhuanlan.zhihu.com/p/87079561
转载本站文章《概述nodeJS企业级框架egg》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/JS-Server/8527.html