Архитектура обработки HTTP-взаимодействий в Iron строится вокруг расширяемого слоя декораторов, которые позволяют модифицировать как входящий запрос, так и исходящий ответ без изменения бизнес-логики контроллеров. Декораторы в Iron представляют собой функции-обёртки, выполняющиеся в строго определённом порядке и имеющие доступ к контексту выполнения запроса.
Каждый декоратор работает с объектом контекста ctx,
содержащим:
ctx.request — данные запросаctx.response — объект ответаctx.state — локальное состояние цепочкиctx.next() — переход к следующему декораторуТакая модель обеспечивает единый механизм расширения поведения без нарушения принципов разделения ответственности.
Цепочка обработки запроса в Iron выглядит как последовательность этапов:
ctxДекораторы делятся на два фундаментальных типа:
Каждый из них может быть синхронным или асинхронным.
Декоратор в Iron представляет собой функцию следующего вида:
function decorator(ctx, next) {
// модификация запроса или подготовка данных
return next().then(() => {
// пост-обработка (для response-декораторов)
});
}
Асинхронная природа позволяет использовать декораторы для работы с базой данных, кэшем, внешними API и другими I/O операциями.
Request-декораторы применяются до попадания запроса в контроллер. Их основная задача — нормализация, валидация и обогащение данных.
Одной из типичных задач является проверка корректности входных данных:
function validateUserRequest(ctx, next) {
const { email } = ctx.request.body;
if (!email || !email.includes('@')) {
ctx.response.status = 400;
ctx.response.body = { error: 'Invalid email' };
return;
}
return next();
}
В данном случае цепочка прерывается при ошибке, и основной обработчик не вызывается.
Декораторы часто используются для проверки прав доступа:
async function authDecorator(ctx, next) {
const token = ctx.request.headers['authorization'];
const user = await verifyToken(token);
if (!user) {
ctx.response.status = 401;
ctx.response.body = { error: 'Unauthorized' };
return;
}
ctx.state.user = user;
return next();
}
Контекст ctx.state служит общим хранилищем для передачи
данных дальше по цепочке.
Декораторы могут преобразовывать структуру запроса:
function normalizeBody(ctx, next) {
const body = ctx.request.body;
ctx.request.body = {
...body,
createdAt: new Date(),
isActive: true
};
return next();
}
Такой подход позволяет централизованно управлять форматированием данных.
Response-декораторы применяются после выполнения бизнес-логики и позволяют изменять исходящий ответ.
Один из самых распространённых сценариев — унификация структуры ответа:
async function responseWrapper(ctx, next) {
await next();
ctx.response.body = {
success: true,
data: ctx.response.body,
timestamp: Date.now()
};
}
Этот декоратор гарантирует единый формат API независимо от контроллера.
Response-декораторы часто используются для мониторинга:
async function logResponse(ctx, next) {
const start = Date.now();
await next();
const duration = Date.now() - start;
console.log({
method: ctx.request.method,
path: ctx.request.url,
status: ctx.response.status,
duration
});
}
Здесь измеряется время выполнения полного запроса.
Декораторы позволяют реализовать слой кэша без вмешательства в бизнес-логику:
async function cacheDecorator(ctx, next) {
const key = ctx.request.url;
const cached = await cache.get(key);
if (cached) {
ctx.response.body = cached;
return;
}
await next();
await cache.set(key, ctx.response.body);
}
Такой подход особенно эффективен для GET-запросов.
В Iron декораторы выполняются в порядке их регистрации. Однако важно учитывать двустороннюю модель исполнения:
Это поведение аналогично middleware-стеку с «обратным проходом».
Пример цепочки:
app.use(decoratorA);
app.use(decoratorB);
app.use(decoratorC);
Порядок выполнения:
Request: A → B → C → handler
Response: C → B → A
Iron поддерживает композицию декораторов, позволяя объединять несколько функций в одну логическую единицу:
function compose(...decorators) {
return function(ctx, next) {
let index = -1;
function dispatch(i) {
if (i <= index) return Promise.reject('next called multiple times');
index = i;
let fn = decorators[i] || next;
if (!fn) return Promise.resolve();
return Promise.resolve(fn(ctx, () => dispatch(i + 1)));
}
return dispatch(0);
};
}
Композиция упрощает повторное использование логики.
Любой декоратор может перехватывать ошибки в цепочке исполнения:
async function errorBoundary(ctx, next) {
try {
await next();
} catch (err) {
ctx.response.status = 500;
ctx.response.body = {
error: 'Internal Server Error',
message: err.message
};
}
}
Такая конструкция формирует глобальный уровень защиты приложения.
Декораторы могут выполняться условно, в зависимости от параметров запроса:
function onlyPost(ctx, next) {
if (ctx.request.method !== 'POST') {
return next();
}
return validateBody(ctx, next);
}
Также поддерживаются приоритеты, влияющие на порядок регистрации:
app.use(authDecorator, { priority: 10 });
app.use(loggingDecorator, { priority: 1 });
Чем выше приоритет, тем раньше выполняется декоратор.
Все декораторы в Iron поддерживают асинхронное выполнение. Это позволяет интегрировать внешние сервисы без блокировки основного потока:
async function enrichUser(ctx, next) {
const userData = await fetchUserProfile(ctx.state.user.id);
ctx.state.profile = userData;
return next();
}
При этом важно избегать гонок данных при параллельных запросах,
используя изолированный ctx.
Response-декораторы часто используются для управления HTTP-заголовками:
async function securityHeaders(ctx, next) {
await next();
ctx.response.headers['X-Frame-Options'] = 'DENY';
ctx.response.headers['X-Content-Type-Options'] = 'nosniff';
}
Это позволяет централизованно управлять политиками безопасности.
Ключевая идея Iron заключается в том, что контроллеры остаются «чистыми», а вся инфраструктурная логика выносится в декораторы:
Контроллер при этом содержит только бизнес-логику:
async function getUser(ctx) {
const user = await userService.findById(ctx.params.id);
ctx.response.body = user;
}
Использование декораторов формирует модульную архитектуру, где поведение системы собирается из независимых блоков. Это позволяет:
Такая модель особенно эффективна в крупных приложениях, где количество маршрутов и зависимостей быстро растёт.