手写一个 Koa2 其实真不难

前言

手写一个 Koa2 其实真的不难,如果你觉得难只是因为你没有去了解它,没去研究它的源代码。一开始使用 Koa2 搭建一个博客时觉得很方便,以至于让我对它产生敬畏之情,不敢接近它。但当我看到它的源代码时,却发现它文件不多,代码也不多就几百行。读过源代码的人会发现,其实 Koa2 只是一个 http 请求的处理库而已。由于它本身的简洁,所以很多你想要的功能都不会有,但是你可以使用众多的中间件以及第三方工具库来进行业务的处理。

源代码链接:https://github.com/koajs/koa/tree/master/lib

Koa2 之旅

源码中只有四个文件

  1. application.js // 应用主文件
  2. context.js // 上下文的处理
  3. request.js // 请求处理
  4. response.js // 响应处理

本文不会深入去第一个细节,只会对 Kos2 的大体框架的实现说一遍,下面就贴出 application.js 的实现代码,注意这个不是官方的实现代码:

/*
* @Author: zhaoxixiong
* @Date:   2019-08-22 13:49:46
* @Last Modified by:   zhaoxixiong
* @Last Modified time: 2019-08-28 08:14:06
*/
// require http node module
const http = require('http');
const context = require('./context');
const request = require('./request');
const response = require('./response');
class Application {
    constructor(){
        this.context = Object.create(context);
        this.request = Object.create(request);
        this.response = Object.create(response);
        this.middleware = []; // middleware  array
    }
    // request handler function
    callback (request, response) {
        const middlewareFn = this.handleMiddleware(this.middleware);
        // get context
        const ctx = this.createContext(request, response);
        this.handlerRequest(ctx, middlewareFn);
    }
    async handlerRequest(ctx, fn){
        // deepth handler middleware
        await fn(ctx);
        this.respond(ctx);
    }
    handleMiddleware(middleware){
        return function (ctx){
            deepHandler(0); // handler first middleware
            function deepHandler(n, next){
                const fn = middleware[n];
                if(!fn){return Promise.resolve()} // if no middleware,return Promise.resove as a result
                return Promise.resolve(fn(ctx, deepHandler.bind(null, n+1)));
            }
        }
    }
    /*
     response handler
     */
    respond(ctx){
        const res = ctx.res
        return res.end('Hollow');
    }


    /*
     * 1.create a server
     * 2.start server listen
     */
    listen(...args){
        // use bind() method to confirm this correctly
        const server = http.createServer(this.callback.bind(this));
        server.listen(...args);
    }
    /*
     * create a context that proxy req and res
     */
    createContext(req, res){
        // new context should inherit Application context
        const context = Object.create(this.context);
        // delegate req on the context
        context.req = req;
        // delegate res on the context
        context.res = res;
        // return context
        return context;
    }
    /*
     * middleware store
     */
    use(fn){
        this.middleware.push(fn);
        // return this for call chaining
        return this;
    }
}
module.exports = Application

其它三个文件的代码其实只是对请求作一些处理而已

// context.js is just an object
const context = {}
module.exports = context

// request.js is just an object
const request = {}
module.exports = request

// response.js is just an object
const response = {}
module.exports = response

application.js 做了两件事,把上下文件进行挂载,实现中间件的处理。其中 context.js 中使用了一个 delegates 模块(https://www.npmjs.com/package/delegates),源码链接:https://github.com/tj/node-delegates

通过阅读源码,我们可以发现这个模块的作用是属性访问的一个代理,可以让你在另一个对象中访问其它对象的一些属性和方法。