koa源碼粗略理解以及中間件的實現

一、 在 koa 中經常使用的 app.use() 函數源碼以下git

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 數組中。github

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() 幹了啥事,其核心源碼以下bash

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() 運行的結果。app

先來看一下 const fn = compose(this.middleware); 的結果,compose 是一個將 中間件函數數組 轉化成洋蔥式調用函數的工具函數,compose 核心源碼以下所示koa

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 中間件爲例,其源碼以下所示async

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() 中的ui

(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);
    }
複製代碼
相關文章
相關標籤/搜索