Koa源码简单实现

2020-01-29  本文已影响0人  Sommouns

github地址https://github.com/sommouns/my_koa

const http = require('http')

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

class SKoa {
    constructor() {
        this.middlewares = []
    }

    // 核心目的就是开个http服务
    listen (...args) {
        const server = http.createServer(async (req, res) => {
            // 创建上下文
            let 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)
    }

    createContext (req, res) {
        // 主要就是对request和response做一层封装,然后全部存到ctx上
        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
    }

    compose (middlewares) {
        return function (ctx) {
            return dispatch(0)

            function dispatch (i) {
                let fn = middlewares[i]
                if (!fn) {
                    return Promise.resolve()
                }
                return Promise.resolve(
                    fn(ctx, function next () {
                        return dispatch(i + 1)
                    })
                )
            }
        }
    }
}

module.exports = SKoa
// 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
    }
}
// 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
    }
}

然后附上一些常用的中间件的实现


Logger

module.exports = async (ctx, next) => {
    const start = Date.now()
    await next()
    const end = Date.now()
    console.log('request ' + ctx.url + ', using ' + parseInt(end - start) + 'ms')
}

Router

Router主要的思路就是返回一个routes方法,它可以返回一个中间件。这个中间件用途也很简单,就是去匹配路由,当method和url匹配上之后,去调用后面再传入的中间接就可以了,要注意异步的问题!

class Router {
    constructor() {
        this.stack = []
    }

    register (path, method, middleware) {
        let route = { path, method, middleware }
        this.stack.push(route)
    }

    get (path, middleware) {
        this.register(path, 'get', middleware)
    }

    post (path, middleware) {
        this.register(path, 'post', middleware)
    }

    routes () {
        let stock = this.stack
        return async (ctx, next) => {
            let currentPath = ctx.url
            let route

            for (let i = 0; i < stock.length; i++) {
                let item = stock[i]
                if (currentPath === item.path && item.method.indexOf(ctx.method) >= 0) {
                    route = item.middleware
                    break
                }
            }

            if (typeof route === 'function') {
                await route(ctx, next)
                return
            }

            await next()
        }
    }
}

module.exports = Router

Static

思路很简单,就是去读路径,然后文件夹和文件分开处理(这里简单起见,只是颜色不同),文件夹就可以直接html拼接输出,文件就直接去读即可

const fs = require('fs')
const path = require('path')

module.exports = (dirPath = './public') => {
    return async (ctx, next) => {
        if (ctx.url.startsWith('/public')) {
            const url = path.resolve(__dirname, dirPath)
            const fileBaseName = path.basename(url)
            const filePath = url + ctx.url.replace('/public', '')
            console.log(filePath)

            try {
                const stats = fs.statSync(filePath)
                // const
                if (stats.isDirectory()) {
                    const dir = fs.readdirSync(filePath)
                    const ret = ['<div style="padding-left:20px">']
                    dir.forEach(filename => {
                        if (filename.indexOf('.') > -1) {
                            ret.push(
                                `<p><a style="color: black" href="${ctx.url}/${filename}">${filename}</a></p>`
                            )
                        } else {
                            ret.push(
                                `<p><a href="${ctx.url}/${filename}">${filename}</a></p>`
                            )
                        }
                    })
                    ret.push('</div>')
                    ctx.body = ret.join('')
                } else {
                    const content = fs.readFileSync(filePath)
                    ctx.body = content
                }
            } catch (e) {
                ctx.body = "404 not found"
            }
        } else {
            await next()
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读