We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
首发地址
周五组内同学讨论搞一些好玩的东西,有人提到了类似『5分钟实现koa』,『100行实现react』的创意,仔细想了以后,5分钟实现koa并非不能实现,遂有了这篇博客。
先打开koa官网,随意找出了一个代表koa核心功能的的demo就可以,如下
const Koa = require('koa'); const app = new Koa(); // x-response-time app.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; ctx.set('X-Response-Time', `${ms}ms`); }); // logger app.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}`); }); // response app.use(async ctx => { ctx.body = 'Hello World'; }); app.listen(3000);
最终要实现的效果是实现的一个5min-koa模块,直接将代码中第一行替换为const Koa = require('./5min-koa');,程序可以正常执行就可以了。
const Koa = require('./5min-koa');
通过koa官网得知,app.listen方法实际上是如下代码的简写
const http = require('http'); const Koa = require('koa'); const app = new Koa(); http.createServer(app.callback()).listen(3000);
所以我们可以先把app.listen实现出来
class Koa { constructor() {} callback() { return (req, res) => { // TODO } } listen(port) { http.createServer(this.callback()).listen(port); } }
koa的核心分为四部分,分别是
我们先来实现一个最简化版的context,如下
class Context { constructor(app, req, res) { this.app = app this.req = req this.res = res // 为了尽可能缩短实现时间,我们直接使用原生的res和req,没有实现ctx上的ctx.request ctx.response // ctx.request ctx.response只是在原生res和req上包装处理了一层 } // 实现一些demo中使用到的ctx上代理的方法 get set() { return this.res.setHeader } get method() { return this.req.method } get url() { return this.req.url } }
这样就完成了一个最基本的Context,别看小,已经够用了。 每一次有新的请求,都会创建一个新的ctx对象。
koa的中间件是一个异步函数,接受两个参数,分别是ctx和next,其中ctx是当前的请求上下文,next是下一个中间件(也是异步函数),这样想来,我们需要一个维护中间件的数组,每次调用app.use就是往数组中push一个一步函数。所以use方法实现如下
use(middleware) { this.middlewares.push(middleware) }
每次有新的请求,我们都需要把这次请求的上下文灌进数组中的每一个中间件里。单单灌进ctx还不够,还要使每个中间件都能通过next函数调用到下一个中间件。当我们调用next函数时,一般是不需要传参数的,而被调用的中间件中一定会接收到ctx和next两个参数。
调用方不需要传参,被调用方却能接到参数,这让我立刻想到bind方法,只要将每一个中间件所需要的ctx和next都提前绑定好,问题就解决了。下面的代码就是通过bind方法,将用户传入的middleware列表转换成next函数列表
let bindedMiddleware = [] for (let i = middlewares.length - 1; i >= 0; i--) { if (middlewares.length == i + 1) { // 最后一个中间件,next方法设置为Promise.resolve bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, Promise.resolve)) } else { bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, bindedMiddleware[0])) } }
最后我们就得到了一个next函数数组,也就是bindedMiddleware这个变量了。
http.createServer中的回调函数,每次接收到请求的时候会被调用,所以我们在上面callback方法的TODO位置,编写处理请求的代码, 并将上面的middleware列表转next函数列表的代码放入其中。
function handleRequest(ctx, middlewares) { if (middlewares && middlewares.length > 0) { let bindedMiddleware = [] for (let i = middlewares.length - 1; i >= 0; i--) { if (middlewares.length == i + 1) { bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, Promise.resolve)) } else { bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, bindedMiddleware[0])) } } return bindedMiddleware[0]() } else { return Promise.resolve() } }
我们简单出来下相应就好了,直接将ctx.body发送给客户端。
function handleResponse (ctx) { return function() { ctx.res.writeHead(200, { 'Content-Type': 'text/plain' }); ctx.res.end(ctx.body); } }
koa的app实例上面带有on,emit等方法,这是node events模块实现好的东西。直接让Koa类继承自events模块就好了。 我们再将上面实现出来的handleRequest和handleResponse方法放入koa类的callback方法中,得到最终我们实现的Koa,一共58行代码,如下
const http = require('http'); const Emitter = require('events'); class Context { constructor(app, req, res) { this.app = app; this.req = req; this.res = res; } get set() { return this.res.setHeader } get method() { return this.req.method } get url() { return this.req.url } } class Koa extends Emitter{ constructor(options) { super(); this.options = options this.middlewares = []; } use(middleware) { this.middlewares.push(middleware); } callback() { return (req, res) => { let ctx = new Context(this, req, res); handleRequest(ctx, this.middlewares).then(handleResponse(ctx)); } } listen(port) { http.createServer(this.callback()).listen(port); } } function handleRequest(ctx, middlewares) { if (middlewares && middlewares.length > 0) { let bindedMiddleware = []; for (let i = middlewares.length - 1; i >= 0; i--) { if (middlewares.length == i + 1) { bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, Promise.resolve)); } else { bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, bindedMiddleware[0])); } } return bindedMiddleware[0](); } else { return Promise.resolve(); } } function handleResponse (ctx) { return function() { ctx.res.writeHead(200, { 'Content-Type': 'text/plain' }); ctx.res.end(ctx.body); } } module.exports = Koa;
试试跑一下篇首的Demo,没什么问题。
简版实现,码糙理不糙,展示出了koa核心的东西,但少了错误处理,也完全没有考虑性能啥的,需要完善的地方还很多很多。
笔者在写了这个5分钟koa以后去看了koa源码,发现实现思路基本就是这样,相信经过我的这个5分钟koa的洗礼,你去看koa源码一样小菜一碟。
Done!
The text was updated successfully, but these errors were encountered:
58行代码😄
Sorry, something went wrong.
No branches or pull requests
首发地址
准备
先打开koa官网,随意找出了一个代表koa核心功能的的demo就可以,如下
最终要实现的效果是实现的一个5min-koa模块,直接将代码中第一行替换为
const Koa = require('./5min-koa');
,程序可以正常执行就可以了。Koa的核心
通过koa官网得知,app.listen方法实际上是如下代码的简写
所以我们可以先把app.listen实现出来
koa的核心分为四部分,分别是
Context
我们先来实现一个最简化版的context,如下
这样就完成了一个最基本的Context,别看小,已经够用了。
每一次有新的请求,都会创建一个新的ctx对象。
Middleware
koa的中间件是一个异步函数,接受两个参数,分别是ctx和next,其中ctx是当前的请求上下文,next是下一个中间件(也是异步函数),这样想来,我们需要一个维护中间件的数组,每次调用app.use就是往数组中push一个一步函数。所以use方法实现如下
每次有新的请求,我们都需要把这次请求的上下文灌进数组中的每一个中间件里。单单灌进ctx还不够,还要使每个中间件都能通过next函数调用到下一个中间件。当我们调用next函数时,一般是不需要传参数的,而被调用的中间件中一定会接收到ctx和next两个参数。
调用方不需要传参,被调用方却能接到参数,这让我立刻想到bind方法,只要将每一个中间件所需要的ctx和next都提前绑定好,问题就解决了。下面的代码就是通过bind方法,将用户传入的middleware列表转换成next函数列表
最后我们就得到了一个next函数数组,也就是bindedMiddleware这个变量了。
Request
http.createServer中的回调函数,每次接收到请求的时候会被调用,所以我们在上面callback方法的TODO位置,编写处理请求的代码, 并将上面的middleware列表转next函数列表的代码放入其中。
Responce
我们简单出来下相应就好了,直接将ctx.body发送给客户端。
完成Koa类的实现
koa的app实例上面带有on,emit等方法,这是node events模块实现好的东西。直接让Koa类继承自events模块就好了。
我们再将上面实现出来的handleRequest和handleResponse方法放入koa类的callback方法中,得到最终我们实现的Koa,一共58行代码,如下
试试跑一下篇首的Demo,没什么问题。
结语
简版实现,码糙理不糙,展示出了koa核心的东西,但少了错误处理,也完全没有考虑性能啥的,需要完善的地方还很多很多。
笔者在写了这个5分钟koa以后去看了koa源码,发现实现思路基本就是这样,相信经过我的这个5分钟koa的洗礼,你去看koa源码一样小菜一碟。
Done!
The text was updated successfully, but these errors were encountered: