01、介绍
- 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
后,通过浏览器输入网址访问 http://localhost: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
function respond(req, res) { // 获取设置的body数据 let body = res.body; if (typeof body === 'object') { // 如果是对象,转化成json数据返回 body = JSON.stringify(body); res.end(body); } else { // 默认其他数据直接返回 res.end(body); } }
在callback
中调用
callback() { const fn = compose(this.middleware); const handleRequest = (req, res) => { // 当中间件函数全部执行完毕时,会触发then方法,从而执行respond方法返回响应 const handleResponse = () => respond(req, res); fn(req, res).then(handleResponse); } return handleRequest; }
修改入口文件 index.js 代码
// 引入自定义模块 const MyKoa = require('./js/my-application'); // 创建实例对象 const app = new MyKoa(); // 使用中间件 app.use((req, res, next) => { console.log('中间件函数执行了~~~111'); next(); }) app.use((req, res, next) => { console.log('中间件函数执行了~~~222'); // 设置响应内容,由框架负责返回响应~ res.body = 'hello myKoa'; }) // 监听端口号 app.listen(3000, err => { if (!err) console.log('服务器启动成功了'); else console.log(err); })
此时我们就能根据不同响应内容做出处理了~当然还是比较简单的,可以接着去扩展~
06、定义 Request 模块
// 此模块需要npm下载 const parse = require('parseurl'); const qs = require('querystring'); module.exports = { /** * 获取请求头信息 */ get headers() { return this.req.headers; }, /** * 设置请求头信息 */ set headers(val) { this.req.headers = val; }, /** * 获取查询字符串 */ get query() { // 解析查询字符串参数 --> key1=value1&key2=value2 const querystring = parse(this.req).query; // 将其解析为对象返回 --> {key1: value1, key2: value2} return qs.parse(querystring); } }
07、定义 Response 模块
module.exports = { /** * 设置响应头的信息 */ set(key, value) { this.res.setHeader(key, value); }, /** * 获取响应状态码 */ get status() { return this.res.statusCode; }, /** * 设置响应状态码 */ set status(code) { this.res.statusCode = code; }, /** * 获取响应体信息 */ get body() { return this._body; }, /** * 设置响应体信息 */ set body(val) { // 设置响应体内容 this._body = val; // 设置响应状态码 this.status = 200; // json if (typeof val === 'object') { this.set('Content-Type', 'application/json'); } }, }
08、定义 Context 模块
// 此模块需要npm下载 const delegate = require('delegates'); const proto = module.exports = {}; // 将response对象上的属性/方法克隆到proto上 delegate(proto, 'response') .method('set') // 克隆普通方法 .access('status') // 克隆带有get和set描述符的方法 .access('body') // 将request对象上的属性/方法克隆到proto上 delegate(proto, 'request') .access('query') .getter('headers') // 克隆带有get描述符的方法
09、揭秘 delegates 模块
module.exports = Delegator; /** * 初始化一个 delegator. */ function Delegator(proto, target) { // this必须指向Delegator的实例对象 if (!(this instanceof Delegator)) return new Delegator(proto, target); // 需要克隆的对象 this.proto = proto; // 被克隆的目标对象 this.target = target; // 所有普通方法的数组 this.methods = []; // 所有带有get描述符的方法数组 this.getters = []; // 所有带有set描述符的方法数组 this.setters = []; } /** * 克隆普通方法 */ Delegator.prototype.method = function(name){ // 需要克隆的对象 var proto = this.proto; // 被克隆的目标对象 var target = this.target; // 方法添加到method数组中 this.methods.push(name); // 给proto添加克隆的属性 proto[name] = function(){ /* this指向proto, 也就是ctx 举个栗子:ctx.response.set.apply(ctx.response, arguments) arguments对应实参列表,刚好与apply方法传参一致 执行ctx.set('key', 'value') 实际上相当于执行 response.set('key', 'value') */ return this[target][name].apply(this[target], arguments); }; // 方便链式调用 return this; }; /** * 克隆带有get和set描述符的方法. */ Delegator.prototype.access = function(name){ return this.getter(name).setter(name); }; /** * 克隆带有get描述符的方法. */ Delegator.prototype.getter = function(name){ var proto = this.proto; var target = this.target; this.getters.push(name); // 方法可以为一个已经存在的对象设置get描述符属性 proto.__defineGetter__(name, function(){ return this[target][name]; }); return this; }; /** * 克隆带有set描述符的方法. */ Delegator.prototype.setter = function(name){ var proto = this.proto; var target = this.target; this.setters.push(name); // 方法可以为一个已经存在的对象设置set描述符属性 proto.__defineSetter__(name, function(val){ return this[target][name] = val; }); return this; };
10、使用 ctx 取代 req 和 res
修改 my-application
const {createServer} = require('http'); const context = require('./my-context'); const request = require('./my-request'); const response = require('./my-response'); module.exports = class Application { constructor() { this.middleware = []; // Object.create(target) 以target对象为原型, 创建新对象, 新对象原型有target对象的属性和方法 this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); } use(fn) { this.middleware.push(fn); } listen(...args) { // 使用nodejs的http模块监听端口号 const server = createServer(this.callback()); server.listen(...args); } callback() { const fn = compose(this.middleware); const handleRequest = (req, res) => { // 创建context const ctx = this.createContext(req, res); const handleResponse = () => respond(ctx); fn(ctx).then(handleResponse); } return handleRequest; } // 创建context 上下文对象的方法 createContext(req, res) { /* 凡是req/res,就是node原生对象 凡是request/response,就是自定义对象 这是实现互相挂载引用,从而在任意对象上都能获取其他对象的方法 */ const context = Object.create(this.context); const request = context.request = Object.create(this.request); const response = context.response = Object.create(this.response); context.app = request.app = response.app = this; context.req = request.req = response.req = req; context.res = request.res = response.res = res; request.ctx = response.ctx = context; request.response = response; response.request = request; return context; } } // 将原来使用req,res的地方改用ctx function compose(middleware) { return (ctx) => { return dispatch(0); function dispatch(i) { let fn = middleware[i]; if (!fn) return Promise.resolve(); return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1))); } } } function respond(ctx) { let body = ctx.body; const res = ctx.res; if (typeof body === 'object') { body = JSON.stringify(body); res.end(body); } else { res.end(body); } }
修改入口文件 index.js 代码
// 引入自定义模块 const MyKoa = require('./js/my-application'); // 创建实例对象 const app = new MyKoa(); // 使用中间件 app.use((ctx, next) => { console.log('中间件函数执行了~~~111'); next(); }) app.use((ctx, next) => { console.log('中间件函数执行了~~~222'); // 获取请求头参数 console.log(ctx.headers); // 获取查询字符串参数 console.log(ctx.query); // 设置响应头信息 ctx.set('content-type', 'text/html;charset=utf-8'); // 设置响应内容,由框架负责返回响应~ ctx.body = '<h1>hello myKoa</h1>'; }) // 监听端口号 app.listen(3000, err => { if (!err) console.log('服务器启动成功了'); else console.log(err); })
到这里已经写完了 Koa 主要代码,有一句古话 - 看万遍代码不如写上一遍。 还等什么,赶紧写上一遍吧~
当你能够写出来,再去阅读源码,你会发现源码如此简单~
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
RTX 5090要首发 性能要翻倍!三星展示GDDR7显存
三星在GTC上展示了专为下一代游戏GPU设计的GDDR7内存。
首次推出的GDDR7内存模块密度为16GB,每个模块容量为2GB。其速度预设为32 Gbps(PAM3),但也可以降至28 Gbps,以提高产量和初始阶段的整体性能和成本效益。
据三星表示,GDDR7内存的能效将提高20%,同时工作电压仅为1.1V,低于标准的1.2V。通过采用更新的封装材料和优化的电路设计,使得在高速运行时的发热量降低,GDDR7的热阻比GDDR6降低了70%。
更新日志
- 群星《歌手2024 第13期》[FLAC/分轨][325.93MB]
- 阿木乃《爱情买卖》DTS-ES【NRG镜像】
- 江蕾《爱是这样甜》DTS-WAV
- VA-Hair(OriginalBroadwayCastRecording)(1968)(PBTHAL24-96FLAC)
- 博主分享《美末2RE》PS5 Pro运行画面 玩家仍不买账
- 《双城之战2》超多新歌MV发布:林肯公园再次献声
- 群星《说唱梦工厂 第11期》[320K/MP3][63.25MB]
- 群星《说唱梦工厂 第11期》[FLAC/分轨][343.07MB]
- 群星《闪光的夏天 第5期》[320K/MP3][79.35MB]
- 秀兰玛雅.1999-友情人【大旗】【WAV+CUE】
- 小米.2020-我想在城市里当一个乡下人【滚石】【FLAC分轨】
- 齐豫.2003-THE.UNHEARD.OF.CHYI.3CD【苏活音乐】【WAV+CUE】
- 黄乙玲1986-讲什么山盟海誓[日本东芝版][WAV+CUE]
- 曾庆瑜1991-柔情陷阱[台湾派森东芝版][WAV+CUE]
- 陈建江《享受男声》DTS-ES6.1【WAV】