читать оригинал
предисловие
Koa 2.x
version — самый популярный фреймворк NodeJS на данный момент,Koa 2.0
Исходный код особенно оптимизирован, в отличие отExpress
Инкапсулировано так много функций, что большинство функцийKoa
команда разработчиков (сExpress
является продуктом) и участники сообщества дляKoa
Это обеспечивается промежуточным программным обеспечением, реализованным функцией инкапсуляции NodeJS.Использование очень простое, то есть вводится промежуточное программное обеспечение, а вызовKoa
изuse
Метод используется в соответствующем положении, так что его можно использовать внутренне с помощьюctx
Чтобы реализовать некоторые функции, мы обсудим принципы реализации общего промежуточного программного обеспечения и то, как нам следует разработатьKoa
Промежуточное программное обеспечение используется самостоятельно и другими.
Введение в луковую модель Коа
На этот раз мы не будем слишком подробно анализировать принцип реализации луковой модели, а в основном проанализируем, как работает промежуточное ПО в зависимости от использования API и луковой модели.
// 洋葱模型特点
// 引入 Koa
const Koa = require("koa");
// 创建服务
const app = new Koa();
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(2);
});
app.use(async (ctx, next) => {
console.log(3);
await next();
console.log(4);
});
app.use(async (ctx, next) => {
console.log(5);
await next();
console.log(6);
});
// 监听服务
app.listen(3000);
// 1
// 3
// 5
// 6
// 4
// 2
мы знаемKoa
изuse
Метод поддерживает асинхронность, поэтому для обеспечения нормального выполнения кода согласно порядку выполнения onion-модели необходимо вызватьnext
Когда код ждет, дождитесь асинхронного завершения, прежде чем продолжить выполнение, так что мы находимся вKoa
Рекомендуется использоватьasync/await
Да, представленное промежуточное ПО все вuse
метод, с помощью которого мы можем анализировать каждыйKoa
Все промежуточное ПО возвращаетasync
функциональный.
Моделирование промежуточного программного обеспечения koa-bodyparser
хочу проанализироватьkoa-bodyparser
Принцип первой необходимости знать использование и функции,koa-bodyparser
промежуточное ПО - этоpost
Строки запроса запроса и отправки формы преобразуются в объекты и связываются вctx.request.body
Нам удобно получать значения из другого промежуточного программного обеспечения или интерфейсов, и его нужно установить заранее перед использованием.
npm install koa koa-bodyparser
Конкретное использование koa-bodyparser выглядит следующим образом:
// koa-bodyparser 的用法
const Koa = require("koa");
const bodyParser = require("koa-bodyparser");
const app = new Koa();
// 使用中间件
app.use(bodyParser());
app.use(async (ctx, next) => {
if (ctx.path === "/" && ctx.method === "POST") {
// 使用中间件后 ctx.request.body 属性自动加上了 post 请求的数据
console.log(ctx.request.body);
}
});
app.listen(3000);
Из использования мы видим, чтоkoa-bodyparser
Промежуточное программное обеспечение на самом деле является функцией, мы помещаем его вuse
реализуется, согласноKoa
характеристики, делаем вывод, чтоkoa-bodyparser
Функция должна вернуть намasync
Ниже приведен код нашей реализации моделирования.
// 文件:my-koa-bodyparser.js
const querystring = require("querystring");
module.exports = function bodyParser() {
return async (ctx, next) => {
await new Promise((resolve, reject) => {
// 存储数据的数组
let dataArr = [];
// 接收数据
ctx.req.on("data", data => dataArr.push(data));
// 整合数据并使用 Promise 成功
ctx.req.on("end", () => {
// 获取请求数据的类型 json 或表单
let contentType = ctx.get("Content-Type");
// 获取数据 Buffer 格式
let data = Buffer.concat(dataArr).toString();
if (contentType === "application/x-www-form-urlencoded") {
// 如果是表单提交,则将查询字符串转换成对象赋值给 ctx.request.body
ctx.request.body = querystring.parse(data);
} else if (contentType === "applaction/json") {
// 如果是 json,则将字符串格式的对象转换成对象赋值给 ctx.request.body
ctx.request.body = JSON.parse(data);
}
// 执行成功的回调
resolve();
});
});
// 继续向下执行
await next();
};
};
В приведенном выше коде нам нужно обратить внимание на несколько моментов, а именноnext
вызова и зачем получать данные через потоки, обрабатывать данные и вешать данные наctx.request.body
Сделать это в Обещании.
прежде всегоnext
Звоните, знаемKoa
изnext
Выполнение фактически выполняет функцию следующего промежуточного программного обеспечения, то есть следующегоuse
серединаasync
функция, чтобы убедиться, что следующий асинхронный код выполняется, прежде чем продолжить выполнение текущего кода, поэтому нам нужно использоватьawait
Ожидание, за которым следуют данные от получения до зависанияctx.request.body
Все выполняются в промисе, т.к. операция получения данных асинхронная, и весь процесс обработки данных нужно дождаться асинхронного завершения, а потом вешать данные вctx.request.body
on, гарантируется, что мы находимся в следующемuse
изasync
функция может быть вctx.request.body
чтобы получить данные, поэтому мы используемawait
Подождите, пока обещание будет выполнено, прежде чем выполнятьnext
.
моделирование промежуточного программного обеспечения koa-better-body
koa-bodyparser
Он все еще немного слаб в обработке отправки форм, потому что загрузка файлов не поддерживается, иkoa-better-body
восполняет этот недостаток, ноkoa-better-body
дляKoa 1.x
версия промежуточного ПО,Koa 1.x
Промежуточное ПО используетсяGenerator
функция, нам нужно использоватьkoa-convert
Будуkoa-better-body
Конвертировано вKoa 2.x
промежуточное ПО.
npm install koa koa-better-body koa-convert path uuid
Конкретное использование koa-better-body выглядит следующим образом:
// koa-better-body 的用法
const Koa = require("koa");
const betterBody = require("koa-better-body");
const convert = require("koa-convert"); // 将 koa 1.0 中间转化成 koa 2.0 中间件
const path = require("path");
const fs = require("fs");
const uuid = require("uuid/v1"); // 生成随机串
const app = new Koa();
// 将 koa-better-body 中间件从 koa 1.0 转化成 koa 2.0,并使用中间件
app.use(convert(betterBody({
uploadDir: path.resolve(__dirname, "upload")
})));
app.use(async (ctx, next) => {
if (ctx.path === "/" && ctx.method === "POST") {
// 使用中间件后 ctx.request.fields 属性自动加上了 post 请求的文件数据
console.log(ctx.request.fields);
// 将文件重命名
let imgPath = ctx.request.fields.avatar[0].path;
let newPath = path.resolve(__dirname, uuid());
fs.rename(imgPath, newPath);
}
});
app.listen(3000);
в приведенном выше кодеkoa-better-body
Основная функция состоит в том, чтобы сохранить файл, загруженный формой, в локальную указанную папку и повесить объект файлового потока вctx.request.fields
свойств, мы будем моделировать следующийkoa-better-body
Функциональная реализация версии на основеKoa 2.x
Промежуточное ПО для обработки загрузки файлов.
// 文件:my-koa-better-body.js
const fs = require("fs");
const uuid = require("uuid/v1");
const path = require("path");
// 给 Buffer 扩展 split 方法预备后面使用
Buffer.prototype.split = function (sep) {
let len = Buffer.from(sep).length; // 分隔符所占的字节数
let result = []; // 返回的数组
let start = 0; // 查找 Buffer 的起始位置
let offset = 0; // 偏移量
// 循环查找分隔符
while ((offset = this.indexOf(sep, start)) !== -1) {
// 将分隔符之前的部分截取出来存入
result.push(this.slice(start, offset));
start = offset + len;
}
// 处理剩下的部分
result.push(this.slice(start));
// 返回结果
return result;
}
module.exports = function (options) {
return async (ctx, next) => {
await new Promise((resolve, reject) => {
let dataArr = []; // 存储读取的数据
// 读取数据
ctx.req.on("data", data => dataArr.push(data));
ctx.req.on("end", () => {
// 取到请求体每段的分割线字符串
let bondery = `--${ctx.get("content-Type").split("=")[1]}`;
// 获取不同系统的换行符
let lineBreak = process.platform === "win32" ? "\r\n" : "\n";
// 非文件类型数据的最终返回结果
let fields = {};
// 分隔的 buffer 去掉没用的头和尾即开头的 '' 和末尾的 '--'
dataArr = dataArr.split(bondery).slice(1, -1);
// 循环处理 dataArr 中每一段 Buffer 的内容
dataArr.forEach(lines => {
// 对于普通值,信息由包含键名的行 + 两个换行 + 数据值 + 换行组成
// 对于文件,信息由包含 filename 的行 + 两个换行 + 文件内容 + 换行组成
let [head, tail] = lines.split(`${lineBreak}${lineBreak}`);
// 判断是否是文件,如果是文件则创建文件并写入,如果是普通值则存入 fields 对象中
if (head.includes("filename")) {
// 防止文件内容含有换行而被分割,应重新截取内容并去掉最后的换行
let tail = lines.slice(head.length + 2 * lineBreak.length, -lineBreak.length);
// 创建可写流并指定写入的路径:绝对路径 + 指定文件夹 + 随机文件名,最后写入文件
fs.createWriteStream(path.join(__dirname, options.uploadDir, uuid())).end(tail);
} else {
// 是普通值取出键名
let key = head.match(/name="(\w+)"/)[1];
// 将 key 设置给 fields tail 去掉末尾换行后的内容
fields[key] = tail.toString("utf8").slice(0, -lineBreak.length);
}
});
// 将处理好的 fields 对象挂在 ctx.request.fields 上,并完成 Promise
ctx.request.fields = fields;
resolve();
});
});
// 向下执行
await next();
}
}
Приведенную выше логику содержимого можно понять с помощью комментариев к коду, то есть симуляции.koa-better-body
Функциональная логика , наша главная забота заключается в том, как реализовано промежуточное программное обеспечение.Асинхронная операция, реализованная вышеуказанной функцией, все еще читает данные.Для того, чтобы дождаться окончания обработки данных, она все еще выполняется в промисе, и с использованиемawait
Подождите, обещание успешно выполненоnext
.
моделирование промежуточного программного обеспечения koa-views
Шаблон узла — это инструмент, который мы часто используем для отображения страниц на стороне сервера.koa-view
Промежуточное программное обеспечение, помогите нам быть совместимыми с этими шаблонами, сначала установите зависимые модули.
npm install koa koa-views ejs
Ниже приведен файл шаблона ejs:
<!-- 文件:index.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ejs</title>
</head>
<body>
<%=name%>
<%=age%>
<%if (name=="panda") {%>
panda
<%} else {%>
shen
<%}%>
<%arr.forEach(item => {%>
<li><%=item%></li>
<%})%>
</body>
</html>
Конкретное использование koa-views выглядит следующим образом:
// koa-views 的用法
const Koa = require("koa");
const views = require("koa-views");
const path = require("path");
const app = new Koa();
// 使用中间件
app.use(views(path.resolve(__dirname, "views"), {
extension: "ejs"
}));
app.use(async (ctx, next) => {
await ctx.render("index", { name: "panda", age: 20, arr: [1, 2, 3] });
});
app.listen(3000);
Видно, что мы использовалиkoa-views
После промежуточного программного обеспечения пустьctx
болееrender
Метод помогает нам отображать шаблон и реагировать на страницу, просто используйте его напрямую.ejs
автономныйrender
Метод тот же, и это видно из использованияrender
Метод выполняется асинхронно, поэтому вам нужно использоватьawait
Подождите, давайте смоделируем и реализуем простую версиюkoa-views
промежуточное ПО.
// 文件:my-koa-views.js
const fs = require("fs");
const path = require("path");
const { promisify } = require("util");
// 将读取文件方法转换成 Promise
const readFile = promisify(fs.radFile);
// 到处中间件
module.exports = function (dir, options) {
return async (ctx, next) => {
// 动态引入模板依赖模块
const view = require(options.extension);
ctx.render = async (filename, data) => {
// 异步读取文件内容
let tmpl = await readFile(path.join(dir, `${filename}.${options.extension}`), "utf8");
// 将模板渲染并返回页面字符串
let pageStr = view.render(tmpl, data);
// 设置响应类型并响应页面
ctx.set("Content-Type", "text/html;charset=utf8");
ctx.body = pageStr;
}
// 继续向下执行
await next();
}
}
висит наctx
Вверхrender
Причина, по которой метод выполняется асинхронно, заключается в том, что внутренний файл шаблона чтения выполняется асинхронно и должен ждать, поэтомуrender
методasync
функция, которая динамически вводит шаблон, который мы используем внутри промежуточного программного обеспечения, напримерejs
, И вctx.render
используйте соответствующийrender
способ получить строку страницы после замены данных и начать сhtml
тип ответа.
Коа-статическое моделирование промежуточного программного обеспечения
Нижеkoa-static
Использование промежуточного программного обеспечения и зависимости кода следующие, которые необходимо установить перед использованием.
npm install koa koa-static mime
Конкретное использование koa-static выглядит следующим образом:
// koa-static 的用法
const Koa = require("koa");
const static = require("koa-static");
const path = require("path");
const app = new Koa();
app.use(static(path.resolve(__dirname, "public")));
app.use(async (ctx, next) => {
ctx.body = "hello world";
});
app.listen(3000);
Благодаря использованию и анализу мы знаемkoa-static
Роль промежуточного программного обеспечения заключается в том, чтобы помочь нам обрабатывать статические файлы, когда сервер получает запрос. Если мы напрямую обращаемся к имени файла, он будет искать файл и отвечать напрямую. Если такого пути к файлу нет, он будет рассматриваться как папка и папка будет найдена.index.html
, если он существует, он ответит напрямую, а если он не существует, он будет передан другому промежуточному программному обеспечению для обработки.
// 文件:my-koa-static.js
const fs = require("fs");
const path = require("path");
const mime = require("mime");
const { promisify } = require("util");
// 将 stat 和 access 转换成 Promise
const stat = promisify(fs.stat);
const access = promisify(fs.access)
module.exports = function (dir) {
return async (ctx, next) => {
// 将访问的路由处理成绝对路径,这里要使用 join 因为有可能是 /
let realPath = path.join(dir, ctx.path);
try {
// 获取 stat 对象
let statObj = await stat(realPath);
// 如果是文件,则设置文件类型并直接响应内容,否则当作文件夹寻找 index.html
if (statObj.isFile()) {
ctx.set("Content-Type", `${mime.getType()};charset=utf8`);
ctx.body = fs.createReadStream(realPath);
} else {
let filename = path.join(realPath, "index.html");
// 如果不存在该文件则执行 catch 中的 next 交给其他中间件处理
await access(filename);
// 存在设置文件类型并响应内容
ctx.set("Content-Type", "text/html;charset=utf8");
ctx.body = fs.createReadStream(filename);
}
} catch (e) {
await next();
}
}
}
Приведенная выше логика должна определить, существует ли путь, потому что все экспортируемые функцииasync
функция, поэтому мы будемstat
а такжеaccess
Преобразовано в обещание и использованоtry...catch
Захватить, позвонить, когда путь недействителенnext
Передать другому промежуточному ПО для обработки.
моделирование промежуточного программного обеспечения koa-router
существуетExpress
Во фреймворке маршрутизация встроена в фреймворк, иKoa
не встроен, это использоватьkoa-router
Он реализуется промежуточным программным обеспечением и должен быть установлен перед использованием.
npm install koa koa-router
koa-router
Функция очень мощная Ниже мы просто используем ее и моделируем ее в соответствии с используемой функцией.
// koa-router 的简单用法
const Koa = require("Koa");
const Router = require("koa-router");
const app = new Koa();
const router = new Router();
router.get("/panda", (ctx, next) => {
ctx.body = "panda";
});
router.get("/panda", (ctx, next) => {
ctx.body = "pandashen";
});
router.get("/shen", (ctx, next) => {
ctx.body = "shen";
})
// 调用路由中间件
app.use(router.routes());
app.listen(3000);
видно сверхуkoa-router
То, что экспортируется, является классом, вам нужно создать экземпляр при его использовании и вызвать экземплярroutes
метод возвращаетasync
функция для подключения, но при сопоставлении маршрута он будет основан на маршрутеget
Путь в методе сопоставляется, и внутренняя функция обратного вызова выполняется последовательно.Когда все функции обратного вызова будут выполнены, будет выполнена вся функция обратного вызова.Koa
сериалnext
, принцип такой же, как и у других промежуточных программ, я легко реализую функции, использованные выше.
// 文件:my-koa-router.js
// 控制每一个路由层的类
class Layer {
constructor(path, cb) {
this.path = path;
this.cb = cb;
}
match(path) {
// 地址的路由和当前配置路由相等返回 true,否则返回 false
return path === this.path;
}
}
// 路由的类
class Router {
constructor() {
// 存放每个路由对象的数组,{ path: /xxx, fn: cb }
this.layers = [];
}
get(path, cb) {
// 将路由对象存入数组中
this.layers.push(new Layer(path, cb));
}
compose(ctx, next, handlers) {
// 将匹配的路由函数串联执行
function dispatch(index) {
// 如果当前 index 个数大于了存储路由对象的长度,则执行 Koa 的 next 方法
if(index >= handlers.length) return next();
// 否则调用取出的路由对象的回调执行,并传入一个函数,在传入的函数中递归 dispatch(index + 1)
// 目的是为了执行下一个路由对象上的回调函数
handlers[index].cb(ctx, () => dispatch(index + 1));
}
// 第一次执行路由对象的回调函数
dispatch(0);
}
routes() {
return async (ctx, next) { // 当前 next 是 Koa 自己的 next,即 Koa 其他的中间件
// 筛选出路径相同的路由
let handlers = this.layers.filter(layer => layer.match(ctx.path));
this.compose(ctx, next, handlers);
}
}
}
Выше мы создалиRouter
класс, который определяетget
метод и, конечно же,post
подождите, мы просто реализуемget
Я имею в виду,get
Логика внутри заключается в вызовеget
Параметрическая функция метода и строка маршрутизации объединяются в объект и сохраняются в массиве.layers
, поэтому мы создаем классы, которые специально создают объекты маршрутизацииLayer
, что удобно для расширения.При совпадении маршрута мы можемctx.path
Получите строку маршрута и используйте маршрут для фильтрации объектов маршрута в массиве вызовов, которые не соответствуют маршруту, и вызовитеcompose
Метод принимает отфильтрованный массив в качестве параметраhandlers
Передайте, последовательно выполните функцию обратного вызова на объекте маршрута.
compose
Идея реализации этого метода очень важна, т.к.Koa
Используется в исходном коде для объединения промежуточного ПО, вReact
исходный код для конкатенацииredux
изpromise
,thunk
а такжеlogger
и другие модули, наша реализация это упрощенная версия, не совместимая с асинхронностью, основная идея рекурсияdispatch
Функция, каждый раз, когда функция обратного вызова следующего объекта маршрутизации в массиве вынимается и выполняется, пока не будут выполнены функции обратного вызова всех соответствующих маршрутов, выполнитьKoa
следующее промежуточное ПОnext
, обратите внимание на здесьnext
Отличие от параметров функции обратного вызова в массивеnext
, функция обратного вызова объекта маршрута в массивеnext
Представляет обратный вызов для следующего соответствующего маршрута.
Суммировать
Выше мы разобрали и смоделировали некоторое middleware, собственно разберемсяKoa
а такжеExpress
Сравнительным преимуществом является то, что он не такой тяжелый, его легко разрабатывать и использовать, а все необходимые функции могут быть реализованы соответствующим промежуточным программным обеспечением.Использование промежуточного программного обеспечения может принести нам некоторые преимущества, такие как возможность монтировать наши обработанные данные и новые методы существуютctx
вверх, удобно для спиныuse
Его также можно использовать в функции входящего обратного вызова, а также он может помочь нам справиться с некоторой общей логикой, так что он не будет использоваться во всех случаях.use
Все коллбеки обрабатываются, что сильно сокращает избыточный код, кажется, что на самом деле даетKoa
Процесс использования промежуточного программного обеспечения представляет собой типичный паттерн «декоратора», и после приведенного выше анализа я думаю, что все его понимают.Koa
«Луковая модель» и асинхронные функции позволяют разрабатывать собственное промежуточное ПО.