NestJS / 38 минут
Исключения и filters
Преобразуйте исключения в единый безопасный HTTP-контракт.
- Преобразуйте исключения в единый безопасный HTTP-контракт.
- Применить тему в задаче «Исключения и filters» и объяснить граничные случаи.
Около 8 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.
Ошибочная ветвь HTTP
Исключение прерывает обычный путь выполнения. Nest имеет стандартную обработку HttpException и производных классов: BadRequestException, NotFoundException, ForbiddenException и других. Неожиданный внутренний сбой обычно превращается в 500. Exception filter позволяет согласовать внешний формат ошибок и сохранить различие между отказом клиента и поломкой сервера.
Контроллер или сервис может бросить исключение, но обязан понимать его смысл. Не найденная книга и недоступная БД — разные ситуации. Если любую ошибку перехватить и превратить в 404, клиент получит ложную информацию, а операторы потеряют сигнал сбоя. Отправляйте безопасный внешний ответ, сохраняя внутреннюю причину в защищённой диагностике.
@Get(':id')
async find(@Param('id') id: string) {
const book = await this.books.find(id);
if (!book) throw new NotFoundException('Книга не найдена');
return book;
}Тип и область перехвата
Catch задаёт типы исключений, которые обрабатывает фильтр. ExceptionFilter реализует catch с исключением и ArgumentsHost. Для HTTP switchToHttp предоставляет request и response. Пустой Catch перехватывает любые ошибки, поэтому требует аккуратного различения известных и неожиданных случаев. Не считайте произвольный объект исключением с getStatus без проверки.
@Catch(HttpException)
class RequestFilter implements ExceptionFilter {
catch(error: HttpException, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse<Response>();
response.status(error.getStatus()).json({ error: 'request' });
}
}Response здесь импортируется как тип из express для Express-адаптера. Если проект должен поддерживать разные HTTP-адаптеры, используйте HttpAdapterHost вместо привязки к конкретным методам ответа. Выбор адаптера — техническая деталь инфраструктуры; он не должен разрастаться по всем доменным сервисам.
Безопасный контракт ошибок
Клиенту полезны стабильный код ошибки, понятное сообщение и при необходимости безопасные ошибки полей. Stack trace, SQL, абсолютные пути и credentials не нужны для исправления пользовательского ввода. Даже сообщение известного HttpException может содержать данные, которые не следует пересылать без проверки, если оно создано из текста стороннего сервиса.
type PublicError = {
error: { code: 'NOT_FOUND' | 'INVALID_INPUT' | 'INTERNAL'; message: string };
};
const internal: PublicError = {
error: { code: 'INTERNAL', message: 'Не удалось выполнить запрос' },
};Такой контракт не обязан совпадать с форматом успеха. Не маскируйте ошибку кодом 200 ради одинакового JSON: кеши, мониторинг и клиенты ориентируются на HTTP-статус. Для связи обращения с логами можно возвращать безопасный идентификатор запроса, не содержащий токен сессии или секретную информацию.
Где зарегистрировать фильтр
UseFilters назначает фильтр методу или контроллеру. Глобальный APP_FILTER удобен для единой границы приложения с DI. Передача new SafeFilter в useGlobalFilters подходит для простого независимого фильтра. Если фильтру нужен logger или HTTP adapter, контейнерная регистрация делает зависимость явной и проверяемой.
@Module({
providers: [{ provide: APP_FILTER, useClass: GlobalErrorFilter }],
})
class AppModule {}GlobalErrorFilter в примере — реализованный в приложении класс, APP_FILTER импортируется из @nestjs/core. Фильтр конкретного маршрута может обработать ошибку раньше глобального. Не рассчитывайте на последовательное «дополнение» одного ответа всеми filters: определите единственного владельца формата и проверяйте область назначения.
Ресурсы и повторный ответ
Фильтр должен завершить ответ один раз. После отправки заголовков повторный json приводит к дополнительной ошибке. Потоковые ответы требуют отдельного подхода: часть данных могла уйти клиенту до сбоя. Не пытайтесь в таком случае заменить уже начатый файл обычным JSON-контрактом без проверки состояния ответа.
Освобождение ресурса принадлежит тому коду, который им владеет. Используйте finally для подключения, блокировки или временного объекта, а не надейтесь, что filter догадается обо всех открытых ресурсах. Асинхронную фоновую задачу, не включённую в цепочку запроса, стандартный HTTP filter также не обязан перехватить: у фоновых процессов должна быть собственная обработка ошибок.
Практика безопасного отказа
Создайте GET /fail, бросающий BadRequestException, и фильтр для HttpException. Он сохраняет статус исключения и отвечает только объектом {error: "request"}. solve возвращает реальные status и body из HTTP-проверки. Частное сообщение исходного исключения не должно попасть в ответ. Контекст приложения закрывается в finally.
После решения добавьте в локальном проекте отдельный случай неожиданного Error и убедитесь, что он даёт 500 с безопасным сообщением. Проверьте отсутствие stack и внутренних полей. Это проверка наблюдаемого поведения API; точное количество вызовов вспомогательного метода логирования само по себе не доказывает безопасность внешнего ответа.