koa 中间件式HTTP服务实现(2)

在使用 koa 框架时,我们会去监听一个接口去启动服务,那~ 就是这句话

app.listen(PORT, () => {
  console.log(`the web server is starting at port ${PORT}`);
});

在这过程中,koa 是如何实现它的呢?当然就需要借助到我们 node.js 原生 http 模块

http 模块使用

服务容器 server~,先大概看一下,这个服务容器大概就是用来放一些 处理请求的逻辑和响应请求的回调等就是服务内容。

  • 建立了通信连接
  • 指定了通信接口
  • 提供了可自定内容服务容器,即服务的回调函数的容器
const http = require('http');
const PORT = 3001;
const server = http.createServer((req, res) => {
    // TODO 容器内容
    // TODO 服务回调内容
})
server.listen(PORT, function() {
  console.log(`the server is started at port ${PORT}`)
})

然后是关于服务回调的内容

  • 解析服务的请求 req
  • 对请求内容作出响应 res
const router = (req, res) => {
  res.end(`this page url = ${req.url}`);
}

req: 是服务回调中的第一个参数,主要是提供了HTTP请求request的内容和操作内容的方法。

res: 是服务回调中的第二个参数,主要是提供了HTTP响应response的内容和操作内容的方法。如果请求结束,一定要执行响应 res.end(),要不然请求会一直等待阻塞,直至连接断掉页面崩溃。

最后是整体的一个实现,通过以上的描述,主要HTTP服务内容是在 “服务回调” 中处理的,那我们来根据不同连接拆分一下,就形成了路由router,根据路由内容的拆分,就形成了控制器 controller

const http = require('http');
const PORT = 3001;

// 控制器
const controller = {
  index(req, res) {
    res.end('This is index page')
  },
  home(req, res) {
    res.end('This is home page')
  },
  _404(req, res) {
    res.end('404 Not Found')
  }
}

// 路由器
const router = (req, res) => {
  if( req.url === '/' ) {
    controller.index(req, res)
  } else if( req.url.startsWith('/home') ) {
    controller.home(req, res)
  } else {
    controller._404(req, res)
  }
}

// 服务
const server = http.createServer(router)
server.listen(PORT, function() {
  console.log(`the server is started at port ${PORT}`)
})

知道了 http 模块的使用,那么就来看看 koa.js 是怎么利用它实现的吧~~

服务模块封装~

const http = require('http'); // 这是引用原生 node.js http
const Emitter = require('events'); // 引用原生 node.js events 事件模块

class WebServer extends Emitter {

  constructor() {
    super();
    // 中间件数组
    this.middleware = [];
    // 上下文对象
    this.context = Object.create({});
  }

  /**
   * 服务事件监听
   * @param {*} args 
   */
  listen(...args) {
    // 创建一个服务容器 放入回调
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }

  /**
   * 注册使用中间件
   * @param {Function} fn 
   */
  use(fn) {
    if (typeof fn === 'function') {
      this.middleware.push(fn);
    }
  }

  /**
   * 中间件总回调方法
   */
  callback() {
    let that = this;

    if (this.listeners('error').length === 0) {
      this.on('error', this.onerror);
    }

    // 定义一个处理请求的方法
    const handleRequest = (req, res) => {
      // 创建一个上下文
      let context = that.createContext(req, res);
      // 循环中间件数组,cb就是某个中间件,执行每个中间件
      this.middleware.forEach((cb, idx) => {
        try {
          cb(context);
        } catch (err) {
          that.onerror(err);
        }
        // 到了最后就结束请求
        if (idx + 1 >= this.middleware.length) {
          if (res && typeof res.end === 'function') {
            res.end();
          }
        }
      });
    };
    return handleRequest;
  }

  /**
   * 异常处理监听
   * @param {EndOfStreamError} err 
   */
  onerror(err) {
    console.log(err);
  }

  /**
   * 创建通用上下文
   * @param {Object} req 
   * @param {Object} res 
   */
  createContext(req, res) {
    let context = Object.create(this.context);
    context.req = req;
    context.res = res;
    return context;
  }
}

