美文网首页
koa2.0的实现原理

koa2.0的实现原理

作者: Mr无愧于心 | 来源:发表于2022-04-20 20:30 被阅读0次

    koa2.0基于async await / koa1.0基于generator

    koa 的大致流程,文件结构划分很简单,只有四个文件

    • application
    • context
    • request
    • response

    测试用例

    // 引入koa主要是帮我们封装了http服务
    // 中间件机制、错误处理机制
    // ctx context上下文 
    // 原生的(req、res)
    // koa封装的request、response(有些原生对象上不存在的方法)
    const app = require('./koa');
    app.use((ctx)=>{
      console.log(ctx.req.url)// 原生node的req对象
      console.log(ctx.request.req.url)// 原生node的req对象
      //---------------------------
      console.log(ctx.request.url)// koa包装的request对象
      console.log(ctx.url)// koa包装的request对象
    })
    app.listen(3000);
    

    application封装创建应用程序函数

    let context = require('./context')
    let request = require('./request')
    let response = require('./response')
    let app = require('http');
    class Application {
      constructor(){
        this.context=Object.create(context);
        this.request=Object.create(request);
        this.response=Object.create(response);
      }
      use(fn){// 不考虑多个中间件的情况,文末会做详细介绍
        this.fn=fn
      }
      createContext(req,res){
        let ctx=this.context;
        // koa自己的request、response挂载
        ctx.request=this.request;
        ctx.response=this.response;
        // 原生的req、res挂载
        ctx.request.req=ctx.req=req;
        ctx.response.res=ctx.res=res;
        
        return ctx;
      }
      handleRequest(req,res){
        let ctx = this.createContext(req,res);
        this.fn(ctx)
      }
      listen(){
        app.createServer(handleRequest.bind(this)).listen(...arguments)
      }
    }
    
    

    context对象,拓展request、response

    1. context.js
      使用defineGetter;拦截request/response中的方法,使context中能拿到request/response中的属性
    class Delegator{
        constructor(proto, target){
            this.proto = proto;
            this.target = target;
        }
        getters(name) {
            let { proto, target }  = this;
            proto.__defineGetter__(name, function(){
              return this[target][name];
            });
            return this;
        }
        setters(name) {
            let { proto, target }  = this;
            proto.__defineSetter__(name, function(val){
              return this[target][name] = val;
            });
            return this;
        }
    }
    
    let proto = {}
    
    new Delegator(proto, 'request')
        .getters('url')
        .getters('path')
    
    new Delegator(proto, 'response')
        .getters('body')
        .setters('body')
    module.exports = proto
    
    1. request.js
      利用上文中 ctx.request.req=ctx.req=req;能拿到this.req给request对象添加属性
    let parse = require('parseurl');
    let request = {
        get url(){
            return this.req.url;
        },
        get path(){
            return parse(this.req).pathname;
        }
    }
    
    1. response.js
      利用上文中 ctx.response.res=ctx.res=res;能拿到this.res给response对象添加属性
    let response = {
        set body(value){
            this.res.statusCode = 200;
            this._body = value;
        },
        get body(){
            return this._body;
        }
    }
    

    中间件

    • 洋葱模型,调用next会执行下一个use函数
    const app = require('./koa');
    app.use((ctx,next)=>{
      console.log(1)
      next()
      console.log(2)
    })
    app.use((ctx,next)=>{
      console.log(3)
      next()
      console.log(4)
    })
    app.listen(3000);
    // 1-3-4-2
    

    koa会把所有中间件进行组合 组合成了一个大的promise,只要从开头走向结束就算完成了
    koa的中间件必须增加await next(),或者return next() 否则异步逻辑可能出错

    • application如下扩展
    this.middleware=[]
    use(middleware){
      this.middleware.push(middleware)
    }
    compose(ctx){// 组合函数,将功能组合在一起依次执行
      function dispatch(i){
        if(this.middleware.length===i){
          return Promise.resolve()
        }
        let middleware= this.middleware[i]
        return new Promise(middleware(ctx,()=>dispatch(i+1)))
      }
      return dispatch(0)// 要从第一个中间件开始执行
    }
    handleRequest(req,res){
      const ctx=this.createContext(req,res);// 将req,res统一放到ctx上
      res.statusCode=404;//默认设置返回码404
      this.compose(ctx).then(()=>{
        let body = ctx.body;
        if(body){
          res.end(body)//返回最终结果响应给用户
        }else{
          res.end('Not Found')
        }
      })
    }
    

    错误机制

    compose会捕捉中间件中的错误

    compose(ctx){
      function dispatch(i){
        if(this.middleware.length===i){
          return Promise.resolve()
        }
        let middleware= this.middleware[i]
        try{
          return new Promise(middleware(ctx,()=>dispatch(i+1)))
        }catch(err){
           return Promise.reject(err)
        }
      }
      return dispatch(0)// 要从第一个中间件开始执行
    }
        
    handleRequest(req, res) {
            ...
      const onerror = err => ctx.onerror(err);//如果中间件中有报错就会触发ctx.onerror
      // 通过 catch 调用 context 中的错误处理
      this.compose(ctx, this.middlewares).then(...).catch(onerror)
    }
    

    context中的错误对象

    let proto = {
        onerror(err) {
            if (null == err) return;
            this.app.emit('error', err, this);// event模块的emit会触发app.on('error',()=>{ //错误处理 })
            const { res } = this;
            if (typeof res.getHeaderNames === 'function') {
                res.getHeaderNames().forEach(name => res.removeHeader(name));
            }
            // respond
            this.type = 'text/plain;charset=utf-8';
            this.status = err.status;
            res.end(err.message);
        }
    }
    

    相关文章

      网友评论

          本文标题:koa2.0的实现原理

          本文链接:https://www.haomeiwen.com/subject/qwjjqftx.html