zoukankan      html  css  js  c++  java
  • Koa原理和封装

    相关文章
    最基础
    实现一个简单的koa2框架
    实现一个简版koa
    koa实践及其手撸

    Koa源码只有4个js文件

    • application.js:简单封装http.createServer()并整合context.js
    • context.js:代理并整合request.js和response.js
    • request.js:基于原生req封装的更好用
    • response.js:基于原生res封装的更好用

    如果我们要封装一个Koa,
    需要实现use加载中间件,
    next下一个中间件,并且是环形的,
    中间件是promise的

    ctx=>对应常用的是 body(可读写)/ url(只读)/ method(只读)

    // request.js
    const request = {
      get url() {
        return this.req.url;
      },
      set url(val) {
        this.req.url = val;
      }
    };
    
    module.exports = request;
    
    // response.js
    const response = {
      get body() {
        return this._body;
      },
      set body(data) {
        this._body = data;
      },
      get status() {
        return this.res.statusCode;
      },
      set status(statusCode) {
        if (typeof statusCode !== 'number') {
          throw new Error('statusCode 必须为一个数字');
        }
        this.res.statusCode = statusCode;
      }
    };
    
    module.exports = response;
    
    // context.js
    const context = {
      get url() {
        return this.request.url;
      },
      set url(val) {
        this.request.url = val;
      },
      get body() {
        return this.response.body;
      },
      set body(data) {
        this.response.body = data;
      },
      get status() {
        return this.response.statusCode;
      },
      set status(statusCode) {
        if (typeof statusCode !== 'number') {
          throw new Error('statusCode 必须为一个数字');
        }
        this.response.statusCode = statusCode;
      }
    };
    module.exports = context;
    
    const Emitter = require('events');
    const http = require('http');
    
    // 引入 context request, response 模块
    const context = require('./context');
    const request = require('./request');
    const response = require('./response');
    
    class Application extends Emitter {
      /* 构造函数 */
      constructor() {
        super();
        this.context = Object.create(context);
        this.request = Object.create(request);
        this.response = Object.create(response);
        // 保存所有的中间件函数
        this.middlewares = [];
      }
      // 开启 http server 并且传入参数 callback
      listen(...args) {
        const server = http.createServer(this.callback());
        return server.listen(...args);
      }
      use(fn) {
        // this.callbackFunc = fn;
        // 把所有的中间件函数存放到数组里面去
        this.middlewares.push(fn);
        return this;
      }
      callback() {
        return (req, res) => {
    
          // 创建ctx
          const ctx = this.createContext(req, res);
          // 响应内容
          const response = () => this.responseBody(ctx);
    
          // 响应时 调用error函数
          const onerror = (err) => this.onerror(err, ctx);
    
          //调用 compose 函数,把所有的函数合并
          const fn = this.compose();
          return fn(ctx).then(response).catch(onerror);
        }
      }
      /**
         监听失败,监听的是上面的catch
       */
      onerror(err) {
        if (!(err instanceof Error)) throw new TypeError(util.format('non-error thrown: %j', err));
    
        if (404 == err.status || err.expose) return;
        if (this.silent) return;
    
        const msg = err.stack || err.toString();
        console.error();
        console.error(msg.replace(/^/gm, '  '));
        console.error();
      }
      /*
       构造ctx
       @param {Object} req实列
       @param {Object} res 实列
       @return {Object} ctx实列
      */
      createContext(req, res) {
        // 每个实列都要创建一个ctx对象
        const ctx = Object.create(this.context);
        // 把request和response对象挂载到ctx上去
        ctx.request = Object.create(this.request);
        ctx.response = Object.create(this.response);
        ctx.req = ctx.request.req = req;
        ctx.res = ctx.response.res = res;
        return ctx;
      }
      /*
       响应消息
       @param {Object} ctx 实列
      */
      responseBody(ctx) {
        const content = ctx.body;
        if (typeof content === 'string') {
          ctx.res.setHeader('Content-Type', 'text/pain;charset=utf-8')
          ctx.res.end(content);
        } else if (typeof content === 'object') {
          ctx.res.setHeader('Content-Type', 'text/json;charset=utf-8')
          ctx.res.end(JSON.stringify(content));
        }
      }
      /*
       把传进来的所有的中间件函数合并为一个中间件
       @return {function}
      */
       compose(){
          let middlewares = this.middlewares
          return function(ctx){
            return dispatch(0)
            function dispatch(i){
               let fn = middlewares[i]
               if(!fn){
                  return Promise.resolve()
               }
               return Promise.resolve(fn(ctx, function next(){
                  return dispatch(i+1)
               }))
            }
          }
        } 
    }
    
    module.exports = Application;
    
    // 使用
    const testKoa = require('./application');
    const app = new testKoa();
    
    app.use((ctx) => {
      str += 'hello world'; // 没有声明该变量, 所以直接拼接字符串会报错
      ctx.body = str;
    });
    
    app.on('error', (err, ctx) => { // 捕获异常记录错误日志
      console.log(err);
    });
    
    app.listen(3000, () => {
      console.log('listening on 3000');
    });
    

    优化
    如果有一个中间件写了两个next,会执行两次,需要通过判断next的总执行次数和中间件的长度,如果不一样,就要报错

    环形【洋葱】有什么好处
    上面的洋葱圈可能没看懂,上一个简易版的

    var arr = [function(next){
       console.log(1)
       next()
       console.log(2)
    },function(next){
       console.log(3)
       next()
       console.log(4)
    }]
    var i = 0;
    function init(){
       arr[i](function(){
    	i++
    	if(arr[i]){
    	   init()
    	}
       })
    }
    init()
    // 1342
    

    为什么是1342
    上面的代码打个断点就知道了

    // 这样应该看得懂吧
    function(){
       console.log(1)
       var next = function(){
          console.log(3)
          var next = ...
          console.log(4)
       }
       next()
       console.log(2)
    }
    

    在以前不是express设计的框架,整个请求到响应结束是链结构的,一个修改响应的插件就需要放到最后面,但是有个环形的设计,只要把要修改响应的代码写到next执行后就行了,对于开发者也是,获取请求的数据,修改请求的数据,next,查数据库,响应body

    文件访问中间件

    module.exports = (dirPath = "./public") => {
        return async (ctx, next) => {
            if (ctx.url.indexOf("/public") === 0) {
                // public开头 读取文件
                const url = path.resolve(__dirname, dirPath);
                const fileBaseName = path.basename(url);
                const filepath = url + ctx.url.replace("/public", ""); 
                    console.log(filepath);
                // console.log(ctx.url,url, filepath, fileBaseName) 
                try {
                    stats = fs.statSync(filepath);
                    if (stats.isDirectory()) {
                        const dir = fs.readdirSync(filepath);
                        const ret = ['<div style="padding-left:20px">'];
                        dir.forEach(filename => {
                            console.log(filename);
                            // 简单认为不带小数点的格式,就是文件夹,实际应该用statSync 
                            if (filename.indexOf(".") > -1) {
                                ret.push(
                                    `<p><a style="color:black" href="${
                                    ctx.url
                                    }/${filename}">${filename}</a></p>`
                                );
                            } else {
                                // 文件
                                ret.push(
                                    `<p><a href="${ctx.url}/${filename}">${filename}</a></p>`
                                );
                            }
                        });
                        ret.push("</div>");
                        ctx.body = ret.join("");
                    } else {
                        console.log("文件");
                        const content = fs.readFileSync(filepath);
                        ctx.body = content;
                    }
                } catch (e) {
                    // 报错了 文件不存在
                    ctx.body = "404, not found";
                }
            } else {
                // 否则不是静态资源,直接去下一个中间件
                await next();
            }
        }
    }
    
    // 使用
    const static = require('./static') 
    app.use(static(__dirname + '/public'));
    

    路由中间件

    class Router {
        constructor() {
            this.stack = [];
        }
        // 每次定义一个路由,都注册一次
        register(path, methods, middleware) {
            let route = { path, methods, middleware }
            this.stack.push(route);
        }
        // 现在只支持get和post,其他的同理 
        get(path, middleware) {
            this.register(path, 'get', middleware);
        }
        post(path, middleware) {
            this.register(path, 'post', middleware);
        }
          //调用
        routes() {
            let stock = this.stack;
            return async function (ctx, next) {
                let currentPath = ctx.url;
                let route;
                for (let i = 0; i < stock.length; i++) {
                    let item = stock[i];
                    if (currentPath === item.path && item.methods.indexOf(ctx.method) >= 0) {
                        // 判断path和method
                        route = item.middleware; break;
                    }
                }
                if (typeof route === 'function') {
                    route(ctx, next);
                    return;
                }
                await next();
            };
        }
    }
    
    module.exports = Router;
    
    // 使用
    const Koa = require('Koa')
    const Router = require('./router')
    const app = new Koa()
    const router = new Router();
    router.get('/index', async ctx => { ctx.body = 'index page'; });
    router.get('/post', async ctx => { ctx.body = 'post page'; });
    router.get('/list', async ctx => { ctx.body = 'list page'; });
    router.post('/index', async ctx => { ctx.body = 'post page'; });
    // 路由实例输出父中间件 
    app.use(router.routes());
    

    下一篇mongodb插件mongoose的使用

  • 相关阅读:
    vue-element-admin 权限的添加
    vue 图标通过组件的方式引用步骤
    linux系统环境下配置vue项目运行环境
    5.5 卷积神经网络(LeNet)
    5.4 池化层
    5.3 多输入通道和多输出通道
    5.2 填充和步幅
    html && CSS
    P2827 [NOIP2016 提高组] 蚯蚓
    5.1 二维卷积层
  • 原文地址:https://www.cnblogs.com/pengdt/p/12072519.html
Copyright © 2011-2022 走看看