We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
我们在使用koa的时候发现,其实koa只是对req,res进行了封装,但是很多一些功能如路由、静态资源、模板引擎等都没有支持,看过源码都知道koa的源码就那么一点。但是丰富的第三方中间件弥补了这个不足。接下来我们来揭开一下一些常用中间件的内部原理,你会发现其实koa的中间件真的大同小异。
我们先来简单看下 koa-bodyparser 的基本用法
koa-bodyparser
const Koa = require("koa") const bodyParser = require("koa-bodyparser") const app = new Koa() app.use(bodyParser()) // 挂载中间件 koa-bodyparser app.use(async (ctx, next) => { // 渲染一个表单,表单提交时候请求接口 /login if (ctx.method === "GET" && ctx.path === "/form") { ctx.body = ` <form action="/login" method="post"> <input type="text" name="username"/> <input type="text" name="password"/> <button>提交</button> </form> `; } else { await next(); } }); app.use(async ctx => { if (ctx.method === "POST" && ctx.path === "/login") { ctx.body = ctx.request.body; //将接口得到的数据展示在页面上 } }); app.listen(5050);
上面的代码其实很简单,/form 路由渲染一个表单组件,表单组件请求的是/login 路由,而/login将得到的数据渲染在页面,我们来看下运行效果:
/form
/login
![koa1](https://user-images.githubusercontent.com/53081460/86111612-8dfccf00-baf9-11ea-9c5d-4f0739e4dbad.gif
知道 koa-bodyparser 的基本用法之后,我们试一下自己实现一个类似功能的中间件
const bodyParser = ()=>{ return async (ctx,next)=>{ // 返回一个async函数 await new Promise((resolve,reject)=>{ let arr = []; ctx.req.on('data',(chunk)=>{ arr.push(chunk); }) ctx.req.on('end',function(){ ctx.request.body = Buffer.concat(arr).toString(); // 将文件流赋值给 ctx.request.body resolve(); }) }) await next(); } }
其实你会发现,内部就是这么简单,而且不难发现,app.use() 方法在 koa 中是接受一个 async函数的,根据app.use(bodyParser()) 来挂载中间件我们可以知道 bodyParser() 返回的就是一个 async 函数,而中间件的核心原理则是利用 koa 中间件的洋葱机制,在一开始给 ctx 上挂载一些属性或者方法,则在后面的 arr.use() 中都可以通过 ctx来拿到对应挂载的方法。
app.use()
koa
async
app.use(bodyParser())
bodyParser()
ctx
arr.use()
koa-static 的作用用一句话来概括就是,起一个静态服务,下面看下具体用法
koa-static
const Koa = require("koa"); const app = new Koa(); const static = require('koa-static'); app.use(static(__dirname)) app.listen(5050);
以上代码的意思是,以当前目录为根目录起一个静态服务,通过 http://localhost:5050/XXX 就可以获取到对应的资源了。
http://localhost:5050/XXX
而且不难发现,koa-static 的用法和 koa-bodyparser 很像,都是app.use(xxx()) 的形式,那我们就来简单实现一个这个效果
app.use(xxx())
function static(pathname) { return async (ctx, next) => { try { // 因为 fs.stat 主要通过报错来判断当前路径文件存不存在 所以try catch处理 let filePath = ctx.path filePath = path.join(pathname, filePath) // 拿到绝对路径 let statObj = await fs.stat(filePath) if (statObj.isDirectory()) {// 如果是目录,则拼接上 `index.html` filePath = path.resolve(filePath, 'index.html') } ctx.body = await fs.readFile(filePath, 'utf-8') //读取处理过的路径,然后返回 }catch(e) { return next() // 如果处理不了 就走下一个中间件 } } }
原理很简单,就是读当前传入路径的文件,有的话就赋值给 ctx.body , 没有的话走下一个中间件
ctx.body
顾名思义,一个路由的第三方中间件,我们直接看它的基础用法
const Koa = require('koa'); const Router = require('koa-router'); const router = new Router(); const app = new Koa(); router.get('/hello',async (ctx,next)=>{ ctx.body = 'hello'; next(); }) app.use(router.routes()); app.listen(5050);
上诉代码主要是起一个/hello的get请求接口,返回hello, 大大的简化了koa对路由的操作,如ctx.method === "GET" && ctx.path === "/hello" 类似的判断,我们自己实现的时候需要注意到一些细节,如 Router 是一个类,app.use()挂载的是类上routes方法返回的函数,我们试下来实现一款简单的koa-router.
/hello
get
hello
ctx.method === "GET" && ctx.path === "/hello"
Router
routes
koa-router
class Layer{ //将栈中的结构抽取一个类 方便后序扩展 constructor(method,pathname,callback){ this.method = method; this.pathname = pathname; this.callback = callback; } match(path,method){ //将匹配方法抽离出来 return path === this.pathname && method.toLowerCase() === this.method; } } class Router{ constructor(){ this.stack = [];// 这里面存放着所有的路由关系 } get(pathname,callback){ // 我们次调用get方法都会像内部数组放一层 let layer = new Layer('get', pathname, callback) this.stack.push(layer) } compose(fns,ctx,next){ // compose原理 和koa类似 先抽取栈中的第一个执行, 将下一个函数作为第一个函数的参数next,以此递归 let dispatch = (index)=>{ if(index === fns.length) return next(); // 边界值判断 避免爆了 let callback = fns[index].callback;// 拿到栈中每一项执行 return Promise.resolve(callback(ctx,()=>dispatch(index+1))) } return dispatch(0); } routes(){ return async (ctx,next)=>{ // 获取请求的路径 let path = ctx.path; // /hello let method = ctx.method; // get let fns = this.stack.filter(layer=>layer.match(path,method));//在存储的路由表中筛选出符合条件项 this.compose(fns,ctx,next) // 进行组合 } } } module.exports = Router;
我们慢慢来体会一下 koa-router 的内部流程 首先我们使用route.get('/xxx', callback) 的时候会调用 Router类上的get方法,该方法会将 method path callback 通过 Layer 类组装一下,存入 stack中,我们 statck的结构如下
route.get('/xxx', callback)
method
path
callback
Layer
stack
statck
然后我们调用 route.routes() 的时候,会根据当前请求的方法和路径去 stack中筛选出符合条件的项,将他们组合成一个大的函数,内部原理是和koa的中间件原理一样的,先执行第一个函数,将下一个函数作为第一个函数的参数next
route.routes()
next
其实这只是实现koa-router 的一小部分内容,还有很多如二级路由啊、参数处理啥的比较恶心,这里就不写了
一句话来说 koa-views就是用来实现模板引擎的,很简单
koa-views
const Koa = require('koa'); const Router = require('koa-router'); const views = require('koa-views'); const app = new Koa(); const path = require('path'); const router = new Router(); app.use(views(path.resolve(__dirname,'views'),{ map: { html: 'ejs' //如果遇到.html 后缀的文件,用ejs模板处理 } })); router.get('/',async ctx=>{ await ctx.render('hello',{name:'zf'}); }) app.use(router.routes()) app.listen(5050);
先来理一下koa-views 的基本用法吧,首先挂载中间件,声明一些参数,如 遇到.html 后缀的文件用 ejs 模板引擎来渲染,最后在 ctx上挂载一个render()方法,传入对应的数据来渲染对应的页面。知道原理之后,我们不妨自己来实现一下
.html
ejs
render()
const views = (dirname,{map})=>{ return async (ctx,next)=>{ ctx.render = async (filename,data)=>{ // 将方法挂载到 ctx.render let ejs = require(map.html);// 引用ejs模板 这里的做法有点low 其实应该遍历取出的 const renderFile = util.promisify(ejs.renderFile); // 调用ejs的方法渲染页面 // 渲染文件,成功后将结果返回去 ctx.body = await renderFile(path.join(dirname,filename+'.html'),data); } await next(); // 增加逻辑后 继续向下执行 koa-static } }
通过对工作做经常用到的几个中间件进行了一波深入了解,相信大家对koa中间件的路子和形式都摸得差不多了。核心原理就是利用koa中的洋葱模型,一开始往ctx上挂载自己需要的属性或者方法,后序就可以通过ctx来调用啦。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
koa中间件大揭秘
前言
我们在使用koa的时候发现,其实koa只是对req,res进行了封装,但是很多一些功能如路由、静态资源、模板引擎等都没有支持,看过源码都知道koa的源码就那么一点。但是丰富的第三方中间件弥补了这个不足。接下来我们来揭开一下一些常用中间件的内部原理,你会发现其实koa的中间件真的大同小异。
koa-bodyparser
我们先来简单看下
koa-bodyparser
的基本用法上面的代码其实很简单,
/form
路由渲染一个表单组件,表单组件请求的是/login
路由,而/login
将得到的数据渲染在页面,我们来看下运行效果:![koa1](https://user-images.githubusercontent.com/53081460/86111612-8dfccf00-baf9-11ea-9c5d-4f0739e4dbad.gif
知道
koa-bodyparser
的基本用法之后,我们试一下自己实现一个类似功能的中间件其实你会发现,内部就是这么简单,而且不难发现,
app.use()
方法在koa
中是接受一个async
函数的,根据app.use(bodyParser())
来挂载中间件我们可以知道bodyParser()
返回的就是一个async
函数,而中间件的核心原理则是利用koa
中间件的洋葱机制,在一开始给ctx
上挂载一些属性或者方法,则在后面的arr.use()
中都可以通过ctx
来拿到对应挂载的方法。koa-static
koa-static
的作用用一句话来概括就是,起一个静态服务,下面看下具体用法以上代码的意思是,以当前目录为根目录起一个静态服务,通过
http://localhost:5050/XXX
就可以获取到对应的资源了。而且不难发现,
koa-static
的用法和koa-bodyparser
很像,都是app.use(xxx())
的形式,那我们就来简单实现一个这个效果原理很简单,就是读当前传入路径的文件,有的话就赋值给
ctx.body
, 没有的话走下一个中间件koa-router
顾名思义,一个路由的第三方中间件,我们直接看它的基础用法
上诉代码主要是起一个
/hello
的get
请求接口,返回hello
, 大大的简化了koa
对路由的操作,如ctx.method === "GET" && ctx.path === "/hello"
类似的判断,我们自己实现的时候需要注意到一些细节,如Router
是一个类,app.use()
挂载的是类上routes
方法返回的函数,我们试下来实现一款简单的koa-router
.我们慢慢来体会一下
koa-router
的内部流程首先我们使用
route.get('/xxx', callback)
的时候会调用Router
类上的get
方法,该方法会将method
path
callback
通过Layer
类组装一下,存入stack
中,我们statck
的结构如下然后我们调用
route.routes()
的时候,会根据当前请求的方法和路径去stack
中筛选出符合条件的项,将他们组合成一个大的函数,内部原理是和koa
的中间件原理一样的,先执行第一个函数,将下一个函数作为第一个函数的参数next
其实这只是实现
koa-router
的一小部分内容,还有很多如二级路由啊、参数处理啥的比较恶心,这里就不写了koa-views
一句话来说
koa-views
就是用来实现模板引擎的,很简单先来理一下
koa-views
的基本用法吧,首先挂载中间件,声明一些参数,如 遇到.html
后缀的文件用ejs
模板引擎来渲染,最后在ctx
上挂载一个render()
方法,传入对应的数据来渲染对应的页面。知道原理之后,我们不妨自己来实现一下总结
通过对工作做经常用到的几个中间件进行了一波深入了解,相信大家对
koa
中间件的路子和形式都摸得差不多了。核心原理就是利用koa
中的洋葱模型,一开始往ctx
上挂载自己需要的属性或者方法,后序就可以通过ctx
来调用啦。The text was updated successfully, but these errors were encountered: