Аксиос-интерцепторы в качестве HTTP-мидлвэра: концептуальная модель для приложений React
Понять, как интерцепторы запросов и ответов Axios функционируют в качестве промежуточного слоя, что должно находиться в каждом из них, и как они централизуют обработку заголовков аутентификации и ошибок 401 в React.
Источники: 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 от циклов и одновременных обновлений.