美文网首页
手撸一个Koa框架

手撸一个Koa框架

作者: 语文化及 | 来源:发表于2020-06-14 23:38 被阅读0次

为了更好的了解Koa实现的原理,我们会自己手写一个带核心功能的Koa框架。
我们先看一下如果使用原生的node.js api 是如何实现一个http server 的。

const http = require('http')

const app = http.createServer((req, rsp) => {
    rsp.writeHead(200, { 'Content-Type': 'text/plain'})
    rsp.end('Hello world')
})

app.listen(3000, () => {
    console.log('listening at port 3000')
})

可以看到,createServer 方法的callback 中提供两个参数req 和 rsp 分别代表 请求体和返回体,我们可以通过对rsp返回体对象进行设置header返回头的各种参数,最后返回的数据会放在end 方法当中,可以是普通的字符串,json,xml,图片或其他数据流。

实现koa框架的步骤

实现一个koa框架主要需要用到以下的文件:

  1. request.js, 主要封装原生nodejs中urlmethod的getter参数.

  2. response.js, 主要提供了koa中的body属性的getter 和setter。

  3. context.js,对应koa中ctx参数类型 。原生nodejs的api中,主要通过req和rsp操纵请求参数和返回内容。Koa中把请求和返回都融合到了ctx对应的context类中。

  4. lzh_koa.js,对应koa中Koa类,是这个自己实现的框架的主类,提供各种主要的方法如 listen,use以及内部的compose方法,用于把koa调用use方法之后的各个中间件融合到一起的功能。

开始实现

1.request.js

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

2.response.js

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

3.context.js, 支持从上面自己封装的request和response中提取以及设置对应的参数

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
    } 
};

4.lzh_koa.js

compose 函数

这个类当中有一个高阶函数compose,用于把middlewares 里边的每一个中间件负责装配到一起。

compose(middlewares) {
        return function (ctx) {
          // 执行第0个
            return dispatch(0);
            function dispatch(i) {
                let fn = middlewares[i];
                if (!fn) {
                    return Promise.resolve();
                }
                return Promise.resolve(fn(ctx, function next() {
                    // promise完成后,再执行下一个
                    return dispatch(i + 1);
                })
                );
            }
        };
    }

首先middlewares是传进来的中间件的数组, 而每一个中间件都是以闭包的形式 (ctx, next) => { // 中间件实现代码} 传进来的,所以可以把它看成是一个装满我们需要的中间件的数组。

我们会使用递归的方式完成中间件函数的装配,之所以考虑使用递归的方式因为每一个next 参数传入的值刚好是下个dispatch的返回值, 作为递归体function dispatch(i)用于按顺序递归调用传进来的中间件。而递归结束条件则是当每一个中间件都被Promise装配过了,如下所示:

let fn = middlewares[i];
if (!fn) { return Promise.resolve() }

为了方便理解,下面是一个具体些的例子,假如有三个中间件分别是fn1 fn2 fn3 分别被装进middlewares数组里边,那被compose数组装配完后的的函数就会长成下边这个样子。

return Promise.resolve(fn1(ctx, function next() {
             // promise完成后,再执行下一个
            return  Promise.resolve( fn2(ctx, function next () {
                   return Promise.resolve( fn3(ctx, function next () {} ))
                        }))
         }))

createContext 函数

// 构建上下文
    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
    }

可以看到这个函数里边用到了我们之前声明的 context request response 在这里用上了,主要我们会把 req 和 res 集中到ctx里边进行处理。

use 函数

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

use 函数唯一的功能就是帮我们把中间件都加到middlewares 数组当中。

listen 函数

listen(...args) {
        const server = http.createServer(async (req, res) => {
            // 
            const ctx = this.createContext(req, res)
            
            const func = this.compose(this.middlewares)

            await func(ctx)

            res.end(ctx.body)
            // this.callback(req, res)
        })
        server.listen(...args)
  }

仿照Koa的写法,最后会有一个listen 函数对服务器端口进行监听,这里会首先把请求和返回体都合并到ctx对象中,再使用之前做好的compose方法对中间件数组进行合并供后面的统一调用 ( await func(ctx))。

最后一步就是调用res.end(ctx.body) 把相应返回了。

测试使用

最后我们起一个新的js文件进行测试使用.

const Koa = require('./lzh_koa')
const app = new Koa()
const delay = () => Promise.resolve(resolve => setTimeout(() => resolve(), 2000));
app.use(async (ctx, next) => {
    ctx.body = "1";
    await next();
    ctx.body += "5";
});
app.use(async (ctx, next) => {
    ctx.body += "2";
    await delay();
    await next();
    ctx.body += "6";
});
app.use(async (ctx, next) => {
    ctx.body += "3";
});

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

输出顺序为: 12365, 与Koa框架实现一致。

说在最后

至此我们自己就实现了一个带核心功能的Koa 框架, 当然里边还有很多细节的功能例如设置ctx返回类型等没有实现,但都可以通过扩展reponse和request类达到。

Demo地址放在 https://github.com/lzhlewis2015/lzh_koa .

相关文章

  • 手撸一个Koa框架

    为了更好的了解Koa实现的原理,我们会自己手写一个带核心功能的Koa框架。我们先看一下如果使用原生的node.js...

  • koa2 入门教程

    koa koa 中文koa-generator 简介 koa 是一个新的 web 框架, 由 express 原班...

  • LLogger 3

    2018/3/26 吃了一肚子生菜。。。 koa 很ok 撸了一把koa,撸得神清气爽= _= 2018/3/22...

  • 起点

    打算自己手撸一个博客系统,练练手。后端使用go的gin框架+mysql,前端手撸js吧。 应该会挺丑,哈哈~~

  • Koa2核心原理

    解析Koa2核心原理(手写KOA框架并解析) 前言:相对于express框架,koa框架只是提供了async/aw...

  • Node.js Koa框架之Response/Request 委

    目录 Koa框架 Response 、Request 委托 1. Koa框架 —— Koa 是包含一系列中间件...

  • 【融职培训】Web前端学习 第5章 node基础教程6 Koa基

    一、Koa框架概述 Koa是一个基于Node的web服务器开发框架,通过Koa我们可以更便捷第开发web服务器。不...

  • Koa2: 处理文件上传模块koa-body

    koa-bodyparser在Koa框架中,处理post请求数据的有一个koa-bodyparser模块,但是这个...

  • trello01:后端搭建

    开发依赖: koa : 后端的主框架。 koa-router:基于Koa的路由。 koa-static-cache...

  • 手撸一个RPC框架

    如何调用他人的远程服务? 由于各服务部署在不同机器,服务间的调用免不了网络通信过程,服务消费方每调用一个服务都要写...

网友评论

      本文标题:手撸一个Koa框架

      本文链接:https://www.haomeiwen.com/subject/plyqxktx.html