برای مثال در صورتیکه در برنامه خود از اعتبار سنجی مبتنی بر توکن (Token Base Authentication) استفاده میکنید، قبل از ارسال هر درخواست (request)، کدهایی مشابه کد زیر باید نوشته شوند:
let headers = new Headers(); let token = localStorage.getItem('token'); headers.append('Authorization', 'bearer ' + token); this.http.get('/api/controller/action', { headers: headers })
همچنین فرض کنید بعد از رسیدن جواب هر درخواست، میخواهید response code را چک کنید و خطاهای احتمالی را مدیریت کنید. مثلا درصورت دریافت کد 401، کاربر را به صفحه «ورود» و با دریافت کد 404 آنرا را به صفحه «یافت نشد» هدایت کنید و یا با دریافت کد 403 یا 500 پیغام مناسبی را نمایش دهید. بدیهی است در این صورت بعد از هر آمدن پاسخ از سمت سرور (response)، این کدها بایستی تکرار شوند.
جهت پرهیز از این کدهای تکراری، میتوان برای ماژول Http، یک interceptor واحد درنظر گرفت که تمامی کدهای تکراری را تنها یکبار داخل آن پیاده سازی کرد. مزیت این روش، مدیریت راحت کد، کاهش پیچیدگیها و همچنین حذف کدهای تکراری و یکسان سازی آنها است.
هرچند در Angular دیگر به مانند Angular 1.x مفهوم intercept بر روی Http را به صورت توکار نداریم، ولی به دلایل زیر ما نیاز به پیاده سازی interceptor برای ماژول Http را داریم:
- تنظیم هدرهای سفارشی و اصلاح آدرس، قبل از ارسال درخواست به سمت سرور
- تنظیم token در هدر درخواست، جهت اعتبار سنجی
- مدیریت سراسری خطاهای Http
- انجام هرگونه عملیات crosscutting
حالا که Angular مفهموم intercept را برای ماژول Http خود به صورت توکار درنظر نگرفته است، راهحل چیست؟ بهترین راهحل برای پیاده سازی موارد مطرح شده در بالا، ارث بری و یا گسترش (extend) مستقیم ماژول Http است:
import { Injectable } from "@angular/core"; import { ConnectionBackend, RequestOptions, Request, RequestOptionsArgs, Response, Http, Headers } from "@angular/http"; import { Observable } from "rxjs/Rx"; import 'rxjs/add/operator/catch'; import 'rxjs/add/observable/throw'; @Injectable() export class InterceptedHttp extends Http { constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) { super(backend, defaultOptions); } request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> { // اصلاح url if (typeof (url) === 'string') url = this.updateUrl((url as string)); else url.url = this.updateUrl((url as Request).url); return super.request(url, this.getRequestOptionArgs(options)).catch((err: Response) => { // Exception handling switch (err.status) { case 400: console.log('interceptor: 400'); console.log(err); break; case 404: console.log('interceptor: 404'); console.log(err); break; case 500: console.log('interceptor: 500'); console.log(err); break; case 401: console.log('interceptor: 401'); console.log(err); break; case 403: console.log('interceptor: 403'); console.log(err); break; default: console.log('interceptor: ' + err.status); console.log(err); } return Observable.throw(err); }); } private updateUrl(req: string) { return `http://localhost:61366/api/${req}` } private getRequestOptionArgs(options?: RequestOptionsArgs): RequestOptionsArgs { if (options == null) { options = new RequestOptions(); } if (options.headers == null) { options.headers = new Headers(); } // هدر درخواست تنظیم let token = localStorage.getItem('token'); options.headers.append('Authorization', 'bearer ' + token); return options; } }
نکته: به عنوان مثال، در صورتیکه قصد دارید، برای درخواستهایی از جنس get، هدر متفاوتی نسبت به دیگر درخواستها داشته باشید، آنگاه پیاده سازی عملیات اصلاح هدر در متد request جواب کار را نخواهد داد. برای حل این موضوع میتوانید به جای اصلاح header در متد request، تمامی متدهای get ،post، put ،delete ،patch ،head و options را باز نویسی کرده و در هرکدام از این متدها اینکار را انجام دهید.
حالا با تغییر قسمت providers در ماژول اصلی برنامه به شکل زیر، Angular را مجبور میکنیم بجای استفاده از ماژول Http توکار خود، از ماژول جدید InterceptedHttp استفاده کند:
//… providers: [{ provide: Http, useFactory: (backend: XHRBackend, options: RequestOptions) => { return new InterceptedHttp(backend, options); }, deps: [XHRBackend, RequestOptions], }], //…
همه چیز آماده است. اکنون کافی است ماژول Http را در سرویس یا کامپوننتهای خود تزریق کرده و درخواستهای Http را بدون هیچگونه نوشتن کد اضافی برای تنظیم هدر و غیره (با فرض اینکه تمامی آنها در متد request از ماژول http نوشته شدهاند)، به مانند قبل صادر کنید. برای نمونه کد زیر را ببینید.
import { Http, URLSearchParams } from '@angular/http'; //… constructor(private _http: Http) { } ngOnInit() { let urlSearchParams: URLSearchParams = new URLSearchParams(); urlSearchParams.append('page', page.toString()); urlSearchParams.append('count', count.toString()); let params = urlSearchParams.toString(); this._http.get(`/cars`, { params: params }) .subscribe(result => { console.log('service: Succ'); this.cars = result.json(); }, err => { console.log('service: error'); }); } //…