博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
快来,你想要的koa知识几乎都在这里了!
阅读量:6999 次
发布时间:2019-06-27

本文共 7860 字,大约阅读时间需要 26 分钟。

之前用koa写过不少的demo,加一个实际的线上应用,但一直没有怎么看过它的源码。

这次抽空看了一下源码。

它其实只有4个文件:

  • application.js (主文件)
  • context.js (创建网络请求的上下文对象)
  • request.js (包装 koa 的 request 对象)
  • response.js (包装 koa 的 response对象)

通过package.json文件,我们可以清楚地看到:

application.js是入口文件,那么进去看看吧。

核心方法

  • listen
  • use

基础用法

const Koa = require('koa');const app = new Koa();app.listen(3000);app.use((ctx, next) => {      ctx.body = "hello"})复制代码

listen

就是起了一个服务。

这里有一个模块,可以用它来做一些调试。(使用前提是你的环境变量设置了DEBUG,不然看不到输出)

callback函数代码:

use

use方法,源码给的注释是:

Use the given middleware fn.

Koa里面就是通过一个个的中间件来构建整个服务。

use方法的实现超级简单:

上面callback函数中,有一个代码:

const fn = componse(this.middleware)复制代码

它就是用来组合所有的中间件

中间件

比如我们有这样一段代码:

let fn1 = (ctx,next)=>{    console.log(1);    next();    console.log(2);}let fn2 = (ctx,next)=>{    console.log(3);    next();    console.log(4);}let fn3 = (ctx,next)=>{    console.log(5);    next();    console.log(6);}复制代码

希望能得到:1、3、5、6、4、2的结果。

这个代码比较容易:

let fns = [fn1,fn2,fn3]; function dispatch(index){    let middle = fns[index];    // 判断一下临界点    if(fns.length === index) return function(){}    middle({},()=>dispatch(index+1));}dispatch(0);复制代码

理解了同步的写法,当中间件的写法为asyn await时,就好写了。

function dispatch(index){    let middle = fns[index];    if(fns.length === index) return Promise.resolve()    return Promise.resolve(middle({},()=>dispatch(index+1)))}复制代码

一起来看一下compose的代码吧:

核心逻辑和上面的代码差不多,无非是在逻辑判断上更加地严谨一些。

容易犯的错误

const Koa = require('koa');const app = new Koa();app.listen(3000);function ajax(){    return new Promise(function(resolve,reject){        setTimeout(function(){            resolve("123");        },3000)    })}app.use(async (ctx,next)=>{    console.log(1);    next();    console.log(2);});app.use(async (ctx, next) => {    ctx.body = await ajax();})复制代码

上面的结果是not found,原因是第一个中间件那里没有await next。

ctx

我们再去看createContext的源码实现:

request

就是对之前req对象重新包装了一层。

这里用了高级的语法: get/set,类似Object.definePrototype,主要可以在set的时候做一些逻辑处理。

response

和request.js的处理方式类似。这里我摘抄一段body的写法:

{    get body() {        return this._body;    },    set body(val) {        const original = this._body;        this._body = val;        // no content        if (null == val) {            if (!statuses.empty[this.status]) this.status = 204;            this.remove('Content-Type');            this.remove('Content-Length');            this.remove('Transfer-Encoding');            return;        }        // set the status        if (!this._explicitStatus) this.status = 200;        // set the content-type only if not yet set        const setType = !this.header['content-type'];        // string        if ('string' == typeof val) {            if (setType) this.type = /^\s*
this.ctx.onerror(err)); // overwriting if (null != original && original != val) this.remove('Content-Length'); if (setType) this.type = 'bin'; return; } // json this.remove('Content-Length'); this.type = 'json'; }}复制代码

context

文件所做的事情就比较有意思了。

它做了一层代理,将request下的一些属性方法以及response下的一些属性方法直接挂载在ctx对象上。

譬如之前要通过ctx.request.url来得到请求路径,现在只要写成ctx.url即可。

delegate这个库,我们来简单看一眼,主要看两个方法即可:

我们可以再简化一下:

let proto = {}function delateGetter(property,name){    proto.__defineGetter__(name,function(){        return this[property][name];    })}function delateSetter(property,name){    proto.__defineSetter__(name,function(val){        this[property][name] = val;    })}delateGetter('request','query');delateGetter('request','method')delateGetter('response','body');delateSetter('response','body');复制代码

相信看了之后,对这个实现逻辑有了一个比较清晰的认知。

一些中间件的实现

看完koa的源码,我们可以知道koa本身非常小,实现地比较优雅,可以通过写中间件来实现自己想要的。

常用的中间件大概有:static、body-parser、router、session等。

koa-static

koa-static是一个简单的静态中间件,它的源码在,核心逻辑实现是由完成,不过我翻了一下,里面没有etag的处理。

我们自己也可以写一个最最简单的static中间件:

const path = require('path');const util = require('util');const fs = require('fs');const stat = util.promisify(fs.stat);function static(p){    return async (ctx,next)=>{        try{            p = path.join(p,'.'+ctx.path);            let stateObj = await stat(p);            console.log(p);            if(stateObj.isDirectory()){                            }else{                ctx.body = fs.createReadStream(p);            }        }catch(e){            console.log(e)            await next();        }    }}    复制代码

body-parser

基础代码如下:

function bodyParser(){    return async (ctx,next)=>{        await new Promise((resolve,reject)=>{            let buffers = [];            ctx.req.on('data',function(data){                buffers.push(data);            });            ctx.req.on('end',function(){                ctx.request.body = Buffer.concat(buffers)                resolve();            });        });        await next();    }}module.exports = bodyParser;复制代码

无非Buffer.concat(buffers)会有几种情况需要处理一下,如form、json、file等。

在中,它用co-body包装了一层。

form和json的处理相对比较容易:

querystring.parse(buff.toString()); // form的处理JSON.parse(buff.toString()); // json的处理复制代码

这里需要说一下的是,file是如何处理的:

这里需要封装一个Buffer.split方法,来得到中间的几块内容,再进行切割处理。

Buffer.prototype.split = function(sep){    let pos = 0;    let len = Buffer.from(sep).length;    let index = -1;    let arr = [];    while(-1!=(index = this.indexOf(sep,pos))){        arr.push(this.slice(pos,index));        pos = index+len;    }    arr.push(this.slice(pos));    return arr;}复制代码
// 核心实现let type = ctx.get('content-type');let buff = Buffer.concat(buffers);let fields = {}if(type.includes('multipart/form-data')){    let sep = '--'+type.split('=')[1];    let lines = buff.split(sep).slice(1,-1);    lines.forEach(line=>{        let [head,content] = line.split('\r\n\r\n');        head = head.slice(2).toString();        content = content.slice(0,-2);        let [,name] = head.match(/name="([^;]*)"/);        if(head.includes('filename')){            // 取除了head的部分            let c = line.slice(head.length+6);            let p = path.join(uploadDir,Math.random().toString());            require('fs').writeFileSync(p,c)            fields[name] = [{
path:p}]; } else { fields[name] = content.toString(); } })}ctx.request.fields = fields;复制代码

