Spec-Zone.ru › Angular

Перехватчики

HttpClient поддерживает разновидность промежуточного ПО, известную как перехватчики.

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

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

Перехватчики

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

Перехватчики можно использовать для реализации различных распространённых шаблонов, например:

  • Добавление заголовков аутентификации к исходящим запросам к определённому API.
  • Повторные попытки выполнения неудачных запросов с экспоненциальной задержкой.
  • Кэширование ответов на определённый период времени или до сброса кэша при изменении данных.
  • Настройка разбора ответов.
  • Измерение времени ответа сервера и ведение журналов.
  • Управление элементами пользовательского интерфейса, например индикатором загрузки, во время выполнения сетевых операций.
  • Сбор и пакетная отправка запросов, сделанных за определённый промежуток времени.
  • Автоматическое завершение запросов с ошибкой по истечении настраиваемого срока или тайм-аута.
  • Регулярный опрос сервера и обновление результатов.

Определение перехватчика

Базовая форма перехватчика — это функция, которая получает исходящий HttpRequest и функцию next, представляющую следующий этап обработки в цепочке перехватчиков.

Например, этот loggingInterceptor записывает URL исходящего запроса в console.log перед передачей запроса дальше:

export function loggingInterceptor(
  req: HttpRequest<unknown>,
  next: HttpHandlerFn,
): Observable<HttpEvent<unknown>> {
  console.log(req.url);
  return next(req);
}

Чтобы этот перехватчик действительно перехватывал запросы, необходимо настроить HttpClient, чтобы он использовал его.

Настройка перехватчиков

Объявить набор перехватчиков, которые следует использовать, можно при настройке HttpClient через внедрение зависимостей, используя функцию withInterceptors:

bootstrapApplication(App, {
  providers: [provideHttpClient(withInterceptors([loggingInterceptor, cachingInterceptor]))],
});

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

Перехват событий ответа

Перехватчик может преобразовывать поток Observable из HttpEvent, возвращаемый next, чтобы получить доступ к ответу или изменить его. Поскольку этот поток включает все события ответа, может потребоваться проверить .type каждого события, чтобы определить окончательный объект ответа.

export function loggingInterceptor(
  req: HttpRequest<unknown>,
  next: HttpHandlerFn,
): Observable<HttpEvent<unknown>> {
  return next(req).pipe(
    tap((event) => {
      if (event.type === HttpEventType.Response) {
        console.log(req.url, 'returned a response with status', event.status);
      }
    }),
  );
}

СОВЕТ: Перехватчики естественным образом связывают ответы с исходящими запросами, поскольку преобразуют поток ответов в замыкании, которое захватывает объект запроса.

Изменение запросов

Большинство свойств экземпляров HttpRequest и HttpResponse неизменяемы, поэтому перехватчики не могут изменять их напрямую. Вместо этого перехватчики применяют изменения, клонируя эти объекты с помощью операции .clone() и указывая, какие свойства нужно изменить в новом экземпляре. Это может потребовать неизменяемого обновления самого значения (например, HttpHeaders или HttpParams).

Например, чтобы добавить заголовок к запросу:

const reqWithHeader = req.clone({
  headers: req.headers.set('X-New-Header', 'new header value'),
});

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

ВАЖНО: Тело запроса или ответа не защищено от глубоких изменений. Если перехватчику необходимо изменить тело, позаботьтесь о корректной обработке повторного запуска для одного и того же запроса.

Внедрение зависимостей в перехватчиках

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

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

export function authInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn) {
  // Inject the current `AuthService` and use it to get an authentication token:
  const authToken = inject(AuthService).getAuthToken();

  // Clone the request to add the authentication header.
  const newReq = req.clone({
    headers: req.headers.append('X-Authentication-Token', authToken),
  });
  return next(newReq);
}

Метаданные запроса и ответа

Часто бывает полезно включить в запрос информацию, которая не отправляется на серверную часть, а предназначена специально для перехватчиков. Объекты HttpRequest имеют объект .context, который хранит такие метаданные в виде экземпляра HttpContext. Этот объект действует как типизированная карта с ключами типа HttpContextToken.

Чтобы показать, как работает эта система, воспользуемся метаданными, чтобы управлять включением перехватчика кэширования для конкретного запроса.

Определение токенов контекста

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

export const CACHING_ENABLED = new HttpContextToken<boolean>(() => true);

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

Чтение токена в перехватчике

Затем перехватчик может прочитать токен и на основе его значения решить, применять ли логику кэширования:

export function cachingInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
  if (req.context.get(CACHING_ENABLED)) {
    // apply caching logic
    return ...;
  } else {
    // caching has been disabled for this request
    return next(req);
  }
}

Задание токенов контекста при выполнении запроса

