Koa-- 基于 Node.js 平台的下一代 web 开发框架
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。
与其对应的 Express 来比,Koa 更加小巧、精壮,本文将带大家从零开始实现 Koa 的源码,从根源上解决大家对 Koa 的困惑
本文 Koa 版本为 2.7.0, 版本不一样源码可能会有变动02、源码目录介绍
Koa 源码目录截图
通过源码目录可以知道,Koa主要分为4个部分,分别是:
application: Koa 最主要的模块, 对应 app 应用对象
context: 对应 ctx 对象
request: 对应 Koa 中请求对象
response: 对应 Koa 中响应对象
这4个文件就是 Koa 的全部内容了,其中 application 又是其中最核心的文件。我们将会从此文件入手,一步步实现 Koa 框架
03、实现一个基本服务器代码目录
my-application
const {createServer} = require('http'); module.exports = class Application { constructor() { // 初始化中间件数组, 所有中间件函数都会添加到当前数组中 this.middleware = []; } // 使用中间件方法 use(fn) { // 将所有中间件函数添加到中间件数组中 this.middleware.push(fn); } // 监听端口号方法 listen(...args) { // 使用nodejs的http模块监听端口号 const server = createServer((req, res) => { /* 处理请求的回调函数,在这里执行了所有中间件函数 req 是 node 原生的 request 对象 res 是 node 原生的 response 对象 */ this.middleware.forEach((fn) => fn(req, res)); }) server.listen(...args); } }
index.js
// 引入自定义模块 const MyKoa = require('./js/my-application'); // 创建实例对象 const app = new MyKoa(); // 使用中间件 app.use((req, res) => { console.log('中间件函数执行了~~~111'); }) app.use((req, res) => { console.log('中间件函数执行了~~~222'); res.end('hello myKoa'); }) // 监听端口号 app.listen(3000, err => { if (!err) console.log('服务器启动成功了'); else console.log(err); })
运行入口文件 index.js 后,通过浏览器输入网址访问 :3000/ , 就可以看到结果了~~
神奇吧!一个最简单的服务器模型就搭建完了。当然我们这个极简服务器还存在很多问题,接下来让我们一一解决
04、实现中间件函数的 next 方法
提取createServer的回调函数,封装成一个callback方法(可复用)
// 监听端口号方法 listen(...args) { // 使用nodejs的http模块监听端口号 const server = createServer(this.callback()); server.listen(...args); } callback() { const handleRequest = (req, res) => { this.middleware.forEach((fn) => fn(req, res)); } return handleRequest; }
封装compose函数实现next方法
// 负责执行中间件函数的函数 function compose(middleware) { // compose方法返回值是一个函数,这个函数返回值是一个promise对象 // 当前函数就是调度 return (req, res) => { // 默认调用一次,为了执行第一个中间件函数 return dispatch(0); function dispatch(i) { // 提取中间件数组的函数fn let fn = middleware[i]; // 如果最后一个中间件也调用了next方法,直接返回一个成功状态的promise对象 if (!fn) return Promise.resolve(); /* dispatch.bind(null, i + 1)) 作为中间件函数调用的第三个参数,其实就是对应的next 举个栗子:如果 i = 0 那么 dispatch.bind(null, 1)) --> 也就是如果调用了next方法 实际上就是执行 dispatch(1) --> 它利用递归重新进来取出下一个中间件函数接着执行 fn(req, res, dispatch.bind(null, i + 1)) --> 这也是为什么中间件函数能有三个参数,在调用时我们传进来了 */ return Promise.resolve(fn(req, res, dispatch.bind(null, i + 1))); } } }
使用compose函数
callback () { // 执行compose方法返回一个函数 const fn = compose(this.middleware); const handleRequest = (req, res) => { // 调用该函数,返回值为promise对象 // then方法触发了, 说明所有中间件函数都被调用完成 fn(req, res).then(() => { // 在这里就是所有处理的函数的最后阶段,可以允许返回响应了~ }); } return handleRequest; }
修改入口文件 index.js 代码
// 引入自定义模块 const MyKoa = require('./js/my-application'); // 创建实例对象 const app = new MyKoa(); // 使用中间件 app.use((req, res, next) => { console.log('中间件函数执行了~~~111'); // 调用next方法,就是调用堆栈中下一个中间件函数 next(); }) app.use((req, res, next) => { console.log('中间件函数执行了~~~222'); res.end('hello myKoa'); // 最后的next方法没发调用下一个中间件函数,直接返回Promise.resolve() next(); }) // 监听端口号 app.listen(3000, err => { if (!err) console.log('服务器启动成功了'); else console.log(err); })
此时我们实现了next方法,最核心的就是compose函数,极简的代码实现了功能,不可思议!
05、处理返回响应
定义返回响应函数respond