当然像koa-better-body里面用的file处理,并没有使用split。它用了

截取操作都是在文件中处理的。

koa-router

基础用法

var Koa = require('koa');var Router = require('koa-router');var app = new Koa();var router = new Router();router.get('/', (ctx, next) => {  // ctx.router available});app  .use(router.routes())  .use(router.allowedMethods());复制代码

原理

掘金上有一篇文章:

我这边也按自己看源码的思路分析一下吧。

router.routes是返回一个中间件:

Router.prototype.routes = Router.prototype.middleware = function () {  var router = this;  var dispatch = function dispatch(ctx, next) {    debug('%s %s', ctx.method, ctx.path);    var path = router.opts.routerPath || ctx.routerPath || ctx.path;    var matched = router.match(path, ctx.method);    var layerChain, layer, i;    if (ctx.matched) {      ctx.matched.push.apply(ctx.matched, matched.path);    } else {      ctx.matched = matched.path;    }    ctx.router = router;    if (!matched.route) return next();    var matchedLayers = matched.pathAndMethod    var mostSpecificLayer = matchedLayers[matchedLayers.length - 1]    ctx._matchedRoute = mostSpecificLayer.path;    if (mostSpecificLayer.name) {      ctx._matchedRouteName = mostSpecificLayer.name;    }    layerChain = matchedLayers.reduce(function(memo, layer) {      memo.push(function(ctx, next) {        ctx.captures = layer.captures(path, ctx.captures);        ctx.params = layer.params(path, ctx.captures, ctx.params);        ctx.routerName = layer.name;        return next();      });      return memo.concat(layer.stack);    }, []);    return compose(layerChain)(ctx, next);  };  dispatch.router = this;  return dispatch;};复制代码

它做的事情就是请求进来会经过 router.match,然后将匹配到的 route 的执行函数 push 进数组,并通过 compose(koa-compose) 函数合并返回且执行。

像我们写router.get/post,所做的事就是注册一个路由,然后往this.stack里面塞入layer的实例:

另外像匹配特殊符号,如:/:id/:name,它是利用来做处理的。

看懂上面这些,再结合掘金的那篇源码分析基本能搞的七七八八。

总结

Koa的东西看上去好像比较简单似的,但是还是有很多东西没有分析到,比如源码中的proxy等。

不过根据二八法则,我们基本上只要掌握80%的源码实现就行了。

最后的最后

为我的博客打个广告,欢迎访问:

转载地址:http://izcvl.baihongyu.com/

你可能感兴趣的文章
动态链接库、静态链接库
查看>>
mysql日志问题定位实用命令
查看>>
【LeetCode】257. Binary Tree Paths
查看>>
CI在CentOS中的部署与实践LNMP
查看>>
解决LinearLayout中控件不能居右对齐
查看>>
串口传输文件 lrzsz
查看>>
MySQL SQL优化之in与range查询【转】
查看>>
jQuery 有条件排序
查看>>
有趣html5(两)----使用canvas结合剧本画在画布上的简单图(html5另一个强大)...
查看>>
可方便扩展的JIRA Rest Web API的封装调用
查看>>
strcmp的源码实现
查看>>
Java多线程7:死锁
查看>>
概率图形模型(PGM)学习笔记(四)-贝叶斯网络-伯努利贝叶斯-贝叶斯多项式...
查看>>
worker_pool的例子
查看>>
设计模式之构造者模式
查看>>
MySQL旧版本ORDER BY 方法
查看>>
人体感应模块控制LCD1602背景灯是否开启
查看>>
【转】轻松记住大端小端的含义(附对大端和小端的解释)
查看>>
(转)gethostbyname() -- 用域名或主机名获取IP地址
查看>>
Android 插件化
查看>>