[路飞]_Node.js_Koa框架简单实现

105 阅读2分钟

Koa

简介

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。

Koa框架

简单实现

首先我们把平时写的koa代码写上

index.js

const Koa = require('./koa')

const app = new Koa()

app.use((req, res) => {
    res.end('Hello Koa')
})

app.listen('3000', () => {
    console.log('listen to 3000')
})

编写koa代码

const http = require('http')

class Koa {
    listen(...args) {
        // 创建服务
        const server = http.createServer((req, res) => {
            this.callback(req, res)
        })
        // 监听端口
        server.listen(...args)
    }

    use(callback) {
        this.callback = callback
    }
}

module.exports = Koa

引入上下文

我们知道在使用app.use的时候回调函数的参数应该是ctx上下文而不是(req,res)的形式。

index.js

app.use(ctx => ctx.body = 'hello !')

这时候就需要引入上下文,将req,res挂载到ctx上。

request.js

module.exports = {
    get url() {
        return this.req.url
    },

    get method() {
        return this.req.method.toLowerCase()
    }
}

response.js

module.exports = {
    get body() {
        return this._body
    },

    set body(val) {
        this._body = val
    }
}

context.js

module.exports = {
    get url() {
        return this.request.url
    },

    get body() {
        return this.response.body
    },

    set body(val) {
        this.response.body = val
    },

    get method() {
        return this.request.method
    }
    
}

koa.js

const http = require('http')

const context = require('./context')
const request = require('./request')
const response = require('./response')

class Koa {
    listen(...args) {
        // 创建服务
        const server = http.createServer((req, res) => {
            // 创建上下文
            const ctx = this.createContext(req, res)

            this.callback(ctx)

            res.end(ctx.body)
        })
        // 监听端口
        server.listen(...args)
    }

    use(callback) {
        this.callback = callback
    }

    createContext(req, res) {
        const ctx = Object.create(context)
        ctx.request = Object.create(request)
        ctx.response = Object.create(response)

        ctx.req = ctx.request.req = req
        ctx.res = ctx.response.res = res
        return ctx
    }
}

module.exports = Koa

中间件实现

在日常开发中我们需要在执行程序之前,需要做一些鉴权,日志记录,事物等一些操作,这时候就需要引入一个概念——切面编程(AOP)。

比如现在有两个app.use(),那么在正常情况下,只会执行后面一个use,前面一个use的callback会被覆盖。那么要达到前面回调函数不被覆盖,也能正常执行,我们需要将函数组合。

commpose.js

const add = (x, y) => x + y
const square = (x) => x * x

const compose = (...[first, ...other]) => (...args) => {
    let ret = first(...args)
    other.forEach(fn => {
        ret = fn(ret)
    })
    return ret
}

const fn = compose(add, square, square)

console.log(fn(1, 2))  // 81

在上面实现了一个简单的compose函数,函数会一层一层的被调用,但还没有达到洋葱圈的效果。

commpose.js

function compose(middlrewares) {
    return function() {
        return dispatch(0)
        function dispatch(i) {
            let fn = middlrewares[i]
            if (!fn) {
                return Promise.resolve()
            }

            return Promise.resolve(
                fn(function next() {
                    return dispatch(i + 1)
                })
            )
        }
    }
}

async function fn1(next) {
    console.log('fn1')
    await next()
    console.log('end fn1')
}

async function fn2(next) {
    console.log('fn2')
    await next()
    console.log('end fn2')
}

async function fn3(next) {
    console.log('fn3')
}

const finalFn = compose([fn1, fn2, fn3])
finalFn()

以上执行函数时会在next()的时候先去执行下一个函数,然后等下一个函数回调后再往下执行。

应用到koa代码中实现 index.js

app.use(async(ctx, next) => {
    ctx.body = '1'
    await next()
    ctx.body += '5'
})
app.use(async(ctx, next) => {
    ctx.body += '2'
    await next()
    ctx.body += '4'
})
app.use((ctx, next) => {
    ctx.body += '3'
})

koa.js

class Koa {
    constructor() {
        this.middlewares = []
    }
    listen(...args) {
        // 创建服务
        const server = http.createServer(async (req, res) => {
            // 创建上下文
            const ctx = this.createContext(req, res)

            // 中间件合成
            const fn = this.compose(this.middlewares)
            await fn(ctx)
            res.end(ctx.body)
        })
        // 监听端口
        server.listen(...args)
    }

    use(middleware) {
        this.middlewares.push(middleware)
    }

    // 合成函数
    compose(middlrewares) {
        return function(ctx) {
            return dispatch(0)
            function dispatch(i) {
                let fn = middlrewares[i]
                if (!fn) {
                    return Promise.resolve()
                }
    
                return Promise.resolve(
                    fn(ctx, function next() {
                        return dispatch(i + 1)
                    })
                )
            }
        }
    }
    ...
}