Как реализовать интерцептор (interceptor) для HTTP-запросов?angular-44

HTTP-интерцепторы (interceptors) — мощный механизм для перехвата и обработки HTTP-запросов и ответов. Рассмотрим их создание и применение.

1. Базовый интерцептор

Создадим простейший интерцептор, который логирует все запросы:

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

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    console.log(`Запрос к URL: ${request.url}`);
    return next.handle(request);
  }
}

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

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

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

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: LoggingInterceptor,
      multi: true // важно для нескольких интерцепторов
    }
  ]
})
export class AppModule {}

3. Основные сценарии использования

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

intercept(request: HttpRequest<any>, next: HttpHandler) {
  const authReq = request.clone({
    headers: request.headers.set('Authorization', 'Bearer ' + this.auth.getToken())
  });
  return next.handle(authReq);
}

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

intercept(request: HttpRequest<any>, next: HttpHandler) {
  return next.handle(request).pipe(
    catchError(error => {
      if (error.status === 401) {
        this.auth.logout();
      }
      return throwError(error);
    })
  );
}

Добавление базового URL

intercept(request: HttpRequest<any>, next: HttpHandler) {
  const apiReq = request.clone({
    url: `https://api.example.com/${request.url}`
  });
  return next.handle(apiReq);
}

Кэширование запросов

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

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

  const cachedResponse = this.cache.get(request.url);
  if (cachedResponse) {
    return of(cachedResponse);
  }

  return next.handle(request).pipe(
    tap(event => {
      if (event instanceof HttpResponse) {
        this.cache.set(request.url, event);
      }
    })
  );
}

4. Особенности работы с интерцепторами

  1. Порядок выполнения: Интерцепторы выполняются в порядке регистрации
  2. Иммутабельность: Запросы нужно клонировать (request.clone())
  3. Множественность: Можно зарегистрировать несколько интерцепторов
  4. Обработка ошибок: Ошибки можно обрабатывать как в интерцепторе, так и в сервисе

5. Продвинутые техники

Перехват и модификация ответа

intercept(request: HttpRequest<any>, next: HttpHandler) {
  return next.handle(request).pipe(
    map(event => {
      if (event instanceof HttpResponse) {
        return event.clone({ body: this.modifyBody(event.body) });
      }
      return event;
    })
  );
}

Отображение загрузки

intercept(request: HttpRequest<any>, next: HttpHandler) {
  this.loadingService.show();

  return next.handle(request).pipe(
    finalize(() => this.loadingService.hide())
  );
}

Retry при неудачных запросах

intercept(request: HttpRequest<any>, next: HttpHandler) {
  return next.handle(request).pipe(
    retryWhen(errors => errors.pipe(
      delay(1000),
      take(3)
    )
  );
}

Лучшие практики

  1. Один интерцептор — одна ответственность
  2. Не злоупотребляйте — слишком много логики в интерцепторах усложняет отладку
  3. Тестируйте интерцепторы изолированно
  4. Порядок регистрации имеет значение
  5. Используйте environment для конфигурации

Резюмируем

HTTP-интерцепторы в Angular позволяют:

  • Централизованно обрабатывать запросы и ответы
  • Добавлять заголовки авторизации
  • Глобально обрабатывать ошибки
  • Реализовывать кэширование
  • Добавлять логирование
  • Управлять состоянием загрузки

Пример комплексного интерцептора:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private auth: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // Пропускаем запросы авторизации
    if (req.url.includes('/auth')) {
      return next.handle(req);
    }

    const authReq = req.clone({
      headers: req.headers.set('Authorization', `Bearer ${this.auth.getToken()}`),
      url: environment.apiUrl + req.url
    });

    return next.handle(authReq).pipe(
      catchError(err => {
        if (err.status === 401) {
          this.auth.logout();
        }
        return throwError(err);
      }),
      tap(event => {
        if (event instanceof HttpResponse) {
          console.log(`Ответ от ${req.url}`, event);
        }
      })
    );
  }
}