Exception filterlar
Nest ilova bo'ylab qayta ishlanmagan barcha istisnolarni qayta ishlash uchun mas'ul bo'lgan o'rnatilgan exceptions layer bilan keladi. Istisno ilova kodingiz tomonidan qo'lga olinm
Nest ilova bo'ylab qayta ishlanmagan barcha istisnolarni qayta ishlash uchun mas'ul bo'lgan o'rnatilgan exceptions layer bilan keladi. Istisno ilova kodingiz tomonidan qo'lga olinmasa, u ushbu qatlam tomonidan ushlanadi va keyin avtomatik ravishda mos, foydalanuvchi uchun qulay javob yuboriladi.
Tayyor holatda bu ish o'rnatilgan global exception filter tomonidan bajariladi; u HttpException (va undan meros oladigan sinflar) turidagi istisnolarni qayta ishlaydi. Istisno tanilmagan bo'lsa (HttpException ham emas, HttpException dan meros oladigan sinf ham emas), o'rnatilgan exception filter quyidagi standart JSON javobini hosil qiladi:
1{
2 "statusCode": 500,
3 "message": "Internal server error"
4}Global exception filter http-errors kutubxonasini qisman qo'llab-quvvatlaydi. Asosan, statusCode va message xossalarini o'z ichiga olgan har qanday tashlangan istisno to'g'ri to'ldiriladi va javob sifatida qaytariladi (tanilmagan istisnolar uchun odatdagi InternalServerErrorException o'rniga).
Standart istisnolarni tashlash
Nest @nestjs/common paketidan eksport qilinadigan o'rnatilgan HttpException sinfini taqdim etadi. Odatdagi HTTP REST/GraphQL API ilovalari uchun, muayyan xato holatlarida standart HTTP javob obyektlarini yuborish eng yaxshi amaliyot hisoblanadi.
Masalan, CatsController da findAll() metodi (GET route handler) bor. Tasavvur qilaylik, bu route handler qandaydir sabab bilan istisno tashlaydi. Buni ko'rsatish uchun uni quyidagicha hard-code qilamiz:
1@Get()
2async findAll() {
3 throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
4}Bu yerda HttpStatus dan foydalandik. Bu @nestjs/common paketidan import qilinadigan yordamchi enum.
Klient ushbu endpointni chaqirganda, javob quyidagicha bo'ladi:
1{
2 "statusCode": 403,
3 "message": "Forbidden"
4}HttpException konstruktori javobni aniqlovchi ikkita majburiy argument qabul qiladi:
responseargumenti JSON javob bodysini aniqlaydi. U quyida tasvirlanganidekstringyokiobjectbo'lishi mumkin.statusargumenti HTTP status code ni aniqlaydi.
Standart holatda JSON javob bodysi quyidagi ikkita xossani o'z ichiga oladi:
statusCode:statusargumentida berilgan HTTP status code qiymatiga standartlashadimessage:statusga asoslangan HTTP xatoning qisqa tavsifi
JSON javob bodysidagi faqat message qismini o'zgartirmoqchi bo'lsangiz,
response argumentida satr yuboring. To'liq JSON javob bodysini almashtirish uchun response argumentida obyekt uzating. Nest obyektni serializatsiya qilib, JSON javob bodysi sifatida qaytaradi.
Ikkinchi konstruktor argumenti - status - to'g'ri HTTP status code bo'lishi kerak.
Eng yaxshi amaliyot - @nestjs/common dan import qilingan HttpStatus enumidan foydalanish.
Uchinchi konstruktor argumenti (ixtiyoriy) - options - xato cause ni taqdim etish uchun ishlatilishi mumkin. Bu cause obyekti javob obyektiga serializatsiya qilinmaydi, ammo HttpException tashlanishiga sabab bo'lgan ichki xato haqida qimmatli ma'lumot berib, loglash uchun foydali bo'lishi mumkin.
Quyida butun javob bodysini almashtirish va xato sababini taqdim etish misoli:
1@Get()
2async findAll() {
3 try {
4 await this.service.findAll()
5 } catch (error) {
6 throw new HttpException({
7 status: HttpStatus.FORBIDDEN,
8 error: 'This is a custom message',
9 }, HttpStatus.FORBIDDEN, {
10 cause: error
11 });
12 }
13}Yuqoridagidan foydalanilganda, javob quyidagicha ko'rinadi:
1{
2 "status": 403,
3 "error": "This is a custom message"
4}Istisnolarni loglash
Standart holatda exception filter HttpException (va undan meros oladigan istisnolar) kabi o'rnatilgan istisnolarni loglamaydi. Bu istisnolar tashlanganda, ular konsolda ko'rinmaydi, chunki ular ilovaning normal oqimi bir qismi sifatida qaraladi. Xuddi shunday xatti-harakat WsException va RpcException kabi boshqa o'rnatilgan istisnolar uchun ham qo'llaniladi.
Bu istisnolar @nestjs/common paketidan eksport qilinadigan IntrinsicException bazaviy sinfidan meros oladi. Ushbu sinf normal ilova ishlashining bir qismi bo'lgan istisnolarni bo'lmaganlaridan ajratishga yordam beradi.
Agar bu istisnolarni loglashni xohlasangiz, maxsus exception filter yaratishingiz mumkin. Buni keyingi bo'limda tushuntiramiz.
Maxsus istisnolar
Ko'p holatlarda sizga maxsus istisnolar yozish shart bo'lmaydi va keyingi bo'limda ta'riflangan o'rnatilgan Nest HTTP istisnosidan foydalanishingiz mumkin. Agar baribir moslashtirilgan istisnolar yaratishingiz kerak bo'lsa, o'zingizning istisnolar ierarxiyasi ni yaratish yaxshi amaliyot; bunda maxsus istisnolar HttpException bazaviy sinfidan meros oladi. Bu yondashuv bilan Nest istisnolaringizni tanib oladi va xato javoblarini avtomatik boshqaradi. Keling, shunday maxsus istisnoni yaratamiz:
1export class ForbiddenException extends HttpException {
2 constructor() {
3 super('Forbidden', HttpStatus.FORBIDDEN);
4 }
5}ForbiddenException bazaviy HttpException dan meros olganligi sabab, u o'rnatilgan exception handler bilan to'liq mos ishlaydi, shuning uchun uni findAll() metodida bemalol ishlatishimiz mumkin.
1@Get()
2async findAll() {
3 throw new ForbiddenException();
4}O'rnatilgan HTTP istisnolari
Nest bazaviy HttpException dan meros oladigan standart istisnolar to'plamini taqdim etadi. Ular @nestjs/common paketidan eksport qilinadi va eng ko'p uchraydigan HTTP istisnolarini ifodalaydi:
BadRequestExceptionUnauthorizedExceptionNotFoundExceptionForbiddenExceptionNotAcceptableExceptionRequestTimeoutExceptionConflictExceptionGoneExceptionHttpVersionNotSupportedExceptionPayloadTooLargeExceptionUnsupportedMediaTypeExceptionUnprocessableEntityExceptionInternalServerErrorExceptionNotImplementedExceptionImATeapotExceptionMethodNotAllowedExceptionBadGatewayExceptionServiceUnavailableExceptionGatewayTimeoutExceptionPreconditionFailedException
Barcha o'rnatilgan istisnolar options parametri orqali xato cause va xato tavsifini ham taqdim etishi mumkin:
1throw new BadRequestException('Something bad happened', {
2 cause: new Error(),
3 description: 'Some error description',
4});Yuqoridagidan foydalansak, javob quyidagicha ko'rinadi:
1{
2 "message": "Something bad happened",
3 "error": "Some error description",
4 "statusCode": 400
5}Exception filterlar
Bazaviy (o'rnatilgan) exception filter ko'plab holatlarni avtomatik tarzda boshqarishi mumkin bo'lsa-da, siz exceptions layer ustidan to'liq nazoratni xohlashingiz mumkin. Masalan, loglash qo'shish yoki dinamik omillarga qarab boshqa JSON sxemasini ishlatishni istashingiz mumkin. Exception filterlar aynan shu maqsad uchun mo'ljallangan. Ular boshqaruv oqimini va klientga yuboriladigan javob tarkibini aniq boshqarishga imkon beradi.
Keling, HttpException sinfining instansiyasi bo'lgan istisnolarni ushlash va ular uchun maxsus javob mantiqini amalga oshirish uchun exception filter yaratamiz. Buni qilish uchun underlying platform Request va Response obyektlariga kirishimiz kerak. Request obyektini olib, original url ni ajratib olamiz va uni loglash ma'lumotiga qo'shamiz. Response obyektidan foydalanib, response.json() metodini chaqirish orqali yuboriladigan javobni bevosita nazorat qilamiz.
1import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
2import { Request, Response } from 'express';
3
4@Catch(HttpException)
5export class HttpExceptionFilter implements ExceptionFilter {
6 catch(exception: HttpException, host: ArgumentsHost) {
7 const ctx = host.switchToHttp();
8 const response = ctx.getResponse<Response>();
9 const request = ctx.getRequest<Request>();
10 const status = exception.getStatus();
11
12 response
13 .status(status)
14 .json({
15 statusCode: status,
16 timestamp: new Date().toISOString(),
17 path: request.url,
18 });
19 }
20}Barcha exception filterlar ExceptionFilter<T> generic interfeysini amalga oshirishi kerak. Bu sizdan catch(exception: T, host: ArgumentsHost) metodini ko'rsatilgan imzo bilan taqdim etishni talab qiladi. T istisno tipini bildiradi.
Agar @nestjs/platform-fastify dan foydalanayotgan bo'lsangiz, response.json() o'rniga response.send() dan foydalanishingiz mumkin. fastify dan to'g'ri tiplarni import qilishni unutmang.
@Catch(HttpException) dekoratori kerakli metadatani exception filterga biriktiradi va Nestga ushbu filter HttpException turidagi istisnolarni qidirayotganini va boshqa hech narsani emasligini bildiradi. @Catch() dekoratori bitta parametrni yoki vergul bilan ajratilgan parametrlar ro'yxatini qabul qilishi mumkin. Bu sizga bir vaqtning o'zida bir nechta istisno turlari uchun filter sozlash imkonini beradi.
Arguments host
Keling, catch() metodining parametrlarini ko'rib chiqamiz. exception parametri hozir qayta ishlanayotgan istisno obyektidir. host parametri esa ArgumentsHost obyektidir. ArgumentsHost kuchli yordamchi obyekt bo'lib, uni execution context chapter da batafsil ko'rib chiqamiz*. Bu kod namunasida biz undan original request handlerga (istisno kelib chiqqan controllerdagi) uzatilayotgan Request va Response obyektlariga havola olish uchun foydalanamiz. Bu kod namunasi ichida ArgumentsHost ning ba'zi yordamchi metodlarini qo'llab, kerakli Request va Response obyektlarini olamiz. ArgumentsHost haqida batafsil bu yerda.
*Ushbu abstraksiya darajasining sababi shuki, ArgumentsHost barcha kontekstlarda ishlaydi (masalan, hozir ishlayotgan HTTP server konteksti, shuningdek Microservices va WebSockets). Execution context bobida ArgumentsHost va uning yordamchi funksiyalari yordamida har qanday execution context uchun mos underlying arguments ga qanday kirishimizni ko'ramiz. Bu bizga barcha kontekstlarda ishlaydigan umumiy exception filterlarini yozish imkonini beradi.
Filterlarni ulash
Yangi HttpExceptionFilter ni CatsController ning create() metodiga bog'laylik.
1@Post()
2@UseFilters(new HttpExceptionFilter())
3async create(@Body() createCatDto: CreateCatDto) {
4 throw new ForbiddenException();
5}@UseFilters() dekoratori @nestjs/common paketidan import qilinadi.
Bu yerda biz @UseFilters() dekoratoridan foydalandik. @Catch() dekoratoriga o'xshash, u bitta filter instansiyasini yoki vergul bilan ajratilgan filter instansiyalari ro'yxatini qabul qilishi mumkin. Bu yerda biz HttpExceptionFilter instansiyasini joyida yaratdik. Muqobil ravishda, instansiya o'rniga sinfni uzatishingiz mumkin; bu instansiyalash mas'uliyatini freymvorkka qoldiradi va dependency injectionni yoqadi.
1@Post()
2@UseFilters(HttpExceptionFilter)
3async create(@Body() createCatDto: CreateCatDto) {
4 throw new ForbiddenException();
5}Imkon bo'lganda, filterlarni instansiya emas, sinf orqali qo'llashni afzal ko'ring. Bu xotira sarfini kamaytiradi, chunki Nest bir xil sinf instansiyalarini butun modul bo'ylab oson qayta ishlatishi mumkin.
Yuqoridagi misolda HttpExceptionFilter faqat bitta create() route handlerga qo'llanilgan, ya'ni u method-scoped. Exception filterlar turli darajalarda scoped bo'lishi mumkin: controller/resolver/gateway darajasida method-scoped, controller-scoped yoki global-scoped.
Masalan, filterni controller-scoped qilib sozlash uchun quyidagicha qilasiz:
1@Controller()
2@UseFilters(new HttpExceptionFilter())
3export class CatsController {}Bu konstruktsiya CatsController ichida aniqlangan har bir route handler uchun HttpExceptionFilter ni sozlaydi.
Global-scoped filter yaratish uchun quyidagicha qilasiz:
1async function bootstrap() {
2 const app = await NestFactory.create(AppModule);
3 app.useGlobalFilters(new HttpExceptionFilter());
4 await app.listen(process.env.PORT ?? 3000);
5}
6bootstrap();useGlobalFilters() metodi gatewaylar yoki gibrid ilovalar uchun filterlarni sozlamaydi.
Global-scoped filterlar butun ilova bo'ylab, har bir controller va har bir route handler uchun ishlatiladi. Dependency injection nuqtai nazaridan shuni yodda tuting: har qanday moduldan tashqarida (useGlobalFilters() orqali, yuqoridagi misoldagidek) ro'yxatdan o'tkazilgan global filterlar bog'liqliklarni in'eksiya qila olmaydi, chunki bu modul kontekstidan tashqarida amalga oshiriladi. Bu muammoni hal qilish uchun quyidagi konstruktsiya yordamida global-scoped filterni bevosita istalgan moduldan ro'yxatdan o'tkazishingiz mumkin:
1import { Module } from '@nestjs/common';
2import { APP_FILTER } from '@nestjs/core';
3
4@Module({
5 providers: [
6 {
7 provide: APP_FILTER,
8 useClass: HttpExceptionFilter,
9 },
10 ],
11})
12export class AppModule {}Filter uchun dependency injection'ni shu yondashuv orqali bajarayotganda, bu konstruktsiya qaysi modulda qo'llanmasin, filter aslida global ekanini yodda tuting. Buni qayerda qilish kerak? Filter (HttpExceptionFilter yuqoridagi misolda) aniqlangan modulni tanlang. Shuningdek, useClass maxsus provayder ro'yxatga olishning yagona usuli emas. Batafsil bu yerda.
Bu usul bilan kerak bo'lganicha filterlar qo'shishingiz mumkin; shunchaki har birini providers massiviga qo'shing.
Hammasini ushlash
Har qanday qayta ishlanmagan istisnoni (istisno turidan qat'i nazar) ushlash uchun @Catch() dekoratorining parametrlar ro'yxatini bo'sh qoldiring, masalan, @Catch().
Quyidagi misolda platformaga bog'liq bo'lmagan kod berilgan, chunki u javobni yetkazish uchun HTTP adapter dan foydalanadi va platformaga xos obyektlardan (Request va Response) bevosita foydalanmaydi:
1import {
2 ExceptionFilter,
3 Catch,
4 ArgumentsHost,
5 HttpException,
6 HttpStatus,
7} from '@nestjs/common';
8import { HttpAdapterHost } from '@nestjs/core';
9
10@Catch()
11export class CatchEverythingFilter implements ExceptionFilter {
12 constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
13
14 catch(exception: unknown, host: ArgumentsHost): void {
15 // In certain situations `httpAdapter` might not be available in the
16 // constructor method, thus we should resolve it here.
17 const { httpAdapter } = this.httpAdapterHost;
18
19 const ctx = host.switchToHttp();
20
21 const httpStatus =
22 exception instanceof HttpException
23 ? exception.getStatus()
24 : HttpStatus.INTERNAL_SERVER_ERROR;
25
26 const responseBody = {
27 statusCode: httpStatus,
28 timestamp: new Date().toISOString(),
29 path: httpAdapter.getRequestUrl(ctx.getRequest()),
30 };
31
32 httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
33 }
34}Hammasini ushlaydigan exception filterni ma'lum bir turga bog'langan filter bilan birlashtirganda, "Hammasini ushla" filteri bog'langan turdagi istisnoni to'g'ri qayta ishlashi uchun birinchi bo'lib e'lon qilinishi kerak.
Meros olish
Odatda siz ilovangiz talablarini qondirish uchun to'liq moslashtirilgan exception filterlar yaratasiz. Biroq, ba'zi holatlarda o'rnatilgan standart global exception filter ni shunchaki kengaytirib, muayyan omillarga ko'ra xatti-harakatni o'zgartirishni xohlashingiz mumkin.
Istisnolarni bazaviy filterga delegatsiya qilish uchun BaseExceptionFilter dan meros olib, meros qilingan catch() metodini chaqirishingiz kerak.
1import { Catch, ArgumentsHost } from '@nestjs/common';
2import { BaseExceptionFilter } from '@nestjs/core';
3
4@Catch()
5export class AllExceptionsFilter extends BaseExceptionFilter {
6 catch(exception: unknown, host: ArgumentsHost) {
7 super.catch(exception, host);
8 }
9}BaseExceptionFilter dan meros olgan method-scoped va controller-scoped filterlar new bilan instansiyalanmasligi kerak. Buning o'rniga, freymvork ularni avtomatik instansiyalasin.
Global filterlar bazaviy filterni meros olishi mumkin. Buni ikki usuldan biri bilan qilish mumkin.
Birinchi usul - maxsus global filterni instansiyalashda HttpAdapter havolasini in'eksiya qilish:
1async function bootstrap() {
2 const app = await NestFactory.create(AppModule);
3
4 const { httpAdapter } = app.get(HttpAdapterHost);
5 app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
6
7 await app.listen(process.env.PORT ?? 3000);
8}
9bootstrap();Ikkinchi usul - APP_FILTER tokenidan bu yerda ko'rsatilgandek foydalanish.