[JavaScript, Node.JS] Хочу middleware, но не хочу ExpressJS
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Middleware в случае с HTTP-сервером в Node.JS — это промежуточный код, который выполняется до того, как начнёт выполняться ваш основной код. Это, чаще всего, нужно для того, чтобы сделать какой-то дополнительный тюнинг или проверку входящего запроса. Например, чтобы превратить данные из POST-запроса в формате JSON-строки в обычный объект, или получить доступ к кукам в виде объета, и т.п.
Стандартный модуль http из Node.JS не поддерживает такие вещи. Самый очевидный путь: установить ExpressJS и не париться. Но, на мой взгляд, если есть возможность самому написать немного кода и не добавлять ещё 50 пакетов-зависимостей в проект, архитектура станет проще, скорость работы будет выше, будет меньше точек отказа, и ещё не будет нужно постоянно пастись на гитхабе и уговаривать разработчиков обновить версии зависимостей в package.json (или просто принять пулл-реквест, где другой человек за него это сделал), чтобы код был постоянно свежим и актуальным. Я пару раз так делал, и мне не очень нравится тратить время на такие вещи. Очень часто, если ты самостоятельно воспроизводишь какую-то технологию, времени на поддержку тратится меньше, чем если ты устанавливаешь сторонний модуль с такой технологией — как раз из-за таких моментов, когда ты тратишь время на то, чтобы напоминать другим разработчикам, что нужно следить за обновлениями зависимостей и реагировать на них своевременно.
Суть middleware довольно-таки проста: это функция, которая принимает три параметра: request, response и next:
- request — инстанс http.IncomingMessage для текущего запроса
- response — инстанс http.ServerResponse для текущего запроса
- next — функция.
Middleware делает все нужные телодвижения с request и response, после чего вызывает функцию next — это сигнал, что оно закончило работу и можно работать дальше (например, запустить в обработку следующее middleware, или просто перейти к основному коду). Если next вызывается без параметров, то всё нормально. Если в вызов передать ошибку, то обработка списка middleware останавливается.
Пример простейшего middleware:
function myMiddleware(request, response, next) {
if (typeof next !== 'function') {
next = () => {};
}
console.log('Incoming request');
next();
}
Если честно, я даже не смотрел, как это реализовано в ExpressJS, но, навскидку, я понимаю этот процесс так: когда вызывается server.use(myMiddleware), моя функция myMiddleware добавляется в какой-то массив, а при каждом входящем запросе вызываются все функции из этого массиа в порядке очерёдности их добавления, после чего начинает работать остальной код. Очевидно, раз используется функция next, то подразумевается асинхронность кода: middleware-функции не просто выполняются одна за другой — перед тем как выполнить следующую функцию из списка, нужно дождаться окончания работы предыдущей.
Получается, вначале мне нужно создать функцию server.use, которая будет регистрировать все middleware.
MyHttpServer.js:
const http = require('http');
const middlewares = [];
/**
* Основной обработчик HTTP-запросов
*
* Пока что тут только заглушка
*
* @param {IncomingMessage} request
* @param {ServerResponse} response
* @return {Promise<void>}
*/
async function requestListener(request, response) {
throw new Error('Not implemented');
}
/*
* Функция-регистратор middleware-кода
*/
function registerMiddleware(callback) {
if (typeof callback !== 'function') {
return;
}
middlewares.push(callback);
}
// Создаётся сервер и регистрируется осноной обработчик
const server = http.createServer(requestListener);
// К серверу добавляется регистратор middleware-функций
server.use = registerMiddleware;
Осталась самая малость: нужно каким-то образом выполнять все эти middleware в асинхронном режиме. Лично я, если мне нужно обойти массив в асинхронном режиме, пользуюсь функцией Array.prototype.reduce(). Она, в определённых условиях, может делать как раз то, что мне нужно. Самое время доработать функцию requestListener.
/*
* Это просто служебная функция — вставлена здесь для примера
* и сама по себе обычно находится в другом модуле
*/
function isError(error) {
switch (Object.prototype.toString.call(error)) {
case '[object Error]':
return true;
case '[object Object]':
return (
error.message
&& typeof error.message === 'string'
&& error.stack
&& typeof error.stack === 'string'
);
}
return false;
}
/**
* Основной обработчик HTTP-запросов
*
* @param {IncomingMessage} request
* @param {ServerResponse} response
* @return {Promise<void>}
*/
async function requestListener(request, response) {
response.isFinished = false;
response.on('finish', () => response.isFinished = true);
response.on('close', () => response.isFinished = true);
let result;
try {
result = await middlewares.reduce(
// Редусер. Первый параметр — предыдущее значение,
// второй — текущее. Чтобы обеспечить асинхронность,
// всё оборачивается в Promise.
(/**Promise*/promise, middleware) => promise.then(result => {
// Если в предыдущем middleware был вызов
// next(new Error('Some message')), текущий middleware
// игнорируется и сразу возвращается ошибка
// из предыдущего кода
if (isError(result)) {
return Promise.reject(result);
}
// Возвращается новый Promise, который, кроме прочего,
// реагирует на какую-то ошибку в рамках не только
// вызова next, но и в рамках всего кода.
// То есть, если middleware вызывает внутри JSON.parse
// без try-catch, то, в случае ошибки парсинга, реакция
// будет такая же, как и при вызове next с передачей
// ошибки в качестве параметра
return new Promise((next, reject) => {
Promise.resolve(middleware(request, response, next)).catch(reject);
});
}),
Promise.resolve()
);
if (isError(result)) {
throw result;
}
} catch (error) {
response.statusCode = 500;
result = 'Error';
}
if (response.isFinished) {
return;
}
response.end(result);
}
Теперь я могу без установки ExpressJS использовать любые middleware, которые были написаны для него. И вообще, используя этот механизм, я могу представить мой основной обработчик запросов в виде обычной middleware-функции.
const cookieParser = require('cookie-parser');
server.use(cookieParser());
// Мой основной код
server.use((request, response, next) => {
if (request.cookies['SSID']) {
response.end('Your session id is ' + request.cookies['SSID']);
} else {
response.end('No session detected');
}
next();
});
Под спойлером — простейший пример стандартного HTTP-сервера Node.JS с поддержкой экспрессовких middleware для тех, кто предпочитает copy/paste.
MyHttpServer.js
SPL
const http = require('http');
const middlewares = [];
function isError(error) {
switch (Object.prototype.toString.call(error)) {
case '[object Error]':
return true;
case '[object Object]':
return (
error.message
&& typeof error.message === 'string'
&& error.stack
&& typeof error.stack === 'string'
);
}
return false;
}
/**
* Основной обработчик HTTP-запросов
*
* @param {IncomingMessage} request
* @param {ServerResponse} response
* @return {Promise<void>}
*/
async function requestListener(request, response) {
response.isFinished = false;
response.on('finish', () => response.isFinished = true);
response.on('close', () => response.isFinished = true);
let result;
try {
result = await middlewares.reduce(
(/**Promise*/promise, middleware) => promise.then(result => {
if (isError(result)) {
return Promise.reject(result);
}
return new Promise((next, reject) => {
Promise.resolve(middleware(request, response, next)).catch(reject);
});
}),
Promise.resolve()
);
if (isError(result)) {
throw result;
}
} catch (e) {
response.statusCode = 500;
result = 'Error';
}
if (response.isFinished) {
return;
}
response.end(result);
}
/*
* Функция-регистратор middleware-кода
*/
function registerMiddleware(callback) {
if (typeof callback !== 'function') {
return;
}
middlewares.push(callback);
}
const server = http.createServer(requestListener);
server.use = registerMiddleware;
const cookieParser = require('cookie-parser');
server.use(cookieParser());
server.use((request, response) => {
if (request.cookies['SSID']) {
return 'Your session id is ' + request.cookies['SSID'];
}
return 'No session detected';
});
server.listen(12345, 'localhost', () => {
console.log('Started http');
});
Нашли ошибку в тексте? Выделите текст, содержащий ошибку и нажмите Alt-F4 (если у вас мак, то ⌘-Q). Шутка, конечно же. Если нашли ошибку, пишите в личные сообщения или в комментарии — постараюсь исправить.
===========
Источник:
habr.com
===========
Похожие новости:
- [JavaScript, Node.JS] Fastify.js — не только самый быстрый веб-фреймворк для node.js
- [JavaScript, Canvas, ReactJS] Варианты создания интерактивной экскурсии для пользователей
- [JavaScript, Промышленное программирование, TypeScript] Продвинутые дженерики в TypeScript. Доклад Яндекса
- [Разработка игр, Node.JS, Unity, Игры и игровые приставки] Спасибо хабравчанам за участие в Онлайнстрации
- [Мессенджеры, Платежные системы, JavaScript, Node.JS] Оплата в телеграм боте — Платежи 2.0 — Сбербанк + Telegraf + Node.js
- [JavaScript, Google Chrome, WebAssembly] Что вошло в релиз движка V8 версии 9.0 (перевод)
- [Разработка игр, Node.JS, Unity, Игры и игровые приставки] Монстрация-онлайнстрация
- [JavaScript, Программирование] Человеко-читаемый JavaScript: история о двух экспертах (перевод)
- [Node.JS, Serverless] Запускаем приложение на Express.js в Yandex Cloud Functions
- [JavaScript, Angular, ReactJS] Only 39% of the functions in node_modules are unique in the default Angular project (перевод)
Теги для поиска: #_javascript, #_node.js, #_middleware, #_nodejs, #_javascript, #_node.js
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 07:22
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Middleware в случае с HTTP-сервером в Node.JS — это промежуточный код, который выполняется до того, как начнёт выполняться ваш основной код. Это, чаще всего, нужно для того, чтобы сделать какой-то дополнительный тюнинг или проверку входящего запроса. Например, чтобы превратить данные из POST-запроса в формате JSON-строки в обычный объект, или получить доступ к кукам в виде объета, и т.п. Стандартный модуль http из Node.JS не поддерживает такие вещи. Самый очевидный путь: установить ExpressJS и не париться. Но, на мой взгляд, если есть возможность самому написать немного кода и не добавлять ещё 50 пакетов-зависимостей в проект, архитектура станет проще, скорость работы будет выше, будет меньше точек отказа, и ещё не будет нужно постоянно пастись на гитхабе и уговаривать разработчиков обновить версии зависимостей в package.json (или просто принять пулл-реквест, где другой человек за него это сделал), чтобы код был постоянно свежим и актуальным. Я пару раз так делал, и мне не очень нравится тратить время на такие вещи. Очень часто, если ты самостоятельно воспроизводишь какую-то технологию, времени на поддержку тратится меньше, чем если ты устанавливаешь сторонний модуль с такой технологией — как раз из-за таких моментов, когда ты тратишь время на то, чтобы напоминать другим разработчикам, что нужно следить за обновлениями зависимостей и реагировать на них своевременно. Суть middleware довольно-таки проста: это функция, которая принимает три параметра: request, response и next:
Middleware делает все нужные телодвижения с request и response, после чего вызывает функцию next — это сигнал, что оно закончило работу и можно работать дальше (например, запустить в обработку следующее middleware, или просто перейти к основному коду). Если next вызывается без параметров, то всё нормально. Если в вызов передать ошибку, то обработка списка middleware останавливается. Пример простейшего middleware: function myMiddleware(request, response, next) {
if (typeof next !== 'function') { next = () => {}; } console.log('Incoming request'); next(); } Если честно, я даже не смотрел, как это реализовано в ExpressJS, но, навскидку, я понимаю этот процесс так: когда вызывается server.use(myMiddleware), моя функция myMiddleware добавляется в какой-то массив, а при каждом входящем запросе вызываются все функции из этого массиа в порядке очерёдности их добавления, после чего начинает работать остальной код. Очевидно, раз используется функция next, то подразумевается асинхронность кода: middleware-функции не просто выполняются одна за другой — перед тем как выполнить следующую функцию из списка, нужно дождаться окончания работы предыдущей. Получается, вначале мне нужно создать функцию server.use, которая будет регистрировать все middleware. MyHttpServer.js: const http = require('http');
const middlewares = []; /** * Основной обработчик HTTP-запросов * * Пока что тут только заглушка * * @param {IncomingMessage} request * @param {ServerResponse} response * @return {Promise<void>} */ async function requestListener(request, response) { throw new Error('Not implemented'); } /* * Функция-регистратор middleware-кода */ function registerMiddleware(callback) { if (typeof callback !== 'function') { return; } middlewares.push(callback); } // Создаётся сервер и регистрируется осноной обработчик const server = http.createServer(requestListener); // К серверу добавляется регистратор middleware-функций server.use = registerMiddleware; Осталась самая малость: нужно каким-то образом выполнять все эти middleware в асинхронном режиме. Лично я, если мне нужно обойти массив в асинхронном режиме, пользуюсь функцией Array.prototype.reduce(). Она, в определённых условиях, может делать как раз то, что мне нужно. Самое время доработать функцию requestListener. /*
* Это просто служебная функция — вставлена здесь для примера * и сама по себе обычно находится в другом модуле */ function isError(error) { switch (Object.prototype.toString.call(error)) { case '[object Error]': return true; case '[object Object]': return ( error.message && typeof error.message === 'string' && error.stack && typeof error.stack === 'string' ); } return false; } /** * Основной обработчик HTTP-запросов * * @param {IncomingMessage} request * @param {ServerResponse} response * @return {Promise<void>} */ async function requestListener(request, response) { response.isFinished = false; response.on('finish', () => response.isFinished = true); response.on('close', () => response.isFinished = true); let result; try { result = await middlewares.reduce( // Редусер. Первый параметр — предыдущее значение, // второй — текущее. Чтобы обеспечить асинхронность, // всё оборачивается в Promise. (/**Promise*/promise, middleware) => promise.then(result => { // Если в предыдущем middleware был вызов // next(new Error('Some message')), текущий middleware // игнорируется и сразу возвращается ошибка // из предыдущего кода if (isError(result)) { return Promise.reject(result); } // Возвращается новый Promise, который, кроме прочего, // реагирует на какую-то ошибку в рамках не только // вызова next, но и в рамках всего кода. // То есть, если middleware вызывает внутри JSON.parse // без try-catch, то, в случае ошибки парсинга, реакция // будет такая же, как и при вызове next с передачей // ошибки в качестве параметра return new Promise((next, reject) => { Promise.resolve(middleware(request, response, next)).catch(reject); }); }), Promise.resolve() ); if (isError(result)) { throw result; } } catch (error) { response.statusCode = 500; result = 'Error'; } if (response.isFinished) { return; } response.end(result); } Теперь я могу без установки ExpressJS использовать любые middleware, которые были написаны для него. И вообще, используя этот механизм, я могу представить мой основной обработчик запросов в виде обычной middleware-функции. const cookieParser = require('cookie-parser');
server.use(cookieParser()); // Мой основной код server.use((request, response, next) => { if (request.cookies['SSID']) { response.end('Your session id is ' + request.cookies['SSID']); } else { response.end('No session detected'); } next(); }); Под спойлером — простейший пример стандартного HTTP-сервера Node.JS с поддержкой экспрессовких middleware для тех, кто предпочитает copy/paste. MyHttpServer.jsSPLconst http = require('http');
const middlewares = []; function isError(error) { switch (Object.prototype.toString.call(error)) { case '[object Error]': return true; case '[object Object]': return ( error.message && typeof error.message === 'string' && error.stack && typeof error.stack === 'string' ); } return false; } /** * Основной обработчик HTTP-запросов * * @param {IncomingMessage} request * @param {ServerResponse} response * @return {Promise<void>} */ async function requestListener(request, response) { response.isFinished = false; response.on('finish', () => response.isFinished = true); response.on('close', () => response.isFinished = true); let result; try { result = await middlewares.reduce( (/**Promise*/promise, middleware) => promise.then(result => { if (isError(result)) { return Promise.reject(result); } return new Promise((next, reject) => { Promise.resolve(middleware(request, response, next)).catch(reject); }); }), Promise.resolve() ); if (isError(result)) { throw result; } } catch (e) { response.statusCode = 500; result = 'Error'; } if (response.isFinished) { return; } response.end(result); } /* * Функция-регистратор middleware-кода */ function registerMiddleware(callback) { if (typeof callback !== 'function') { return; } middlewares.push(callback); } const server = http.createServer(requestListener); server.use = registerMiddleware; const cookieParser = require('cookie-parser'); server.use(cookieParser()); server.use((request, response) => { if (request.cookies['SSID']) { return 'Your session id is ' + request.cookies['SSID']; } return 'No session detected'; }); server.listen(12345, 'localhost', () => { console.log('Started http'); }); Нашли ошибку в тексте? Выделите текст, содержащий ошибку и нажмите Alt-F4 (если у вас мак, то ⌘-Q). Шутка, конечно же. Если нашли ошибку, пишите в личные сообщения или в комментарии — постараюсь исправить. =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 07:22
Часовой пояс: UTC + 5