在我的Angular 2项目中,我从返回Observable的服务进行API调用。然后调用代码订阅此可观察对象。例如:
getCampaigns(): Observable<Campaign[]> {
return this.http.get('/campaigns').map(res => res.json());
}
假设服务器返回401.如何全局捕获此错误并重定向到登录页面/组件?
感谢。
这是我到目前为止所拥有的:
// boot.ts
import {Http, XHRBackend, RequestOptions} from 'angular2/http';
import {CustomHttp} from './customhttp';
bootstrap(AppComponent, [HTTP_PROVIDERS, ROUTER_PROVIDERS,
new Provider(Http, {
useFactory: (backend: XHRBackend, defaultOptions: RequestOptions) => new CustomHttp(backend, defaultOptions),
deps: [XHRBackend, RequestOptions]
})
]);
// customhttp.ts
import {Http, ConnectionBackend, Request, RequestOptions, RequestOptionsArgs, Response} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
@Injectable()
export class CustomHttp extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
super(backend, defaultOptions);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
console.log('request...');
return super.request(url, options);
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
console.log('get...');
return super.get(url, options);
}
}
我得到的错误信息是“backend.createConnection不是函数”
答案 0 :(得分:76)
我找到的最佳解决方案是覆盖XHRBackend
,以便HTTP响应状态401
和403
导致特定操作。
如果您在Angular应用程序之外处理身份验证,则可以强制刷新当前页面,以便触发外部机制。我在下面的实现中详细介绍了这个解决方案。
您还可以转发到应用程序内的组件,以便不重新加载Angular应用程序。
感谢@mrgoos,由于角度2.3.0中的错误修复(请参阅问题https://github.com/angular/angular/issues/11606)直接扩展Http
模块,因此这是角度2.3.0+的简化解决方案。
import { Injectable } from '@angular/core';
import { Request, XHRBackend, RequestOptions, Response, Http, RequestOptionsArgs, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
@Injectable()
export class AuthenticatedHttpService extends Http {
constructor(backend: XHRBackend, defaultOptions: RequestOptions) {
super(backend, defaultOptions);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return super.request(url, options).catch((error: Response) => {
if ((error.status === 401 || error.status === 403) && (window.location.href.match(/\?/g) || []).length < 2) {
console.log('The authentication session expires or the user is not authorised. Force refresh of the current page.');
window.location.href = window.location.href + '?' + new Date().getMilliseconds();
}
return Observable.throw(error);
});
}
}
模块文件现在只包含以下提供程序。
providers: [
{ provide: Http, useClass: AuthenticatedHttpService }
]
使用路由器和外部身份验证服务的另一种解决方案在@mrgoos的以下gist中有详细说明。
以下实施适用于Angular 2.2.x FINAL
和RxJS 5.0.0-beta.12
。
如果返回HTTP代码401或403,它会重定向到当前页面(加上一个参数以获取唯一的URL并避免缓存)。
import { Request, XHRBackend, BrowserXhr, ResponseOptions, XSRFStrategy, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
export class AuthenticationConnectionBackend extends XHRBackend {
constructor(_browserXhr: BrowserXhr, _baseResponseOptions: ResponseOptions, _xsrfStrategy: XSRFStrategy) {
super(_browserXhr, _baseResponseOptions, _xsrfStrategy);
}
createConnection(request: Request) {
let xhrConnection = super.createConnection(request);
xhrConnection.response = xhrConnection.response.catch((error: Response) => {
if ((error.status === 401 || error.status === 403) && (window.location.href.match(/\?/g) || []).length < 2) {
console.log('The authentication session expires or the user is not authorised. Force refresh of the current page.');
window.location.href = window.location.href + '?' + new Date().getMilliseconds();
}
return Observable.throw(error);
});
return xhrConnection;
}
}
使用以下模块文件。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpModule, XHRBackend } from '@angular/http';
import { AppComponent } from './app.component';
import { AuthenticationConnectionBackend } from './authenticated-connection.backend';
@NgModule({
bootstrap: [AppComponent],
declarations: [
AppComponent,
],
entryComponents: [AppComponent],
imports: [
BrowserModule,
CommonModule,
HttpModule,
],
providers: [
{ provide: XHRBackend, useClass: AuthenticationConnectionBackend },
],
})
export class AppModule {
}
答案 1 :(得分:73)
随着HttpClient的引入,能够轻松拦截所有请求/响应。 HttpInterceptors的一般用法很好documented,请参阅基本用法以及如何提供拦截器。下面是一个可以处理401错误的HttpInterceptor示例。
import { Observable, throwError } from 'rxjs';
import { HttpErrorResponse, HttpEvent, HttpHandler,HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { catchError } from 'rxjs/operators';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((err: HttpErrorResponse) => {
if (err.status == 401) {
// Handle 401 error
} else {
return throwError(err);
}
})
);
}
}
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http'
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).do(event => {}, err => {
if (err instanceof HttpErrorResponse && err.status == 401) {
// handle 401 errors
}
});
}
}
答案 2 :(得分:12)
您从每个请求方法获得的Observable
类型为Observable<Response>
。 Response
对象具有status
属性,该属性将保存服务器返回该代码的401
IF。因此,您可能希望在映射或转换之前检索它。
如果你想避免在每次调用时都使用这个功能,你可能需要扩展Angular 2的Http
类并注入你自己的实现,为常规{{super
调用父类Http
1}}功能,然后在返回对象之前处理401
错误。
请参阅:
https://angular.io/docs/ts/latest/api/http/index/Response-class.html
答案 3 :(得分:10)
由于前端API的过期时间比牛奶快,因此在Angular 6+和RxJS 5.5+中,您需要使用pipe
:
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { Injectable } from '@angular/core';
import { catchError } from 'rxjs/operators';
import { Router } from '@angular/router';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private router: Router) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((err: HttpErrorResponse) => {
if (err.status === 401) {
this.router.navigate(['login'], { queryParams: { returnUrl: req.url } });
}
return throwError(err);
})
);
}
}
针对Angular 7+和rxjs 6+的更新
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { Injectable } from '@angular/core';
import { catchError } from 'rxjs/internal/operators';
import { Router } from '@angular/router';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private router: Router) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request)
.pipe(
catchError((err, caught: Observable<HttpEvent<any>>) => {
if (err instanceof HttpErrorResponse && err.status == 401) {
this.router.navigate(['login'], { queryParams: { returnUrl: req.url } });
return of(err as any);
}
throw err;
})
);
}
}
答案 4 :(得分:9)
避免因使用&#34; Router&#34;等服务而导致的循环引用问题。要注入Http派生类,必须使用post-constructor Injector方法。以下代码是Http服务的工作实现,每次REST API返回时都会重定向到Login路由&#34; Token_Expired&#34;。 请注意,它可以用作常规Http的替代,因此,不需要更改应用程序中已有的组件或服务中的任何内容。
app.module.ts
providers: [
{provide: Http, useClass: ExtendedHttpService },
AuthService,
PartService,
AuthGuard
],
&#13;
延伸型http.service.ts
import { Injectable, Injector } from '@angular/core';
import { Request, XHRBackend, RequestOptions, Response, Http, RequestOptionsArgs, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Router } from '@angular/router';
import { AuthService } from './auth.service';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
@Injectable()
export class ExtendedHttpService extends Http {
private router;
private authService;
constructor( backend: XHRBackend, defaultOptions: RequestOptions, private injector: Injector) {
super(backend, defaultOptions);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
if (typeof url === 'string') {
if (!options) {
options = { headers: new Headers() };
}
this.setHeaders(options);
} else {
this.setHeaders(url);
}
console.log("url: " + JSON.stringify(url) +", Options:" + options);
return super.request(url, options).catch(this.catchErrors());
}
private catchErrors() {
return (res: Response) => {
if (this.router == null) {
this.router = this.injector.get(Router);
}
if (res.status === 401 || res.status === 403) {
//handle authorization errors
//in this example I am navigating to login.
console.log("Error_Token_Expired: redirecting to login.");
this.router.navigate(['signin']);
}
return Observable.throw(res);
};
}
private setHeaders(objectToSetHeadersTo: Request | RequestOptionsArgs) {
if (this.authService == null) {
this.authService = this.injector.get(AuthService);
}
//add whatever header that you need to every request
//in this example I could set the header token by using authService that I've created
//objectToSetHeadersTo.headers.set('token', this.authService.getToken());
}
}
&#13;
答案 5 :(得分:9)
Angular 4.3 +
完成The Gilbert Arenas Dagger回答:
如果您需要拦截任何错误,请对其进行处理并将其转发到链中(而不只是添加.do
的副作用),您可以使用HttpClient及其拦截器做某事:
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// install an error handler
return next.handle(req).catch((err: HttpErrorResponse) => {
console.log(err);
if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
console.log('An error occurred:', err.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.log(`Backend returned code ${err.status}, body was: ${err.error}`);
}
return Observable.throw(new Error('Your custom error'));
});
}
}
答案 6 :(得分:8)
从Angular&gt; = 2.3.0 ,您可以覆盖HTTP
模块并注入您的服务。
在版本2.3.0之前,由于核心错误,您无法使用注入的服务。
我创建了一个gist来展示它是如何完成的。
答案 7 :(得分:2)
Angular&gt; 4.3:基本服务的ErrorHandler
protected handleError(err: HttpErrorResponse | any) {
console.log('Error global service');
console.log(err);
let errorMessage: string = '';
if (err.hasOwnProperty('status')) { // if error has status
if (environment.httpErrors.hasOwnProperty(err.status)) {
// predefined errors
errorMessage = environment.httpErrors[err.status].msg;
} else {
errorMessage = `Error status: ${err.status}`;
if (err.hasOwnProperty('message')) {
errorMessage += err.message;
}
}
}
if (errorMessage === '') {
if (err.hasOwnProperty('error') && err.error.hasOwnProperty('message')) {
// if error has status
errorMessage = `Error: ${err.error.message}`;
}
}
// no errors, then is connection error
if (errorMessage === '') errorMessage = environment.httpErrors[0].msg;
// this.snackBar.open(errorMessage, 'Close', { duration: 5000 }});
console.error(errorMessage);
return Observable.throw(errorMessage);
}