Execution context
Nest bir nechta ilova kontekstlari bo'ylab ishlaydigan ilovalarni yozishni osonlashtiradigan bir qator yordamchi sinflarni taqdim etadi (masalan, Nest HTTP serveriga asoslangan, mi
Nest bir nechta ilova kontekstlari bo'ylab ishlaydigan ilovalarni yozishni osonlashtiradigan bir qator yordamchi sinflarni taqdim etadi (masalan, Nest HTTP serveriga asoslangan, mikroservis va WebSockets ilova kontekstlari). Bu utilitalar joriy execution context haqida ma'lumot beradi va shu ma'lumotlar yordamida turli controllerlar, metodlar va execution contextlar bo'ylab ishlay oladigan umumiy guards, filters va interceptors yaratish mumkin.
Ushbu bobda shunday ikki sinfni ko'rib chiqamiz: ArgumentsHost va ExecutionContext.
ArgumentsHost sinfi
ArgumentsHost sinfi handlerga uzatilayotgan argumentlarni olish uchun metodlar taqdim etadi. U argumentlarni olish uchun mos kontekstni (masalan, HTTP, RPC (mikroservis) yoki WebSockets) tanlash imkonini beradi. Freymvork ArgumentsHost instansiyasini, odatda host parametri sifatida, unga kirish kerak bo'lishi mumkin bo'lgan joylarda taqdim etadi. Masalan, exception filter dagi catch() metodi ArgumentsHost instansiyasi bilan chaqiriladi.
ArgumentsHost shunchaki handler argumentlari ustidagi abstraksiyadir. Masalan, HTTP server ilovalari uchun (@nestjs/platform-express ishlatilganda) host obyekti Expressning [request, response, next] massivini kapsullaydi; bu yerda request - so'rov obyekti, response - javob obyekti, next esa ilovaning request-response siklini boshqaradigan funksiya. Boshqa tomondan, GraphQL ilovalari uchun host obyekti [root, args, context, info] massivini o'z ichiga oladi.
Joriy ilova konteksti
Bir nechta ilova kontekstlarida ishlashi kerak bo'lgan umumiy guards, filters va interceptors ni yaratishda, metodimiz hozir qaysi ilova turida ishlayotganini aniqlash usuliga muhtojmiz. Buni ArgumentsHost ning getType() metodi orqali qilamiz:
1if (host.getType() === 'http') {
2 // do something that is only important in the context of regular HTTP requests (REST)
3} else if (host.getType() === 'rpc') {
4 // do something that is only important in the context of Microservice requests
5} else if (host.getType<GqlContextType>() === 'graphql') {
6 // do something that is only important in the context of GraphQL requests
7}GqlContextType@nestjs/graphql paketidan import qilinadi.
Ilova turi ma'lum bo'lganda, quyida ko'rsatilgandek, yanada umumiy komponentlarni yozishimiz mumkin.
Host handler argumentlari
Handlerga uzatilayotgan argumentlar massivini olish uchun bir yondashuv - host obyektining getArgs() metodidan foydalanish.
1const [req, res, next] = host.getArgs();getArgByIndex() metodi yordamida indeks bo'yicha muayyan argumentni ajratib olish mumkin:
1const request = host.getArgByIndex(0);
2const response = host.getArgByIndex(1);Bu misollarda biz request va response obyektlarini indeks bo'yicha oldik, bu odatda tavsiya etilmaydi, chunki u ilovani muayyan execution contextga bog'lab qo'yadi. Buning o'rniga, host obyektining mos ilova kontekstiga o'tuvchi utilita metodlaridan foydalanib kodingizni yanada mustahkam va qayta foydalaniladigan qilishingiz mumkin. Kontekstni almashtirish utilita metodlari quyida ko'rsatilgan.
1/**
2 * Switch context to RPC.
3 */
4switchToRpc(): RpcArgumentsHost;
5/**
6 * Switch context to HTTP.
7 */
8switchToHttp(): HttpArgumentsHost;
9/**
10 * Switch context to WebSockets.
11 */
12switchToWs(): WsArgumentsHost;Keling, oldingi misolni switchToHttp() metodi yordamida qayta yozamiz. host.switchToHttp() yordamchi chaqiruv HTTP ilova kontekstiga mos bo'lgan HttpArgumentsHost obyektini qaytaradi. HttpArgumentsHost obyektida kerakli obyektlarni ajratib olish uchun ikkita foydali metod mavjud. Bu holatda native Express tiplangan obyektlarni qaytarish uchun Express type assertionsdan ham foydalanamiz:
1const ctx = host.switchToHttp();
2const request = ctx.getRequest<Request>();
3const response = ctx.getResponse<Response>();Xuddi shunday, WsArgumentsHost va RpcArgumentsHost mikroservis va WebSockets kontekstlarida mos obyektlarni qaytaradigan metodlarga ega. Quyida WsArgumentsHost metodlari:
1export interface WsArgumentsHost {
2 /**
3 * Returns the data object.
4 */
5 getData<T>(): T;
6 /**
7 * Returns the client object.
8 */
9 getClient<T>(): T;
10}Quyida RpcArgumentsHost metodlari:
1export interface RpcArgumentsHost {
2 /**
3 * Returns the data object.
4 */
5 getData<T>(): T;
6
7 /**
8 * Returns the context object.
9 */
10 getContext<T>(): T;
11}ExecutionContext sinfi
ExecutionContext ArgumentsHost ni kengaytirib, joriy bajarilish jarayoni haqida qo'shimcha tafsilotlarni taqdim etadi. ArgumentsHost kabi, Nest ExecutionContext instansiyasini kerak bo'lishi mumkin bo'lgan joylarda taqdim etadi, masalan guard dagi canActivate() metodida va interceptor dagi intercept() metodida. U quyidagi metodlarni taqdim etadi:
1export interface ExecutionContext extends ArgumentsHost {
2 /**
3 * Returns the type of the controller class which the current handler belongs to.
4 */
5 getClass<T>(): Type<T>;
6 /**
7 * Returns a reference to the handler (method) that will be invoked next in the
8 * request pipeline.
9 */
10 getHandler(): Function;
11}getHandler() metodi tez orada chaqiriladigan handlerga havola qaytaradi. getClass() metodi esa ushbu handler tegishli bo'lgan Controller sinfining turini qaytaradi. Masalan, HTTP kontekstida agar hozir qayta ishlanayotgan so'rov CatsController dagi create() metodiga bog'langan POST so'rov bo'lsa, getHandler() create() metodiga havola, getClass() esa CatsController sinfini (instansiyani emas) qaytaradi.
1const methodKey = ctx.getHandler().name; // "create"
2const className = ctx.getClass().name; // "CatsController"Joriy sinf va handler metodiga havola olish imkoniyati katta moslashuvchanlik beradi. Eng muhimi, bu bizga guardlar yoki interceptorlar ichida Reflector#createDecorator orqali yaratilgan dekoratorlar yoki o'rnatilgan @SetMetadata() dekoratori orqali o'rnatilgan metadatalarga kirish imkonini beradi. Bu use-case'ni quyida ko'rib chiqamiz.
Reflection va metadata
Nest Reflector#createDecorator metodi orqali yaratilgan dekoratorlar va o'rnatilgan @SetMetadata() dekoratori yordamida route handlerlarga custom metadata biriktirish imkonini beradi. Bu bo'limda ikkala yondashuvni solishtiramiz va guard yoki interceptor ichidan metadatalarga qanday kirishni ko'ramiz.
Reflector#createDecorator yordamida kuchli tiplangan dekoratorlar yaratish uchun tip argumentini ko'rsatishimiz kerak. Masalan, Roles dekoratorini yaratamiz, u argument sifatida satrlar massivi oladi.
1import { Reflector } from '@nestjs/core';
2
3export const Roles = Reflector.createDecorator<string[]>();Bu yerda Roles dekoratori string[] tipidagi bitta argument qabul qiladigan funksiya.
Endi bu dekoratordan foydalanish uchun handlerni shunchaki unga annotatsiya qilamiz:
1@Post()
2@Roles(['admin'])
3async create(@Body() createCatDto: CreateCatDto) {
4 this.catsService.create(createCatDto);
5}Bu yerda Roles dekoratori metadatasini create() metodiga biriktirdik, ya'ni faqat admin roliga ega foydalanuvchilar ushbu route'ga kirishi kerakligini ko'rsatdik.
Marshrutning roli(lar)i (custom metadata) ga kirish uchun yana Reflector yordamchi sinfidan foydalanamiz. Reflector odatiy tarzda sinfga in'eksiya qilinishi mumkin:
1@Injectable()
2export class RolesGuard {
3 constructor(private reflector: Reflector) {}
4}Reflector sinfi @nestjs/core paketidan import qilinadi.
Endi handler metadatasini o'qish uchun get() metodidan foydalanamiz:
1const roles = this.reflector.get(Roles, context.getHandler());Reflector#get metodi metadatalarga oson kirish imkonini beradi: unga ikkita argument uzatamiz - dekorator havolasi va metadata olinadigan kontekst (dekorator maqsadi). Bu misolda ko'rsatilgan dekorator Roles (yuqoridagi roles.decorator.ts fayliga qayting). Kontekst context.getHandler() chaqiruviga bog'liq, bu hozir qayta ishlanayotgan route handler uchun metadatalarni ajratib olishga olib keladi. Esda tuting, getHandler() route handler funksiyasiga havola beradi.
Muqobil ravishda, controller ichidagi barcha route'lar uchun metadata qo'llash maqsadida metadata'ni controller darajasida joylashtirishimiz mumkin.
1@Roles(['admin'])
2@Controller('cats')
3export class CatsController {}Bu holatda controller metadatasini ajratib olish uchun ikkinchi argument sifatida context.getHandler() o'rniga context.getClass() uzatiladi (metadata ajratib olish uchun controller sinfini kontekst sifatida berish uchun):
1const roles = this.reflector.get(Roles, context.getClass());Metadata'ni bir nechta darajada berish imkoniyati bo'lgani uchun, sizga bir nechta kontekstlardan metadata'ni ajratib olib, birlashtirish kerak bo'lishi mumkin. Reflector sinfi bunga yordam beradigan ikkita utilita metodni taqdim etadi. Bu metodlar ham controller, ham metod metadatasini bir vaqtda ajratib olib, ularni turli usullarda birlashtiradi.
Quyidagi scenariyni ko'rib chiqing, bunda Roles metadatasi ikkala darajada ham berilgan.
1@Roles(['user'])
2@Controller('cats')
3export class CatsController {
4 @Post()
5 @Roles(['admin'])
6 async create(@Body() createCatDto: CreateCatDto) {
7 this.catsService.create(createCatDto);
8 }
9}Agar niyatingiz 'user' ni default rol sifatida berish va ayrim metodlar uchun uni selektiv tarzda override qilish bo'lsa, ehtimol getAllAndOverride() metodidan foydalanasiz.
1const roles = this.reflector.getAllAndOverride(Roles, [context.getHandler(), context.getClass()]);Ushbu kodga ega guard create() metodi kontekstida yuqoridagi metadata bilan ishlaganda, roles ['admin'] ni o'z ichiga oladi.
Har ikkala metadatani olib, ularni birlashtirish uchun (bu metod massivlar va obyektlarning ikkalasini ham birlashtiradi) getAllAndMerge() metodidan foydalaning:
1const roles = this.reflector.getAllAndMerge(Roles, [context.getHandler(), context.getClass()]);Bu holatda roles ['user', 'admin'] ni o'z ichiga oladi.
Bu ikki merge metodida ham birinchi argument sifatida metadata key, ikkinchi argument sifatida metadata target kontekstlari massivi (ya'ni getHandler() va/yoki getClass() chaqiruvlari) uzatiladi.
Past darajadagi yondashuv
Avval aytilganidek, Reflector#createDecorator o'rniga o'rnatilgan @SetMetadata() dekoratoridan ham foydalanib handlerga metadata biriktirishingiz mumkin.
1@Post()
2@SetMetadata('roles', ['admin'])
3async create(@Body() createCatDto: CreateCatDto) {
4 this.catsService.create(createCatDto);
5}@SetMetadata() dekoratori @nestjs/common paketidan import qilinadi.
Yuqoridagi konstruktsiya bilan biz roles metadatasini (roles - metadata key, ['admin'] - tegishli qiymat) create() metodiga biriktirdik. Bu ishlaydi, ammo route'larda @SetMetadata() dan bevosita foydalanish yaxshi amaliyot emas. Buning o'rniga, quyida ko'rsatilganidek, o'z dekoratorlaringizni yarating:
1import { SetMetadata } from '@nestjs/common';
2
3export const Roles = (...roles: string[]) => SetMetadata('roles', roles);Bu yondashuv ancha toza va o'qilishi oson, va qisman Reflector#createDecorator yondashuviga o'xshaydi. Farqi shundaki, @SetMetadata bilan metadata key va qiymatini ko'proq boshqarasiz, shuningdek bir nechta argument qabul qiladigan dekoratorlar yaratishingiz mumkin.
Endi bizda maxsus @Roles() dekoratori bor, uni create() metodini bezash uchun ishlatishimiz mumkin.
1@Post()
2@Roles('admin')
3async create(@Body() createCatDto: CreateCatDto) {
4 this.catsService.create(createCatDto);
5}Marshrutning roli(lar)i (custom metadata) ga kirish uchun yana Reflector yordamchi sinfidan foydalanamiz:
1@Injectable()
2export class RolesGuard {
3 constructor(private reflector: Reflector) {}
4}Reflector sinfi @nestjs/core paketidan import qilinadi.
Endi handler metadatasini o'qish uchun get() metodidan foydalanamiz.
1const roles = this.reflector.get<string[]>('roles', context.getHandler());Bu yerda dekorator havolasi o'rniga metadata key ini birinchi argument sifatida uzatamiz (bizning holatda bu 'roles'). Qolgan hamma narsa Reflector#createDecorator misolidagi kabi.