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

HTTP-интерцепторы в Angular — это механизм перехвата и обработки всех исходящих HTTP-запросов и входящих HTTP-ответов, выполняемых через HttpClient. Они позволяют централизованно реализовывать сквозную логику, не дублируя код в каждом сервисе работы с API.

Интерцепторы применяются для:

  • добавления заголовков (Authorization, Content-Type и др.)
  • логирования запросов и ответов
  • обработки ошибок
  • кеширования данных
  • трансформации запросов и ответов
  • отображения глобальных индикаторов загрузки

Интерцептор не заменяет сервисы API, а дополняет их, работая на уровне инфраструктуры.


Архитектура и место в HTTP-пайплайне

Все HTTP-запросы в Angular проходят через цепочку интерцепторов. Эта цепочка формируется в порядке регистрации провайдеров.

Пайплайн выглядит следующим образом:

  1. Компонент или сервис вызывает HttpClient
  2. Запрос последовательно проходит через все интерцепторы
  3. Запрос отправляется на сервер
  4. Ответ возвращается назад через те же интерцепторы в обратном порядке
  5. Результат передаётся вызывающему коду

Каждый интерцептор может:

  • модифицировать запрос
  • передать запрос дальше без изменений
  • заменить запрос
  • перехватить и изменить ответ
  • обработать или пробросить ошибку

Интерфейс HttpInterceptor

Интерцептор представляет собой сервис, реализующий интерфейс HttpInterceptor.

import { Injectable } from '@angular/core';
import {
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class ExampleInterceptor implements HttpInterceptor {

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {

    return next.handle(req);
  }
}

Ключевые элементы:

  • HttpRequest — неизменяемый объект запроса
  • HttpHandler — следующий обработчик в цепочке
  • next.handle() — обязательный вызов для продолжения цепочки

Неизменяемость HTTP-запросов

HttpRequest является immutable-объектом. Любое изменение выполняется через метод clone().

const modifiedRequest = req.clone({
  setHeaders: {
    Authorization: 'Bearer TOKEN'
  }
});

Попытка изменить исходный объект приведёт к ошибке компиляции или игнорированию изменений.


Регистрация интерцептора

Интерцепторы регистрируются через DI-провайдер HTTP_INTERCEPTORS.

import { HTTP_INTERCEPTORS } from '@angular/common/http';

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ExampleInterceptor,
      multi: true
    }
  ]
})
export class AppModule {}

Ключевой параметр:

  • multi: true — позволяет регистрировать несколько интерцепторов

Без multi: true предыдущие интерцепторы будут перезаписаны.


Порядок выполнения интерцепторов

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

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: FirstInterceptor, multi: true },
  { provide: HTTP_INTERCEPTORS, useClass: SecondInterceptor, multi: true }
]
  • Запрос: First → Second → сервер
  • Ответ: сервер → Second → First

Это важно при работе с логированием, авторизацией и обработкой ошибок.


Добавление заголовков авторизации

Один из самых распространённых сценариев — автоматическое добавление токена.

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const token = localStorage.getItem('token');

    if (!token) {
      return next.handle(req);
    }

    const authReq = req.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });

    return next.handle(authReq);
  }
}

Особенности:

  • отсутствие токена не блокирует запрос
  • логика авторизации полностью изолирована от сервисов API

Обработка HTTP-ошибок

Интерцепторы удобны для глобальной обработки ошибок через RxJS.

import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    return next.handle(req).pipe(
      catchError(error => {
        if (error.status === 401) {
          // обработка ошибки авторизации
        }

        if (error.status === 500) {
          // серверная ошибка
        }

        return throwError(() => error);
      })
    );
  }
}

Преимущества:

  • единая точка обработки ошибок
  • отсутствие дублирования catchError в каждом сервисе
  • возможность интеграции с логированием и уведомлениями

Логирование запросов и ответов

Интерцепторы позволяют отслеживать весь HTTP-трафик.

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    console.log('Request:', req);

    return next.handle(req).pipe(
      tap(event => {
        console.log('Response:', event);
      })
    );
  }
}

Часто используется только в режиме разработки и отключается для production-сборок.


Индикатор загрузки

Глобальный loader реализуется без привязки к конкретным компонентам.

@Injectable()
export class LoaderInterceptor implements HttpInterceptor {

  private requests = 0;

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    this.requests++;

    return next.handle(req).pipe(
      finalize(() => {
        this.requests--;
        if (this.requests === 0) {
          // скрыть индикатор
        }
      })
    );
  }
}

Позволяет учитывать параллельные запросы и избегать преждевременного скрытия индикатора.


Условное применение интерцепторов

Иногда требуется пропустить интерцептор для определённых запросов.

Пример с кастомным заголовком:

if (req.headers.has('skip-auth')) {
  const cleanReq = req.clone({
    headers: req.headers.delete('skip-auth')
  });

  return next.handle(cleanReq);
}

Или проверка URL:

if (req.url.includes('/public')) {
  return next.handle(req);
}

Это позволяет гибко управлять поведением без создания отдельных HttpClient-экземпляров.


Кеширование ответов

Интерцепторы подходят для простого кеширования GET-запросов.

@Injectable()
export class CacheInterceptor implements HttpInterceptor {

  private cache = new Map<string, HttpEvent<any>>();

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    if (req.method !== 'GET') {
      return next.handle(req);
    }

    const cached = this.cache.get(req.urlWithParams);
    if (cached) {
      return of(cached);
    }

    return next.handle(req).pipe(
      tap(event => {
        this.cache.set(req.urlWithParams, event);
      })
    );
  }
}

Подходит для:

  • справочников
  • редко изменяемых данных
  • оптимизации сетевых запросов

Ограничения и рекомендации

  • Интерцепторы не предназначены для бизнес-логики
  • Не следует изменять тело запроса без строгой необходимости
  • Асинхронные операции внутри интерцептора должны быть детерминированными
  • Избыточное количество интерцепторов усложняет отладку
  • Порядок регистрации имеет критическое значение

Отличие от middleware

Интерцепторы Angular:

  • работают на уровне клиента
  • используют RxJS
  • интегрированы в DI-контейнер

Middleware:

  • работают на сервере
  • не имеют доступа к состоянию клиента
  • не зависят от UI-фреймворка

Итоговая роль интерцепторов

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