При выполнении запроса через API HttpClient можно задать значения для HttpContextToken:

const data$ = http.get('/sensitive/data', {
  context: new HttpContext().set(CACHING_ENABLED, false),
});

Перехватчики могут считывать эти значения из HttpContext запроса.

Контекст запроса изменяем

В отличие от других свойств объектов HttpRequest, связанный с запросом объект HttpContext изменяем. Если перехватчик изменит контекст запроса, который затем будет отправлен повторно, при следующем запуске тот же перехватчик увидит изменения контекста. Это полезно, если необходимо передавать состояние между несколькими повторными попытками.

Синтетические ответы

Большинство перехватчиков просто вызывают обработчик next, преобразуя запрос или ответ, но это не является обязательным требованием. В этом разделе рассматриваются некоторые способы, с помощью которых перехватчик может реализовать более сложное поведение.

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

Ответ можно создать с помощью конструктора HttpResponse:

const resp = new HttpResponse({
  body: 'response body',
});

Работа с информацией о перенаправлении

Когда HttpClient использует бэкенд Fetch, ответы содержат свойство redirected, указывающее, является ли ответ результатом перенаправления. Это свойство соответствует спецификации нативного Fetch API и может быть полезно в перехватчиках для обработки перенаправлений.

Перехватчик может получить доступ к информации о перенаправлении и обработать её:

export function redirectTrackingInterceptor(
  req: HttpRequest<unknown>,
  next: HttpHandlerFn,
): Observable<HttpEvent<unknown>> {
  return next(req).pipe(
    tap((event) => {
      if (event.type === HttpEventType.Response && event.redirected) {
        console.log('Request to', req.url, 'was redirected to', event.url);
        // Handle redirect logic - maybe update analytics, security checks, etc.
      }
    }),
  );
}

Также можно использовать информацию о перенаправлении для реализации условной логики в перехватчиках:

export function authRedirectInterceptor(
  req: HttpRequest<unknown>,
  next: HttpHandlerFn,
): Observable<HttpEvent<unknown>> {
  return next(req).pipe(
    tap((event) => {
      if (event.type === HttpEventType.Response && event.redirected) {
        // Check if we were redirected to a login page
        if (event.url?.includes('/login')) {
          // Handle authentication redirect
          handleAuthRedirect();
        }
      }
    }),
  );
}

Работа с типами ответов

Когда HttpClient использует бэкенд Fetch, ответы содержат свойство type, указывающее, как браузер обработал ответ с учётом политик CORS и режима запроса. Это свойство соответствует спецификации нативного Fetch API и предоставляет полезные сведения для отладки проблем CORS и понимания доступности ответа.

Свойство type ответа может иметь следующие значения:

  • 'basic' — ответ с того же источника, все заголовки доступны
  • 'cors' — ответ из другого источника с правильно настроенными заголовками CORS
  • 'opaque' — ответ из другого источника без CORS; доступ к заголовкам и телу может быть ограничен
  • 'opaqueredirect' — ответ на перенаправленный запрос в режиме no-cors
  • 'error' — произошла ошибка сети

Перехватчик может использовать информацию о типе ответа для отладки CORS и обработки ошибок:

export function responseTypeInterceptor(
  req: HttpRequest<unknown>,
  next: HttpHandlerFn,
): Observable<HttpEvent<unknown>> {
  return next(req).pipe(
    map((event) => {
      if (event.type === HttpEventType.Response) {
        // Handle different response types appropriately
        switch (event.responseType) {
          case 'opaque':
            // Limited access to response data
            console.warn('Limited response data due to CORS policy');
            break;
          case 'cors':
          case 'basic':
            // Full access to response data
            break;
          case 'error':
            // Handle network errors
            console.error('Network error in response');
            break;
        }
      }
    }),
  );
}

Перехватчики на основе DI

HttpClient также поддерживает перехватчики, определённые как внедряемые классы и настроенные через систему DI. Возможности перехватчиков на основе DI идентичны возможностям функциональных перехватчиков, но механизм настройки отличается.

Перехватчик на основе DI — это внедряемый класс, реализующий интерфейс HttpInterceptor:

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, handler: HttpHandler): Observable<HttpEvent<any>> {
    console.log('Request URL: ' + req.url);
    return handler.handle(req);
  }
}

Перехватчики на основе DI настраиваются с помощью многоэлементного провайдера внедрения зависимостей:

bootstrapApplication(App, {
  providers: [
    provideHttpClient(
      // DI-based interceptors must be explicitly enabled.
      withInterceptorsFromDi(),
    ),

    {provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true},
  ],
});

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

Super-powered by Google ©2010–2025.
Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0.
https://angular.dev/guide/http/interceptors

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API