Главная / Статьи

Аксиос-интерцепторы в качестве HTTP-мидлвэра: концептуальная модель для приложений React

Понять, как интерцепторы запросов и ответов Axios функционируют в качестве промежуточного слоя, что должно находиться в каждом из них, и как они централизуют обработку заголовков аутентификации и ошибок 401 в React.

Издатель: REACTAPP.TOP6 мин чтения1357 слов

Источники: Axios Interceptors Explained: The Missing Piece Every Frontend Developer Should Learn.

В молодом проекте на React запросы к API обычно добавляются по одному путем копирования-вставки: каждый запрос формирует собственный заголовок Authorization, каждый ответ имеет собственную обработку ошибок, а каждый путь на случай сбоя повторяет одну и ту же логику восстановления. Это работает для трех конечных точек, но превращается в проблему технического обслуживания при их количестве в тридцать. Axios предлагает встроенное решение в виде интерсепторов, и как только вы начнете рассматривать их как промежуточные компоненты для обработки HTTP-трафика, становится ясно, что следует перенести в них, а что оставить в компонентах.

Дублирование, устраняемое интерсепторами

Типичной отправной точкой является запрос к пользователям с включенным прямо в код токеном-носителем:

axios.get("/users", {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

Вторая конечная точка дублирует этот блок заголовков буквально слово в слово:

axios.get("/products", {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

А операция записи делает то же самое, причем полезная нагрузка находится посередине:

axios.post("/orders", orderData, {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

Сначала всё это кажется неопасным. По мере роста проекта каждый запрос требует токена, каждый ответ — обработки ошибок, а к каждой неудаче нужно применять одинаковую реакцию. Именно для такой общей работы и существуют интерцепторы.

Что такое интерцептор на самом деле

Интерцептор — это функция, которую Axios запускает перед отправкой каждого исходящего запроса или перед тем, как ваш код увидит каждый входящий ответ: это точка контроля, где можно проверять или изменять трафик. Ответ проходит через свою собственную точку контроля перед тем, как будет выполнено обещание, ожидаемое вашим кодом React.

Общий путь начинается в вашем приложении:

React Application

и проходит через обе точки контроля перед тем, как вернуться к нему:

↓Request Interceptor↓Axios↓Server↓Response Interceptor↓React Application

Ваши компоненты никогда не взаимодействуют напрямую с сервером; всё происходит через цепочку, что делает её идеальным местом для обеспечения единообразного поведения.

Мидлвэр — с примером ресепшена

Если вы писали код на Express или Koa, этот термин вам знаком: это мидлвэр. В противном случае представьте себе посещение офисного здания. Вы не заходите прямо в офис — путь начинается с вас:

You

и проходит через два этапа по пути внутрь:

↓Security Check↓Reception↓Office

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

Мидлвэр — это любой слой между двумя системами, который выполняет действия до того, как запрос будет передан дальше. Замените офис на сервер, начиная с React-приложения:

React App

а интерцептор — это слой посередине:

↓Middleware (Interceptor)↓Server

Другими словами, интерцепторы Axios представляют собой промежуточные компоненты для обработки HTTP-запросов и ответов на стороне клиента.

Интерцепторы запросов и ответов

Axios предлагает два вида интерцепторов, которые регистрируются с помощью axios.interceptors.request.use(...) и axios.interceptors.response.use(...). Каждый вызов принимает обработчик успешного выполнения и необязательный обработчик ошибок, а также возвращает идентификатор, который позже можно передать функции eject для его удаления.

Интерцепторы запросов

Они выполняются до отправки запроса. Компонент инициирует вызов:

Component

а интерцептор обнаруживает его при отправке:

↓Request Interceptor↓Server

Типичные задачи интерцептора запросов:

  • добавление токенов аутентификации
  • установка пользовательских заголовков, таких как локаль или идентификатор запроса
  • изменение содержимого запроса
  • логирование исходящего трафика

Обработчик получает конфигурацию запроса и должен вернуть её (или обещание её возврата), чтобы запрос мог продолжиться; забывать об этом — классическая ошибка начинающих.

Интерцепторы ответов

Они запускаются после того, как сервер ответил, но до того, как ваше приложение получит результат. Ответ поступает от сервера:

Server

и проходит через интерцептор по пути к компоненту:

↓Response Interceptor↓Component

Типичные задачи здесь:

  • обработка ошибок в одном месте
  • трансформация или разбор данных ответа
  • логирование ответов
  • перепробовать неудачные запросы
  • перенаправление пользователя, например на страницу входа

Обработчик успеха отвечает за ответы с кодом 2xx, а обработчик ошибок — за остальные. Если восстановить ситуацию невозможно, следует вернуть отклонённое обещание, чтобы вызывающие коды всё равно увидели сбой.

Что происходит за кулисами одного вызова

Запрос в одну строку создаёт впечатление, что Axios отправляет его сразу же:

axios.get("/users");

На самом деле тот же запрос:

axios.get("/users")

проходит через более длинную цепочку обработки перед тем, как выполнится метод .then():

↓Request Interceptor↓HTTP Request↓Server↓HTTP Response↓Response Interceptor↓.then()↓Your Component

Общая логика выполняется при каждом вызове, причём место вызова об этом не знает.

Почему централизация важна по мере роста приложения

Представьте проект с сотней API-запросов, написанных вручную. Запрос от пользователя содержит заголовок:

axios.get("/users", {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

То же самое касается запроса на товары:

axios.get("/products", {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

И любого оператора записи, например создания заказа:

axios.post("/orders", data, {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

Если бэкенд изменит схему аутентификации, необходимо обновить каждый запрос, и каждый пропущенный запрос становится ошибкой. Интерцептор хранит эту логику один раз, а Axios применяет её везде. Зарегистрируйте его на экземпляре, созданном с помощью axios.create(), чтобы он действовал для вашего API, а не для URL сторонних сервисов.

Сценарий использования: автоматическое добавление токена доступа

Это самая распространённая причина использования интерцепторов. После входа в систему приложение хранит токен доступа, который требуется каждому защищённому концовке. Запрос начинается как обычный запрос:

API Call

а интерцептор добавляет заголовок до того, как запрос достигнет сервера:

↓Request Interceptor↓Authorization Header Added↓Server

Теперь каждый запрос содержит токен. Читайте его внутри интерцептора при каждом запросе, а не один раз при запуске, иначе обновлённый токен никогда не будет использован.

Сценарий использования: обработка ответов 401 в целом

Токены доступа истекают. Когда это происходит, сервер отвечает следующим образом:

401 Unauthorized

Без интерцепторов каждый запрос требует отдельной обработки. Первый запрос к API:

API 1

следуется за аналогичной обработкой, которая дублируется для всех остальных конечных точек:

↓401 HandlingAPI 2↓401 HandlingAPI 3↓401 Handling

Интерцептор ответов может собирать все сообщения 401 Unauthorized в одном месте. Обнаружив такое сообщение:

401 Received

он может обновить токен доступа, повторно отправить исходный запрос и вернуть успешный ответ вызывающему коду:

↓Refresh Access Token↓Retry Original Request↓Return Successful Response

Пользователь никогда не замечает истекшего токена. Важны два меры предосторожности: отмечать повторные запросы в их конфигурации, чтобы второй код 401 не мог создавать бесконечный цикл, и обеспечивать совместное использование одного запроса на обновление при одновременных сбоях, что рассматривается в статье почему одновременные ошибки 401 выводят пользователя из системы и как это исправить с помощью единого запроса на обновление.

Когда интерцепторы бесполезны

Небольшое приложение с двумя-тремя запросами к API почти не выигрывает от использования интерцепторов, а скрытое поведение может удивить читателя, который не видит источника заголовка. При десятках или сотнях запросов интерцепторы являются одним из лучших способов сохранить чистоту и последовательность обработки запросов. Используйте их для решения вопросов, касающихся нескольких частей приложения; логика, связанная с конкретным эндпоинтом, должна находиться в самом эндпоинте.

Заключение

Отправка запросов — это лишь небольшая часть задач слоя HTTP. Аутентификация, обработка ошибок, ведение журналов, формирование ответов и обновление токенов являются межсекторными задачами, и интерцепторы обеспечивают для каждой из них единое решение.

  • Рассматривайте интерцепторы как средний слой на стороне клиента: интерцепторы запросов формируют то, что отправляется, а интерцепторы ответов — то, что приходит.
  • Всегда возвращайте конфигурацию из интерцепторов запросов и снова выбрасывайте нерекупируемые ошибки из интерцепторов ответов.
  • Прикрепляйте их к экземпляру axios.create(), настроенному под ваш API.
  • Защищайте логику повторных попыток при ошибке 401 от циклов и одновременных обновлений.