Menu
CoddyTech

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

Часть раздела TypeScript и NestJS путешествия по Node.js на Coddy. Урок 23 из 24.

Вы хотите, чтобы в каждом ответе указывалось, сколько времени он занял, не изменяя каждый обработчик. Перехватчик оборачивает обработчик: код перед next.handle() выполняется до него, а код, выполняемый с результатом next.handle(), — после него.

intercept(context: ExecutionContext, next: CallHandler) {
    console.log('before');
    return next.handle().pipe(
        tap(() => console.log('after')),
    );
}

next.handle() возвращает результат обработчика в виде наблюдаемого объекта RxJS — значения, которое приходит позже. .pipe(tap(...)) запускает функцию, когда значение приходит, до того, как Nest отправит его; этот pipe не имеет отношения к валидационным конвейерам Nest. Стражи выполняются до любого перехватчика, поэтому запрос, отклонённый стражем, до него не доходит. Возвращайте наблюдаемый объект с конвейером: Nest отправляет то, что он содержит. context.switchToHttp().getResponse() возвращает res, у которого setHeader добавляет заголовок.

app.useGlobalInterceptors(...) применяет перехватчик ко всем маршрутам; @UseInterceptors(...) — к одному контроллеру.

challenge icon

Задание

Легко

«Запустить» снова запускает ваш трекер, используя src/main.ts. Отдельные файлы для практики могут оставаться в дереве; этот сервер их не запускает.

Клиенты и ваша собственная система мониторинга хотят знать, сколько времени занял каждый запрос. Добавьте заголовок ответа X-Response-Time ко всем успешным ответам, не изменяя ни одного маршрута.

  1. Откройте src/common/response-time.interceptor.ts. В методе intercept класса ResponseTimeInterceptor сначала запишите текущее время с помощью Date.now(). Код в начале метода intercept выполняется до метода маршрута.
  2. Получите res Express с помощью context.switchToHttp().getResponse().
  3. next.handle() запускает метод маршрута и возвращает его результат в виде Observable RxJS: значения, которое поступит позже. Продолжайте возвращать его, но передайте его через tap, уже импортированный из rxjs, используя метод Observable .pipe(). Переданная в tap функция запускается, когда поступает результат, до того как Nest отправит его. В этой функции вызовите res.setHeader, чтобы задать для X-Response-Time значение, равное количеству миллисекунд, прошедших с момента записи времени, с добавлением ms, например 3ms. (Метод .pipe() RxJS не имеет отношения к валидационным конвейерам из главы 3. Он добавляет шаг в цепочку Observable.)
  4. В файле src/main.ts зарегистрируйте перехватчик для всего приложения с помощью app.useGlobalInterceptors(...), передав новый экземпляр ResponseTimeInterceptor рядом с конвейером и фильтром.
  5. Тела ответов не должны измениться. GET /tasks по-прежнему возвращает { "items": [...], "page": 1, "total": 3, "pages": 2 }.

Попробуйте: отправьте GET /tasks и откройте вкладку «Заголовки» ответа. Вы увидите X-Response-Time со значением вроде 0ms или 2ms. Этот заголовок есть и в ответе на GET /stats, а также на POST-запрос с { "title": "Buy milk" }, отправленный с заголовком x-api-key: tasks-key-123. Затем отправьте этот POST-запрос без ключа: ответ 403, а заголовка X-Response-Time нет. Nest запускает защитники до перехватчиков, поэтому защитник отклонил запрос до запуска вашего перехватчика. Ошибка, выброшенная внутри маршрута, тоже пропускает заголовок, поскольку tap запускается только при поступлении результата.

Попробуйте сами

import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable, tap } from 'rxjs';

@Injectable()
export class ResponseTimeInterceptor implements NestInterceptor {
    intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
        // Запомните время, дайте обработчику выполниться, затем установите X-Response-Time.
        return next.handle();
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела TypeScript и NestJS

Потренируйтесь самостоятельно: Песочница Express