koa和express
kao和express都是同一个团队开发的,koa框架会用也会写—(koa的实现)已经介绍koa的原理,而koa在express的基础上进行了优化:
- koa使用了类的概念,express没有使用类,而是直接使用函数对象,在上面挂载很多方法
- koa封装了ctx属性,并且在上面挂载了path、url等属性,而express没有ctx,所以其属性直接挂载在res和req上
- koa将router逻辑从express中抽离出来形成koa-router插件,所以express中router和中间件共用一个队列,中间件默认的路由为'/'
- koa将static从express中抽离出形成koa-static,express自带static
- koa将views从express中抽离出形成koa-views,express自带static
- koa将bodyparse从express中抽离出形成koa-bodyparse,express通过body-parser
express的整体框架
从上面的可以知道express的大致框架:
- express是一个对象,上面挂载了static、view、bodyparse等逻辑方法;express也是一个函数,执行会返回一个app对象
- app是一个监听请求时的处理函数,也是一个对象上面挂载了很多方法。
const http = require('http');
const url = require('url');
cosnt methods = require('methods'); //[get,post.......]
function createApplication() {
//监听函数
function app(req, res) {
let method = req.method.toLowerCase();
let { pathname, query } = url.parse(req.url,true);
let index = 0;
// 创建了一个next函数,用来派发中间件、路由,事项洋葱模型
function next() {
if(app.routes.length == index) return res.end(`Cannot found`)
let layer = app.routes[index++];
//这里中间件的处理逻辑和路由的处理逻辑不一样,下面在介绍
layer.handler(req,res,next);
}
next();
}
//内部封装了http模块
app.listen = function () {
let server = http.createServer(app);
server.listen(...arguments)
}
app.routes = []; //中间件和路由队列
// 批量创建方法:app.get、app.set.....注册路由
methods.forEach(method => {
app[method] = function (pathname, handler) {
let layer = {
pathname, //路由
handler, //处理函数
method //路由为get、post....,中间件为middleware
}
//处理带参数的路由/user/:id/:name
let keys = [];
if(pathname.includes(":")){
let regStr = pathname.replace(/:([^/]*)/g,function () {
keys.push(arguments[1]);
return '([^\/]*)'
});
layer.params = keys; // 路径参数key数组[id,name]
// 转化成正则类似/\/user\/([^\/]*)\/([^\/]*)/来匹配请求路径
// 后面会介绍使用的地方
layer.reg = new RegExp(regStr);
}
app.routes.push(layer);
}
});
app.all = function (pathname, handler) {
let layer = {
pathname,
handler,
method:'all'
}
app.routes.push(layer);
}
// 注册中间件
app.use = function (pathname,handler) {
if(typeof handler === 'undefined'){
handler = pathname;
pathname = '/'; //中间件也是一种路由,所有的路由都能匹配
}
let layer = {
pathname,
handler,
method:'middleware' //用来和router的方法区别
}
app.routes.push(layer);
}
return app;
}
module.exports = createApplication;
复制代码
扩展res和req
function createApplication() {
function app(req, res) {
let method = req.method.toLowerCase();
let { pathname, query } = url.parse(req.url,true);
let index = 0;
function next() {
if(app.routes.length == index) return res.end(`Cannot found`)
let layer = app.routes[index++];
layer.handler(req,res,next);
}
next();
}
app.listen = function () {
let server = http.createServer(app);
server.listen(...arguments)
}
app.routes = [];
app.use = function (pathname,handler) {
if(typeof handler === 'undefined'){
handler = pathname;
pathname = '/';
}
let layer = {
pathname,
handler,
method:'middleware'
}
app.routes.push(layer);
}
// 用app.use注册了内置中间件来扩展req和res的
app.use(function (req,res,next) {
let method = req.method.toLowerCase();
let { pathname, query } = url.parse(req.url, true);
req.path = pathname;
req.query = query;
req.hostname = req.headers.host.split(':')[0];
//res.send对返回各种类型兼容处理
res.send = function (params) {
res.setHeader('Content-Type', 'text/html;charset=utf8');
if (typeof params === 'object') {//返回json对象
res.setHeader('Content-Type', 'application/json;charset=utf8');
res.end(util.inspect(params));
} else if (typeof (params) === 'number') {//数字对应状态码
res.statusCode = params;
res.end(require('_http_server').STATUS_CODES[params]);
} else {
res.end(params);
}
}
//res.sendFile返回文件
res.sendFile = function (pathname) {
res.setHeader('Content-Type', require('mime').getType(pathname) + ';charset=utf8');
fs.createReadStream(pathname).pipe(res);
}
//res.redirect重定向
res.redirect = function (pathname) {
res.statusCode = 302;
res.setHeader('Location',pathname);
res.end();
}
next();
})
return app;
}
module.exports = createApplication;
复制代码
区分处理中间件和路由
function app(req, res) {
let method = req.method.toLowerCase();
let { pathname, query } = url.parse(req.url,true);
let index = 0;
function next(err) {
let layer = app.routes[index++];
if(layer){
//中间件的处理,包含存在请求路径处理exp:/user/info匹配/user/
if (layer.method === 'middle') {
if (layer.pathname === '/' || req.path.startsWith(layer.pathname + '/') || req.path === layer.pathname) {
return layer.handler(req, res, next); //把控制权next给了用户
} else {
next(); // 匹配不到路径就执行next()匹配下一个中间件
}
} else {
//router处理含请求参数的路由
if (layer.params) {
if (layer.method === method && (layer.reg.test(req.path))) {
// layer.reg => /\/user\/([^\/]*)\/([^\/]*)/
// req.path => '/user/1/kbz'
// matchers => ['/user/1/kbz','1','2']
// layer.params => [id,name]
// req.params => {id:'1',name:'kbz'}
let matchers = req.path.match(layer.reg);
req.params = layer.params.reduce((memo, current, index) => (memo[current] = matchers[index + 1], memo), {});
return layer.handler(req, res);
}
}
//router处理不含请求参数的路由
if ((layer.pathname === req.path || layer.pathname === '*') && (layer.method === method || layer.method === 'all')) {
return layer.handler(req, res);
}
next() //router的处理会自动调用next()
}
}else{
res.end(`Cannot ${pathname} ${method}`);
}
}
next();
}
复制代码
next(err)错误处理
function app(req, res) {
let method = req.method.toLowerCase();
let { pathname, query } = url.parse(req.url,true);
let index = 0;
function next(err) {
let layer = app.routes[index++];
if(layer){
if(err){
//处理错误,应该找到错误处理中间件,特点是拥有4个参数
//由用户定义,放在对列最后
if (layer.method === 'middle' && layer.handler.length===4 ){
return layer.handler(err,req,res,next)
}else{
next(err); //不是错误处理中间件,就向后继续查找
}
}else{
if (layer.method === 'middle') {
if (layer.pathname === '/' || req.path.startsWith(layer.pathname + '/') || req.path === layer.pathname) {
return layer.handler(req, res, next);
} else {
next();
}
} else {
if (layer.params) {
if (layer.method === method && (layer.reg.test(req.path))) {
let matchers = req.path.match(layer.reg);
req.params = layer.params.reduce((memo, current, index) => (memo[current] = matchers[index + 1], memo), {});
return layer.handler(req, res);
}
}
if ((layer.pathname === req.path || layer.pathname === '*') && (layer.method === method || layer.method === 'all')) {
return layer.handler(req, res);
}
next()
}
}
}else{
res.end(`Cannot ${pathname} ${method}`);
}
}
next();
}
复制代码
内置view渲染逻辑
使用express渲染逻辑主要是调用res.render方法,其中使用最多的就是ejs模板引擎,ejs渲染逻辑可以参考koa框架会用也会写—(koa-view、koa-static)
app.set('views','view'); //渲染文件目录
app.set('view engine','html'); //更改省略的后缀为html,而不是.ejs
app.engine('html',require(ejs').__express); //用ejs模板渲染
复制代码
function createApplication() {
app.use = function (pathname,handler) {
if(typeof handler !== 'function'){
handler = pathname;
pathname = '/';
}
let layer = {
method:'middle',
pathname,
handler
}
app.routes.push(layer);
}
// 配置
app.settings = {}
app.set = function (key,value) {
app.settings[key] = value;
}
app.engines = {}
app.engine = function (ext,renderFn) {
app.engines[ext] = renderFn
}
app.use(function (req,res,next
let method = req.method.toLowerCase();
let { pathname, query } = url.parse(req.url, true);
req.path = pathname;
req.query = query;
req.hostname = req.headers.host.split(':')[0];
res.send = function (params) {
res.setHeader('Content-Type', 'text/html;charset=utf8');
if (typeof params === 'object') {
res.setHeader('Content-Type', 'application/json;charset=utf8');
res.end(util.inspect(params));
} else if (typeof (params) === 'number') {
res.statusCode = params;
res.end(require('_http_server').STATUS_CODES[params]);
} else {
res.end(params);
}
}
res.sendFile = function (pathname) {
res.setHeader('Content-Type', require('mime').getType(pathname) + ';charset=utf8');
fs.createReadStream(pathname).pipe(res);
}
res.redirect = function (pathname) {
res.statusCode = 302;
res.setHeader('Location',pathname);
res.end();
}
//通过render对页面进行渲染
res.render = function (filename,obj) {
let dirname = app.settings['views'] ;
let extname = app.settings['view engine'] ;
let p = path.resolve(dirname,filename+'.'+extname);
app.engines[extname](p,obj,function (data) {
res.end(data);
});
}
next();
})
}
复制代码
内置static逻辑
这里简化了逻辑:
createApplication.static = function (dir) {
return function (req,res,next) {
let p = req.path === '/' ? '/index.html' : req.path
let realPath = path.join(dir, p);
let flag = fs.existsSync(realPath);
if(flag){
fs.createReadStream(realPath).pipe(res);
}else{
next();
}
}
}
复制代码
express的bodyparser
express的bodyparser也是通过插件引入
// body-parser.js
function urlencoded() {
return (req,res,next)=>{
if (req.headers['content-type']==='application/x-www-form-urlencoded'){
let arr = [];
req.on('data',function (data) {
arr.push(data);
})
req.on('end', function (data) {
req.body = require('querystring').parse(Buffer.concat(arr).toString());
next();
})
}else{
next();
}
}
}
function json() {
return (req, res, next) => {
if (req.headers['content-type'] === 'application/json') {
let arr = [];
req.on('data', function (data) {
arr.push(data);
})
req.on('end', function (data) {
req.body = JSON.parse(Buffer.concat(arr).toString());
next();
})
} else {
next();
}
}
}
module.exports.urlencoded = urlencoded
module.exports.json = json
复制代码
结语
前面koa框架会用也会写—(koa的实现)已经详细介绍了koa的原理和中间件,这里主要是表示express和koa不同的地方,主要的插件逻辑可能都简化了,再次说明express的不同:
- koa使用了类的概念,express没有使用类,而是直接使用函数对象,在上面挂载很多方法
- koa封装了ctx属性,并且在上面挂载了path、url等属性,而express没有ctx,所以一些属性和方法直接挂载在res和req上
- koa将router逻辑从express中抽离出来形成koa-router插件,所以express中router和中间件共用一个队列,中间件默认的路由为'/'
- koa将static从express中抽离出形成koa-static,express自带static
- koa将views从express中抽离出形成koa-views,express自带res.render
- koa将bodyparse从express中抽离出形成koa-bodyparse,express自带bodyparse