koa源码粗略理解以及中间件的实现

378 阅读2分钟

1、 在 koa 中常用的 app.use() 函数源码如下


    use(fn) {
        if (typeof fn !== "function")
            throw new TypeError("middleware must be a function!");
        if (isGeneratorFunction(fn)) {
            deprecate(
                "Support for generators will be removed in v3. " +
                    "See the documentation for examples of how to convert old middleware " +
                    "https://github.com/koajs/koa/blob/master/docs/migration.md",
            );
            fn = convert(fn);
        }
        debug("use %s", fn._name || fn.name || "-");
        this.middleware.push(fn);
        return this;
    }
  

this.middleware = [fn,fn,fn...],为一个中间件函数数组,每次使用 app.use(middleware()) ,都会将middleware 中间件添加进 this.middleware 数组中。

app.listen('3000') 中,会运行如下函数

    listen(...args) {
        debug("listen");
        const server = http.createServer(this.callback());
        return server.listen(...args);
    }

    callback() {
        const fn = compose(this.middleware);

        if (!this.listenerCount("error")) this.on("error", this.onerror);

        const handleRequest = (req, res) => {
            const ctx = this.createContext(req, res);
            return this.handleRequest(ctx, fn);
        };

        return handleRequest;
    }


    handleRequest(ctx, fnMiddleware) {
        const res = ctx.res;
        res.statusCode = 404;
        const onerror = err => ctx.onerror(err);
        const handleResponse = () => respond(ctx);
        onFinished(res, onerror);
        return fnMiddleware(ctx)
            .then(handleResponse)
            .catch(onerror);
    }


首先我们看下 http.createServer() 干了啥事,其核心源码如下

function Server(options, requestListener) {
    if (!(this instanceof Server)) return new Server(options, requestListener);
    
    if (typeof options === 'function') {
        requestListener = options;
        options = {};
    } else if (options == null || typeof options === 'object') {
        options = { ...options };
    } else {
        throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
    }
    
    this[kIncomingMessage] = options.IncomingMessage || IncomingMessage;
    this[kServerResponse] = options.ServerResponse || ServerResponse;
    
    net.Server.call(this, { allowHalfOpen: true });
    
    if (requestListener) {
        this.on('request', requestListener);
    }

    ....... 
    
}

核心是 this.on('request', requestListener) 这句,意识是每次来新请求都会调用 requestListener 函数,也就是 koa 源码中的 this.callback() 运行的结果。

先来看一下 const fn = compose(this.middleware); 的结果,compose 是一个将 中间件函数数组 转化成洋葱式调用函数的工具函数,compose 核心源码如下所示

    return function(context, next) {
        // last called middleware #
        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(context, dispatch.bind(null, i + 1)));
            } catch (err) {
                return Promise.reject(err);
            }
        }
    };

以 koa-bodyParser 中间件为例,其源码如下所示

    return async function bodyParser(ctx, next) {
        if (ctx.request.body !== undefined) return await next();
        if (ctx.disableBodyParser) return await next();
        try {
            const res = await parseBody(ctx);
            ctx.request.body = "parsed" in res ? res.parsed : {};
            if (ctx.request.rawBody === undefined) ctx.request.rawBody = res.raw;
        } catch (err) {
            if (onerror) {
                onerror(err, ctx);
            } else {
                throw err;
            }
        }
        await next();
    };

如果 this.middleware = [bodyParser1, bodyParser2, bodyParser3]this.middleware 经过 compose 处理后结果返回如下。

Promise.resolve() 支持链式调用

function(context, next) {
    return Promise.resolve(async function bodyParser1(context, next) {
        ...
        await async function bodyParser2(context, next) {
            ...
            await async function bodyParser3(context, next) {
                ...
                await Promise.resolve()
            }
        }
    })
}

每次有新请求,先调用 this.callback() 中的

    (req, res) => {
        const ctx = this.createContext(req, res);
        return this.handleRequest(ctx, fn);
    };
    

再调用 经过 compose 处理后的中间件函数 fn

    handleRequest(ctx, fnMiddleware) {
        const res = ctx.res;
        res.statusCode = 404;
        const onerror = err => ctx.onerror(err);
        const handleResponse = () => respond(ctx);
        onFinished(res, onerror);
        return fnMiddleware(ctx)
            .then(handleResponse)
            .catch(onerror);
    }