JS实现Koa

中间件原理

koa最核心的原理就是中间件原理:过程中通过next对下一个中间件函数的调用,利用这个特性在 next 之前对 request 进行处理,在 next 函数之后对 response 处理。

Koa 的中间件模型可以非常方便的实现后置处理逻辑。

使用示例

const Koa = require('koa')

const app = new Koa()

app.use(async (ctx, next) => {
    console.log(1);
    await next();
    console.log(2);
});

app.use(async (ctx, next) => {
    console.log(3);
    await next();
    console.log(4);
});

app.use(async (ctx, next) => {
    ctx.body = 'Hello, Koa';
    console.log('body')
});

app.listen(3001, () => {
    console.log('启动成功')
});

输出:1 3 body 4 2
代码执行顺序可以这么理解:
next()前面的代码放到一个队列:[1, 3, body],先进先出
next()后面的代码放到一个栈:[2, 4],后进先出
所以执行顺序就是 1 3 body 4 2

JS实现中间件原理

本质上是递归调用

class Koa {
    constructor() {
        // 中间件队列
        this.middlewareQueue = []
    }

    // 注册中间件,格式(ctx, next) => {}
    use(middileware) {
        this.middlewareQueue.push(middileware)
    }

    // 包装每一个中间件,实现洋葱模型
    compose(ctx) {
        // next()其实就是dispatch函数
        const dispatch = (index) => {
            // 递归结束条件
            if (index > this.middlewareQueue.length - 1) {
                return Promise.resolve()
            }

            // 第i个中间件函数
            const middlewareFn = this.middlewareQueue[index]
            // 包装下一个中间件
            const next = () => dispatch(index + 1)
            // 执行当前中间件
            const result = middlewareFn(ctx, next)
            // 返回结果给上一个中间件,这里使用Promise返回,中间件就可以定义成异步函数了
            return Promise.resolve(result)
        }

        // 从第一个中间件开始执行
        return dispatch(0)
    }

    // 测试洋葱模型
    test() {
        console.log(this.middlewareQueue)
        this.compose({})
    }
}

const app = new Koa()

app.use((ctx, next) => {
    console.log(1)
    const nextResult = next()
    console.log(2, nextResult)
    return 'middle 1'
})

app.use((ctx, next) => {
    console.log(3)
    const nextResult = next()
    console.log(4, nextResult)
    return 'middle 2'
})

app.test()

Koa原理

koa的组成部分

  1. 中间件:app.use进行注册,compose实现洋葱模型
  2. 启动http服务:app.listen,内部调用http.createServer,同时创建context
  3. context对象:挂载ctx.req、ctx.res
  4. request对象:通过ctx.req访问,原理是http.createServer((req, res) => {})的req
  5. response对象:通过ctx.res访问,原理是http.createServer((req, res) => {})的res

koa执行流程:创建服务http.CreateServer -> 创建上下文createContext -> 执行中间件compose() -> 监听端口server.listen(3000)

JS实现Koa

const http = require('http')

class Koa {
    constructor() {
        // 中间件队列
        this.middlewareQueue = []
        // 上下文
        this.context = null
    }

    // 创建上下文
    createContext(req, res) {
        this.context = {
            req, // request对象
            res // response对象
        }
    }

    // 注册中间件,格式(ctx, next) => {}
    use(middileware) {
        this.middlewareQueue.push(middileware)
    }

    // 执行中间件
    compose(ctx) {
        // next()其实就是dispatch函数,实现洋葱模型
        const dispatch = (index) => {
            // 递归结束条件
            if (index > this.middlewareQueue.length - 1) {
                return Promise.resolve()
            }

            // 第i个中间件函数
            const middlewareFn = this.middlewareQueue[index]
            // 包装下一个中间件
            const next = () => dispatch(index + 1)
            // 执行当前中间件
            const result = middlewareFn(ctx, next)
            // 返回结果给上一个中间件,这里使用Promise返回,中间件就可以定义成异步函数了
            return Promise.resolve(result)
        }

        // 从第一个中间件开始执行
        return dispatch(0)
    }

    // 启动http服务
    listen(...args) {
        const requestHandler = (req, res) => {
            console.log('request coming')
            // 创建上下文
            this.createContext(req, res)
            // 执行中间件
            this.compose(this.context)
        }

        const server = http.createServer(requestHandler)
        return server.listen(...args)
    }
}

const app = new Koa()

app.use(async (ctx, next) => {
    console.log(1)
    const nextResult = await next()
    console.log(2, nextResult)
    return 'middle 1'
})

app.use(async (ctx, next) => {
    console.log(3)
    const nextResult = await next()
    console.log(4, nextResult)
    return 'middle 2'
})

app.use(async (ctx, next) => {
    ctx.res.end('okk')
    await next()
})
app.use(async (ctx, next) => {
    console.log('request end')
})

app.listen(3002, () => {
    console.log('start server')
})