Перехватчики
Часть раздела 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(...) — к одному контроллеру.
Задание
Легко«Запустить» снова запускает ваш трекер, используя src/main.ts. Отдельные файлы для практики могут оставаться в дереве; этот сервер их не запускает.
Клиенты и ваша собственная система мониторинга хотят знать, сколько времени занял каждый запрос. Добавьте заголовок ответа X-Response-Time ко всем успешным ответам, не изменяя ни одного маршрута.
- Откройте
src/common/response-time.interceptor.ts. В методеinterceptклассаResponseTimeInterceptorсначала запишите текущее время с помощьюDate.now(). Код в начале методаinterceptвыполняется до метода маршрута. - Получите
resExpress с помощьюcontext.switchToHttp().getResponse(). next.handle()запускает метод маршрута и возвращает его результат в виде Observable RxJS: значения, которое поступит позже. Продолжайте возвращать его, но передайте его черезtap, уже импортированный изrxjs, используя метод Observable.pipe(). Переданная вtapфункция запускается, когда поступает результат, до того как Nest отправит его. В этой функции вызовитеres.setHeader, чтобы задать дляX-Response-Timeзначение, равное количеству миллисекунд, прошедших с момента записи времени, с добавлениемms, например3ms. (Метод.pipe()RxJS не имеет отношения к валидационным конвейерам из главы 3. Он добавляет шаг в цепочку Observable.)- В файле
src/main.tsзарегистрируйте перехватчик для всего приложения с помощьюapp.useGlobalInterceptors(...), передав новый экземплярResponseTimeInterceptorрядом с конвейером и фильтром. - Тела ответов не должны измениться.
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();
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела TypeScript и NestJS
Потренируйтесь самостоятельно: Песочница Express