Декораторы запроса и ответа

Архитектура обработки HTTP-взаимодействий в Iron строится вокруг расширяемого слоя декораторов, которые позволяют модифицировать как входящий запрос, так и исходящий ответ без изменения бизнес-логики контроллеров. Декораторы в Iron представляют собой функции-обёртки, выполняющиеся в строго определённом порядке и имеющие доступ к контексту выполнения запроса.

Каждый декоратор работает с объектом контекста ctx, содержащим:

  • ctx.request — данные запроса
  • ctx.response — объект ответа
  • ctx.state — локальное состояние цепочки
  • ctx.next() — переход к следующему декоратору

Такая модель обеспечивает единый механизм расширения поведения без нарушения принципов разделения ответственности.


Позиция декораторов в цепочке обработки

Цепочка обработки запроса в Iron выглядит как последовательность этапов:

  1. Приём запроса сервером
  2. Инициализация контекста ctx
  3. Выполнение декораторов запроса
  4. Выполнение основного обработчика маршрута
  5. Выполнение декораторов ответа
  6. Отправка результата клиенту

Декораторы делятся на два фундаментальных типа:

  • Request-декораторы — работают до выполнения бизнес-логики
  • Response-декораторы — работают после формирования результата

Каждый из них может быть синхронным или асинхронным.


Базовая структура декоратора

Декоратор в Iron представляет собой функцию следующего вида:

function decorator(ctx, next) {
  // модификация запроса или подготовка данных

  return next().then(() => {
    // пост-обработка (для response-декораторов)
  });
}

Асинхронная природа позволяет использовать декораторы для работы с базой данных, кэшем, внешними API и другими I/O операциями.


Request-декораторы: модификация входящих данных

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-декораторы: обработка результата

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 декораторы выполняются в порядке их регистрации. Однако важно учитывать двустороннюю модель исполнения:

  • Request-декораторы выполняются сверху вниз
  • Response-декораторы выполняются снизу вверх

Это поведение аналогично 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;
}

Декораторы как инструмент архитектурной модульности

Использование декораторов формирует модульную архитектуру, где поведение системы собирается из независимых блоков. Это позволяет:

  • изменять поведение без правки контроллеров
  • повторно использовать общую логику
  • управлять порядком исполнения
  • изолировать побочные эффекты

Такая модель особенно эффективна в крупных приложениях, где количество маршрутов и зависимостей быстро растёт.