Что такое koa framework?
koa — это новый веб-фреймворк, основанный на узле, созданный оригинальной командой Express Framework. Для него характерна элегантность, простота, выразительность и свобода. По сравнению с экспрессом, это более легкая структура узлов, поскольку все ее функции реализуются через плагины.Этот шаблон проектирования архитектуры подключаемых модулей соответствует философии Unix.
Фреймворк koa теперь обновлен до версии 2.x.Эта статья начинается с нуля, шаг за шагом, объясняет структуру исходного кода и принцип реализации фреймворка koa2, показывает и объясняет некоторые из наиболее важных концепций в исходном коде koa2 framework, а затем научит вас, как реализовать простой koa2 framework koa2 помогает каждому изучить и понять koa2 на более глубоком уровне.После прочтения этой статьи перейдите к проверке исходного кода koa2, я думаю, ваше мышление будет очень гладкий.
В этой статье используется платформа koa2, которая отличается от koa1. koa1 использует метод выполнения генератора + co.js, в то время как koa2 использует async/await, поэтому код и демонстрация в этой статье должны работать на версии узла 8 и Если версия узла читателя ниже, рекомендуется обновить или установить babel-cli и использовать в нем узел babel для запуска кода, задействованного в этой статье.
структура исходного кода koa
На изображении выше показана папка lib исходной структуры каталогов koa2.Папка lib содержит четыре основных файла koa2: application.js, context.js, request.js, response.js.
application.js
application.js — это входной файл koa.Он экспортирует конструктор для создания экземпляров класса.Он наследует события, что дает фреймворку возможность отслеживать события и запускать события. Приложение также предоставляет некоторые часто используемые API, такие как toJSON, listen, use и т. д.
Принцип реализации listen на самом деле является инкапсуляцией http.createServer, основное внимание уделяется обратному вызову, передаваемому в этой функции, который включает слияние промежуточного программного обеспечения, обработку контекста и специальную обработку res.
использование заключается в сборе промежуточного программного обеспечения, помещении нескольких промежуточного программного обеспечения в очередь кэширования, а затем рекурсивном объединении и вызове этих серий промежуточного программного обеспечения через плагин koa-compose.
context.js
Эта часть представляет собой контекст приложения ctx из koa. По сути, это просто экспозиция объекта. Акцент делается на делегате. Это прокси. Это сделано для удобства разработчиков. Например, мы хотим получить доступ к ctx. repponse.status, но мы передаем делегат. , вы можете получить к нему прямой доступ, обратившись к ctx.status.
запрос.js, ответ.js
Эти две части представляют собой некоторые операции над собственными res и req, использующие множество синтаксиса get и set ES6, для получения заголовков или установки заголовков, установки тела и т. д., они не будут подробно представлены, если вы заинтересованы Читатели могут сами посмотреть исходный код.
Четыре модуля для реализации koa2
Общая фреймворковая структура исходного кода koa2 кратко описана выше.Далее мы реализуем koa2 фреймворк.Автор считает, что для понимания и реализации koa framework требуется реализация четырех основных модулей, а именно:
-
Инкапсулировать узел http-сервера, создать конструктор класса Koa
-
Создание запроса, ответа, объектов контекста
-
Реализация механизма промежуточного программного обеспечения и модели очистки лука
-
Перехват ошибок и обработка ошибок
Ниже мы анализируем и реализуем один за другим.
Модуль 1: Инкапсуляция http-сервера node и создание конструктора класса Koa
Читая исходный код koa2, мы знаем, что серверное приложение и мониторинг портов koa на самом деле инкапсулированы на основе собственного кода узла.Код на следующем рисунке представляет собой мониторинг сервера, реализованный собственным кодом узла.
let http = require('http');
let server = http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world');
});
server.listen(3000, () => {
console.log('listenning on 3000');
});
Нам нужно инкапсулировать приведенный выше собственный код узла в режим koa:
const http = require('http');
const Koa = require('koa');
const app = new Koa();
app.listen(3000);
Первым шагом реализации koa является инкапсуляция описанного выше процесса, для этого нам нужно создать application.js для реализации конструктора класса Application:
let http = require('http');
class Application {
constructor() {
this.callbackFunc;
}
listen(port) {
let server = http.createServer(this.callback());
server.listen(port);
}
use(fn) {
this.callbackFunc = fn;
}
callback() {
return (req, res) => {
this.callbackFunc(req, res);
};
}
}
module.exports = Application;
Затем создайте example.js, импортируйте application.js и запустите экземпляр сервера, чтобы запустить код мониторинга:
let Koa = require('./application');
let app = new Koa();
app.use((req, res) => {
res.writeHead(200);
res.end('hello world');
});
app.listen(3000, () => {
console.log('listening on 3000');
});
Теперь введите localhost:3000 в браузере, чтобы увидеть «hello world» в браузере. Теперь, когда первый шаг выполнен, мы просто инкапсулировали http-сервер и создали класс, который может генерировать экземпляры koa.Этот класс также реализует app.use для регистрации промежуточного программного обеспечения и регистрации функций обратного вызова, app.listen используется для запуска сервера. instance и передать функцию обратного вызова.Первый модуль в основном реализует типичный стиль koa и устанавливает простой фрейм koa. Далее мы начинаем писать и объяснять второй модуль.
Модуль 2: Создание запроса, ответа, объектов контекста
Читая исходный код koa2, мы знаем, что три файла context.js, request.js и response.js являются файлами кода трех модулей request, response и context соответственно. Контекст — это ctx, когда мы обычно пишем код koa.Он эквивалентен глобальному контексту экземпляра koa this, который соединяет два функциональных модуля запроса и ответа и подвергается воздействию параметров функций обратного вызова, таких как экземпляры koa и промежуточное ПО. на связующую роль.
Два функциональных модуля запроса и ответа соответственно инкапсулируют функцию собственного запроса и ответа узла, используя атрибуты getter и setter, а объект req/res, основанный на объекте node, инкапсулирует объект запроса/ответа koa. Исходя из этого принципа, мы просто реализуем request.js и response.js, сначала создаем файл request.js, а затем пишем следующий код:
let url = require('url');
module.exports = {
get query() {
return url.parse(this.req.url, true).query;
}
};
Таким образом, когда вы используете ctx.query в экземпляре koa, он вернет значение url.parse(this.req.url, true).query. Глядя на исходный код, мы видим, что на основе геттера и сеттера заголовок, URL-адрес, источник, путь и другие методы также инкапсулированы в request.js, все из которых инкапсулированы с помощью геттера и сеттера в собственном запросе, а автор не будет реализовывать их здесь по одному.
Затем мы реализуем модуль кода файла response.js, который совпадает с принципом запроса. Он также инкапсулирует собственный ответ на основе геттеров и сеттеров. Затем мы будем использовать два часто используемых оператора ctx.body и ctx.status как примеры Кратко опишите, если мы реализуем модуль ответа koa, мы сначала создаем файл response.js, а затем вводим следующий код:
module.exports = {
get body() {
return this._body;
},
set body(data) {
this._body = data;
},
get status() {
return this.res.statusCode;
},
set status(statusCode) {
if (typeof statusCode !== 'number') {
throw new Error('something wrong!');
}
this.res.statusCode = statusCode;
}
};
Приведенный выше код реализует чтение и установку статуса koa.При чтении он возвращает атрибут statusCode на основе собственного объекта ответа, а чтение тела заключается в чтении, записи и работе с this._body. Нативный this.res.end здесь не используется для работы с телом, потому что, когда мы пишем код koa, тело будет считываться и изменяться много раз, поэтому фактическая операция возврата информации о браузере находится в пакете и управлении в application.js. .
Теперь мы реализовали request.js и response.js, получили объекты запроса и ответа и методы их инкапсуляции.Затем приступаем к реализации context.js.Функция контекста заключается в монтировании объектов запроса и ответа на ctx.Разрешить koa и код для простого использования методов в объектах запроса и ответа. Теперь мы создаем файл context.js и вводим следующий код:
let proto = {};
function delegateSet(property, name) {
proto.__defineSetter__(name, function (val) {
this[property][name] = val;
});
}
function delegateGet(property, name) {
proto.__defineGetter__(name, function () {
return this[property][name];
});
}
let requestSet = [];
let requestGet = ['query'];
let responseSet = ['body', 'status'];
let responseGet = responseSet;
requestSet.forEach(ele => {
delegateSet('request', ele);
});
requestGet.forEach(ele => {
delegateGet('request', ele);
});
responseSet.forEach(ele => {
delegateSet('response', ele);
});
responseGet.forEach(ele => {
delegateGet('response', ele);
});
module.exports = proto;
Файл context.js в основном используется для монтирования и проксирования часто используемых методов запроса и ответа, прямого проксирования context.request.query через context.query, context.body и context.status proxy context.response.body и context.response . статус. И context.request, context.response будут смонтированы в application.js
Можно использовать простые сеттеры и геттеры для установки каждого метода, но поскольку метод определения объекта контекста относительно прост и стандартизирован, вы можете видеть в исходном коде koa, что исходный код koa использует __defineSetter__ и __defineSetter__ вместо setter/getter. Настройка чтения каждого атрибута предназначена в основном для облегчения расширения и упрощения метода записи.Когда нам нужно проксировать больше методов res и req, мы можем добавить соответствующее имя метода и req к объекту массива в файле context.js. название.
На данный момент мы получили три объекта модуля запроса, ответа и контекста.Следующий шаг — смонтировать все методы запроса и ответа в контексте, чтобы контекст мог реализовать свою роль связующего звена между предыдущим и Далее Измените файл application.js и добавьте следующий код:
let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('./response');
createContext(req, res) {
let ctx = Object.create(this.context);
ctx.request = Object.create(this.request);
ctx.response = Object.create(this.response);
ctx.req = ctx.request.req = req;
ctx.res = ctx.response.res = res;
return ctx;
}
Как видите, мы добавили ключевой метод createContext. Он создает ctx через Object.create, монтирует запрос и ответ в ctx, а также монтирует собственные req и res в подсвойства ctx. /request/response.js можно узнать, откуда взялся использовавшийся на тот момент this.res или this.response.Оказалось, что соответствующий экземпляр был смонтирован в методе createContext.Выше, после построения рантайм-контекста ctx, все параметры нашей функции обратного вызова app.use основаны на ctx.
Модуль 3: Реализация механизма промежуточного программного обеспечения и модели очистки лука
До сих пор мы успешно реализовали объект контекста, объект запроса и модуль объекта ответа.Наиболее важным модулем является модуль промежуточного программного обеспечения koa.Механизм промежуточного программного обеспечения koa представляет собой модель очистки лука.Промежуточное программное обеспечение помещается в очередь массива посредством использования а затем выполняется из внешнего слоя.После встречи с next он входит в следующий мидлвар в очереди.После того как все мидлвары выполнены он начинает возвращаться к фрейму,и выполняет в очереди код который не выполнялся в мидлваре до этого Часть, это модель очищающего лука, механизм промежуточного программного обеспечения koa.
Модель очистки лука koa реализована генератором + co.js в koa1 и реализована async/await + Promise в koa2 Далее мы реализуем механизм промежуточного программного обеспечения в koa2 на основе async/await + Promise . Во-первых, если предположить, что когда промежуточный механизм koa будет готов, он сможет успешно запустить следующий код:
let Koa = require('../src/application');
let app = new Koa();
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(6);
});
app.use(async (ctx, next) => {
console.log(2);
await next();
console.log(5);
});
app.use(async (ctx, next) => {
console.log(3);
ctx.body = "hello world";
console.log(4);
});
app.listen(3000, () => {
console.log('listenning on 3000');
});
После успешного запуска он выведет на терминал 123456, что может подтвердить правильность нашей модели лука, очищающего коа. Далее начинаем реализацию, модифицируем файл application.js и добавляем следующий код:
compose() {
return async ctx => {
function createNext(middleware, oldNext) {
return async () => {
await middleware(ctx, oldNext);
}
}
let len = this.middlewares.length;
let next = async () => {
return Promise.resolve();
};
for (let i = len - 1; i >= 0; i--) {
let currentMiddleware = this.middlewares[i];
next = createNext(currentMiddleware, next);
}
await next();
};
}
callback() {
return (req, res) => {
let ctx = this.createContext(req, res);
let respond = () => this.responseBody(ctx);
let onerror = (err) => this.onerror(err, ctx);
let fn = this.compose();
return fn(ctx);
};
}
С помощью функции использования koa помещает все промежуточное ПО во внутреннюю очередь массива this.middlewares. Очистка луковой модели позволяет выполнять все промежуточное ПО последовательно. После выполнения каждого промежуточного ПО оно будет управлять следующим(). Право передается следующее промежуточное ПО, следующий параметр следующего промежуточного ПО и наиболее важный код для очистки луковой модели — это функция компоновки:
compose() {
return async ctx => {
function createNext(middleware, oldNext) {
return async () => {
await middleware(ctx, oldNext);
}
}
let len = this.middlewares.length;
let next = async () => {
return Promise.resolve();
};
for (let i = len - 1; i >= 0; i--) {
let currentMiddleware = this.middlewares[i];
next = createNext(currentMiddleware, next);
}
await next();
};
}
Роль функции createNext состоит в том, чтобы передать следующее промежуточное программное обеспечение предыдущего промежуточного программного обеспечения в качестве параметра следующему промежуточному программному обеспечению и привязать контекст ctx к текущему промежуточному программному обеспечению.Когда промежуточное программное обеспечение выполняется и вызывается next(), на самом деле выполнить следующее промежуточное ПО.
for (let i = len - 1; i >= 0; i--) {
let currentMiddleware = this.middlewares[i];
next = createNext(currentMiddleware, next);
}
Вышеприведенный код на самом деле является реализацией цепной обратной рекурсивной модели.Я запускаю цикл с максимального номера и инкапсулирую промежуточное ПО с последнего.Каждый раз он инкапсулирует свою собственную функцию выполнения в следующую как предыдущую середину.Следующий параметр программного обеспечения, поэтому при переходе к первому промежуточному программному обеспечению вам нужно только один раз выполнить next() для рекурсивного вызова всего промежуточного программного обеспечения в цепочке.Это основной механизм кода коа, очищающий лук.
Здесь мы суммируем все процессы очистки кода модели лука, описанные выше.Промежуточное программное обеспечение, передаваемое посредством использования, представляет собой функцию обратного вызова.Параметры функции обратного вызова — контекст ctx и next.Далее — фактически передача управления, а функция затем следует остановить запуск текущего промежуточного программного обеспечения, передать управление следующему промежуточному программному обеспечению, выполнить код до next() следующего промежуточного программного обеспечения, когда код, запускаемый следующим промежуточным программным обеспечением, встретит next(), он передаст право выполнения кода к следующему промежуточному ПО Следующее промежуточное ПО, когда выполняется последнее промежуточное ПО, управление меняется на противоположное, и оно начинает возвращаться для выполнения невыполненного кода во всех предыдущих промежуточных ПО Весь этот процесс немного похож на псевдорекурсию, когда наконец все промежуточное ПО После выполнения всех компонентов будет возвращен объект Promise, потому что наша функция compose возвращает асинхронную функцию. могут быть выполнены функция ответа и функция обработки ошибок.
После того, как код механизма промежуточного программного обеспечения написан, запустив наш пример выше, он может вывести 123456. На данный момент базовая структура нашего koa в основном завершена, но структура может не только реализовывать функции для экземпляров платформы и сервера Robust, но также необходимо добавить механизм обработки ошибок.
Модуль 4: Отлов ошибок и обработка ошибок
Для реализации базового фреймворка необходимы обработка и захват ошибок.Надежный фреймворк должен гарантировать, что при возникновении ошибок ошибки и генерируемые исключения могут быть перехвачены, возвращены и отправлены в систему мониторинга для обработки.Обратная связь, простая инфраструктура koa, которую мы реализовали не удалось этого добиться, мы добавим механизм обработки и захвата ошибок.
throw new Error('oooops');
Основываясь на текущей структуре, если в коде промежуточного программного обеспечения возникает указанное выше исключение ошибки, ошибка не может быть обнаружена.На данный момент давайте посмотрим на код возврата функции обратного вызова в application.js следующим образом:
return fn(ctx).then(respond);
Можно видеть, что fn — это функция выполнения промежуточного программного обеспечения, каждый код промежуточного программного обеспечения обернут асинхронным кодом, а функция выполнения, состоящая из промежуточного программного обеспечения, возвращает асинхронную функцию.Согласно спецификации es7 мы знаем, что асинхронный возвращает экземпляр объекта обещания A , если мы хотим поймать ошибки обещания, нам нужно использовать только метод catch обещания, чтобы перехватить все исключения промежуточного программного обеспечения.Измененный код возврата обратного вызова выглядит следующим образом:
return fn(ctx).then(respond).catch(onerror);
Теперь мы реализовали захват исключений ошибок промежуточного ПО, но нам все еще не хватает механизма захвата ошибок на уровне фреймворка.Мы надеемся, что наш экземпляр сервера может иметь механизм мониторинга событий ошибок, и мы можем подписаться и отслеживать уровень фреймворка через функцию слушателя.Ошибка выше, реализовать этот механизм несложно, просто используйте собственный модуль событий nodejs, модуль событий предоставляет нам функцию мониторинга событий и функцию поведения, запускающую событие, одну для передачи событий , и один для получения событий, нам нужно только изменить коа Конструктор может наследовать модуль событий Псевдокод после построения выглядит следующим образом:
let EventEmitter = require('events');
class Application extends EventEmitter {}
После наследования модуля событий, когда мы создаем экземпляр koa, добавляем функцию слушателя, код выглядит следующим образом:
let app = new Koa();
app.on('error', err => {
console.log('error happends: ', err.stack);
});
Таким образом, мы реализовали механизм захвата и мониторинга ошибок на уровне фреймворка. Подводя итог, обработка и захват ошибок разделены на захват обработки ошибок промежуточного программного обеспечения и захват обработки ошибок на уровне платформы, обработку ошибок промежуточного программного обеспечения с использованием обещания, обработку ошибок на уровне платформы с использованием событий собственного модуля nodejs, чтобы мы могли поместить все исключения ошибок на сервер пойманы экземпляры. На данный момент мы полностью реализовали облегченную версию фреймворка koa.
конец
На данный момент мы реализовали облегченную версию фреймворка koa.Мы реализовали инкапсуляцию http-сервера узла, создание конструкторов класса Koa, построение объектов запроса, ответа, контекста, реализацию механизма промежуточного программного обеспечения и модели очистки лука. , и захват ошибок.И четыре основных модуля обработки ошибок, поймите принцип реализации этой облегченной версии koa, а затем посмотрите на исходный код koa2, вы обнаружите, что все вдруг ясно, исходный код koa2 не более чем добавление на основе этой облегченной версии. Обработка многих функций и деталей инструмента не будет вводиться по одной из-за нехватки места.
Технологический еженедельник IVWEBШок в сети, обратите внимание на публичный номер: сообщество IVWEB, регулярно публикуйте качественные статьи каждую неделю.