Перехватчики(Interceptors)
Перехватчик - это класс, аннотированный декоратором @Injectable(). Перехватчики должны реализовать интерфейс NestInterceptor.

Перехватчики обладают набором полезных возможностей, которые вдохновлены техникой аспектно-ориентированного программирования (АОП). Они делают возможным следующее:
привязать дополнительной логики до/после выполнения метода
преобразовать результат, возвращаемый функцией
преобразовать исключение, вызванное из функции
расширить базовое поведение функции
полностью переопределить функцию в зависимости от конкретных условий (например, для целей кэширования)
Основы
Каждый перехватчик реализует метод intercept(), который принимает два аргумента. Первый-это экземпляр ExecutionContext (точно такой же объект, как и для guards). ExecutionContext наследуется от ArgumentsHost. Мы уже видели ArgumentsHost раньше в главе фильтры исключений. Там мы увидели, что это оболочка вокруг аргументов, которые были переданы исходному обработчику, и содержит различные массивы аргументов, основанные на типе приложения. Вы можете вернуться к фильтрам исключений для получения дополнительной информации по этому вопросу.
Контекст выполнения
Расширяя ArgumentsHost, ExecutionContext также добавляет несколько новых вспомогательных методов, которые предоставляют дополнительные сведения о текущем процессе выполнения. Эти сведения могут быть полезны при создании более универсальных перехватчиков, которые могут работать с широким набором контроллеров, методов и контекстов выполнения. Узнайте больше о ExecutionContext здесь.
Обработчик вызовов
Второй аргумент - это CallHandler. Интерфейс CallHandler реализует метод handle(), который можно использовать для вызова метода обработчика маршрута в какой-то момент в вашем перехватчике. Если вы не вызываете метод handle() в своей реализации метода intercept(), то метод обработчика маршрута вообще не будет выполняться.
Этот подход означает, что метод intercept() эффективно обертывает поток запроса/ответа. В результате пользовательская логика может быть реализована как до, так и после выполнения конечного обработчика маршрута. Понятно, что вы можете написать код в своем методе intercept(), который выполняется перед вызовом handle(), но как вы повлияете на то, что произойдет после этого? Поскольку метод handle() возвращает Observable, мы можем использовать мощные операторы RxJS для дальнейшего манипулирования ответом. Используя терминологию аспектно-ориентированного программирования, вызов обработчика маршрута (т.е. вызова handle()) называется Pointcut(срез, запрос точек присоединения), указывая, что это точка, в которую вставляется наша дополнительная логика.
Рассмотрим, например, входящий запрос POST /cats. Этот запрос предназначен для обработчика create(), определенного внутри CatsController. Если перехватчик, который не вызывает метод handle(), вызывается где-либо на этом пути, метод create() не будет выполнен. Как только будет вызван handle() (и его Observable будет возвращен), будет запущен обработчик create(). И как только поток ответа получен через Observable, дополнительные операции могут быть выполнены в потоке, и конечный результат возвращается вызывающему.
Перехват аспекта
Первый вариант использования, который мы рассмотрим, - это использование перехватчика для регистрации взаимодействия пользователей (например, хранение пользовательских вызовов, асинхронная отправка событий или вычисление временной метки). Ниже мы покажем простой LoggingInterceptor:
logging.interceptor.ts
ВNestInterceptor<T, R> это дженерик-интерфейс, в котором Т обозначает тип Observable<T>(поддерживающий поток ответа), а R - это тип значения, обернутый Observable<R>.
Перехватчики, как и контроллеры, провайдеры, guards и т.д. могут вводить зависимости(inject dependencies) через свой конструктор.
Поскольку метод handle() возвращает наблюдаемый объект RxJS Observable, у нас есть широкий выбор операторов, которые мы можем использовать для управления потоком. В приведенном выше примере мы использовали оператор tap(), который вызывает нашу анонимную функцию ведения журнала при изящном или исключительном завершении наблюдаемого потока, но в остальном не вмешивается в жизненный цикл ответа.
Связывание перехватчиков(Binding interceptors)
Для настройки перехватчика мы используем декоратор @UseInterceptors(), импортированный из пакета @nestjs/common. Как и Pipes и Guards, перехватчики могут иметь область действия контроллера, область действия метода или глобальную область действия.
cats.controller.ts
Используя приведенную выше конструкцию, каждый обработчик маршрута, определенный в CatsController, будет использовать LoggingInterceptor. Когда кто-то вызывает конечную точку GET /cats, вы увидите следующие выходные данные в своем стандартном выводе:
Обратите внимание, что мы передали тип LoggingInterceptor (вместо экземпляра), оставив ответственность за создание экземпляра на фреймворке и включив инъекцию зависимостей. Как и в случае с каналами, защитниками и фильтрами исключений, мы также можем передать экземпляр на месте:
cats.controller.ts
Как уже упоминалось, приведенная выше конструкция присоединяет перехватчик к каждому обработчику, объявленному этим контроллером. Если мы хотим ограничить область действия перехватчика одним методом, мы просто применяем декоратор на уровне метода.
Для того, чтобы создать глобальный перехватчик, мы используем useGlobalInterceptors() метод экземпляра приложения Nest:
Глобальные перехватчики используются во всем приложении, для каждого контроллера и каждого обработчика маршрута. С точки зрения внедрения зависимостей глобальные перехватчики, зарегистрированные вне любого модуля (с помощью useGlobalInterceptors(), как в приведенном выше примере), не могут вводить зависимости, поскольку это делается вне контекста любого модуля. Чтобы решить эту проблему, вы можете настроить перехватчик непосредственно из любого модуля, используя следующую конструкцию:
app.module.ts
При использовании этого подхода для выполнения инъекции зависимостей для перехватчика обратите внимание, что независимо от модуля, в котором используется эта конструкция, перехватчик фактически является глобальным. Где это должно быть сделано? Выберите модуль, в котором перехватчик (LoggingInterceptor в приведенном выше примере) будет определен. Кроме того, useClass- это не единственный способ справиться с пользовательской регистрацией провайдера. Узнайте больше здесь.
Сопоставление ответов(Response mapping)
Мы уже знаем, что функция handle() возвращает Observable. Поток содержит значение, возвращаемое обработчиком маршрута, и поэтому мы можем легко изменить его с помощью оператора RxJS map().
Функция сопоставления ответов не работает со специфичной для библиотеки стратегией ответа (прямое использование объекта @Res() запрещено).
Давайте создадим TransformInterceptor, который будет изменять каждый ответ тривиальным образом, чтобы продемонстрировать этот процесс. Он будет использовать оператор map() RxJS для назначения объекта response свойству data вновь созданного объекта, возвращая новый объект клиенту.
transform.interceptor.ts
Перехватчики Nest работают как с синхронными, так и с асинхронными методами intercept(). Вы можете просто изменить метод на async, если это необходимо.
При приведенной выше конструкции, когда кто-то вызывает конечную точку GET /cats, ответ будет выглядеть следующим образом (предполагая, что обработчик маршрута возвращает пустой массив []):
Перехватчики имеют большое значение при создании многоразовых решений для требований, возникающих в рамках всего приложения. Например, представьте, что нам нужно преобразовать каждое вхождение null значения в пустую строку ". Мы можем сделать это с помощью одной строки кода и привязать перехватчик глобально, чтобы он автоматически использовался каждым зарегистрированным обработчиком.
Сопоставление исключений(Exception mapping)
Еще один интересный пример использования заключается в использовании оператора catchError() RxJS для переопределения брошенных исключений:
errors.interceptor.ts
Переопределение потока
Существует несколько причин, по которым мы иногда можем полностью запретить вызов обработчика и вместо этого возвращать другое значение. Очевидным примером является реализация кэша для улучшения времени отклика. Давайте посмотрим на простой cache interceptor, который возвращает свой ответ из кэша. В реалистичном примере мы хотели бы рассмотреть другие факторы, такие как TTL, недействительность кэша, размер кэша и т.д., но это уже выходит за рамки нашего обсуждения. Здесь мы приведем простой пример, который демонстрирует основную концепцию.
cache.interceptor.ts
Наш CacheInterceptor имеет жестко закодированную переменную isCached и жестко закодированный ответ []. Ключевой момент, который следует отметить, заключается в том, что мы возвращаем здесь новый поток, созданный оператором RxJS of(), поэтому обработчик маршрута вообще не будет вызываться. Когда кто-то вызывает конечную точку, использующую CacheInterceptor, ответ (жестко закодированный пустой массив) будет возвращен немедленно. Чтобы создать универсальное решение, вы можете воспользоваться преимуществами Reflector и создать кастомный декоратор. Reflector хорошо описан в главе "Guards".
Несколько операторов
Возможность манипулирования потоком с помощью операторов RxJS дает нам множество возможностей. Давайте рассмотрим еще один распространенный случай использования. Представьте, что вы хотели бы обрабатывать тайм-ауты в запросах маршрута. Если ваша конечная точка ничего не возвращает через некоторое время, вы хотите завершить работу с ответом на ошибку. Следующая конструкция позволяет это сделать:
timeout.interceptor.ts
Через 5 секунд обработка запроса будет отменена. Вы также можете добавить пользовательскую логику перед запуском RequestTimeoutException (например, высвободить ресурсы).
Last updated