module.exports = WebServer;

然后最后是对服务的使用

const WebServer = require('./index');

const app = new WebServer();
const PORT = 3001;

app.use(ctx => {
  ctx.res.write('<p>line 1</p>');
});

app.use(ctx => {
  ctx.res.write('<p>line 2</p>');
});

app.use(ctx => {
  ctx.res.write('<p>line 3</p>');
});

app.listen(PORT, () => {
  console.log(`the web server is starting at port ${PORT}`);
});

这时候,还没把之前(1)的东西加进来,即中间件的执行机制

那是少了哪一部分呢,当然是 compose 这块!即洋葱模式的执行方式,先进后出呗!

const compose = require('./compose');

 那再想一想,什么时候去调用它呢?

 这还用想~ 当然是在处理请求的时候生成的中间链并执行咯~

 将上面的callback改成这样~

callback() {
    if (this.listeners('error').length === 0) {
      this.on('error', this.onerror);
    }

    const handleRequest = (req, res) => {
      let context = this.createContext(req, res);
      let middleware = this.middleware;
      // 执行中间件
      compose(middleware)(context).catch(err => this.onerror(err));
    };
    return handleRequest;
  }

总的源码如下~

compose.js

module.exports = compose;

function compose(middleware) {
  if (!Array.isArray(middleware)) {
    throw new TypeError('Middleware stack must be an array!');
  }

  return function(ctx, next) {
    let index = -1;

    return dispatch(0);

    function dispatch(i) {
      if (i < index) {
        return Promise.reject(new Error('next() called multiple times'));
      }
      index = i;

      let fn = middleware[i];

      if (i === middleware.length) {
        fn = next;
      }

      if (!fn) {
        return Promise.resolve();
      }

      try {
        return Promise.resolve(fn(ctx, () => {
          return dispatch(i + 1);
        }));
      } catch (err) {
        return Promise.reject(err);
      }
    }
  };
}

index.js // 这个就把它想成是 koa.js 框架的入口吧

const http = require('http');
const Emitter = require('events');
const compose = require('./compose');

/**
 * 通用上下文
 */
const context = {
  _body: null,

  get body() {
    return this._body;
  },

  set body(val) {
    this._body = val;
    this.res.end(this._body);
  }
};

class SimpleKoa extends Emitter {
  constructor() {
    super();
    this.middleware = [];
    this.context = Object.create(context);
  }

  /**
   * 服务事件监听
   * @param {*} args
   */
  listen(...args) {
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }

  /**
   * 注册使用中间件
   * @param {Function} fn
   */
  use(fn) {
    if (typeof fn === 'function') {
      this.middleware.push(fn);
    }
  }

  /**
   * 中间件总回调方法
   */
  callback() {
    if (this.listeners('error').length === 0) {
      this.on('error', this.onerror);
    }

    const handleRequest = (req, res) => {
      let context = this.createContext(req, res);
      let middleware = this.middleware;
      // 执行中间件
      compose(middleware)(context).catch(err => this.onerror(err));
    };
    return handleRequest;
  }

  /**
   * 异常处理监听
   * @param {EndOfStreamError} err
   */
  onerror(err) {
    console.log(err);
  }

  /**
   * 创建通用上下文
   * @param {Object} req
   * @param {Object} res
   */
  createContext(req, res) {
    let context = Object.create(this.context);
    context.req = req;
    context.res = res;
    return context;
  }
}

module.exports = SimpleKoa;

example 最后引入这个 koa.js 框架即 index.js 测试下~

const SimpleKoa = require('./index');

const app = new SimpleKoa();
const PORT = 3001;

app.use(async ctx => {
  ctx.body = '<p>this is a body</p>';
});

app.listen(PORT, () => {
  console.log(`the web server is starting at port ${PORT}`);
});

这么一来~ 一个最简单的 koa.js 框架就成了

 

https://chenshenhai.github.io/koajs-design-note/note/chapter01/07.html 原文学习

 

 

猜你喜欢

转载自www.cnblogs.com/cxz520/p/11775596.html
今日推荐