401'leri Angular ile küresel olarak işleme


92

Angular 2 projemde bir Observable döndüren servislerden API çağrıları yapıyorum. Çağıran kod daha sonra bu gözlenebilir olana abone olur. Örneğin:

getCampaigns(): Observable<Campaign[]> {
    return this.http.get('/campaigns').map(res => res.json());
}

Sunucunun bir 401 döndürdüğünü varsayalım. Bu hatayı genel olarak nasıl yakalayabilirim ve bir oturum açma sayfasına / bileşenine yeniden yönlendirebilirim?

Teşekkürler.


Şimdiye kadar sahip olduğum şeyler:

// 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]
    })
]);

// özelhttp.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);
    }
}

Aldığım hata mesajı "backend.createConnection bir işlev değil"


1
Sanırım bu size küçük bir işaret
Pankaj Parkar

Yanıtlar:


79

Açıklama

Bulduğum en iyi çözüm XHRBackend, HTTP yanıt durumu 401ve 403belirli bir eyleme yol açacak şekilde geçersiz kılmaktır.

Kimlik doğrulamanızı Angular uygulamanızın dışında gerçekleştirirseniz, harici mekanizmanız tetiklenecek şekilde mevcut sayfayı yenilemeye zorlayabilirsiniz. Bu çözümü aşağıdaki uygulamada detaylandırıyorum.

Angular uygulamanız yeniden yüklenmeyecek şekilde uygulamanızın içindeki bir bileşeni de iletebilirsiniz.

Uygulama

Açısal> 2.3.0

@Mrgoos sayesinde, doğrudan modülü genişleten açısal 2.3.0'daki bir hata düzeltmesi ( https://github.com/angular/angular/issues/11606 konusuna bakın ) nedeniyle açısal 2.3.0+ için basitleştirilmiş bir çözüm burada Http.

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);
        });
  }
}

Modül dosyası artık yalnızca aşağıdaki sağlayıcıyı içerir.

providers: [
    { provide: Http, useClass: AuthenticatedHttpService }
]

Yönlendirici ve harici bir kimlik doğrulama servisi kullanılarak Başka bir çözüm, aşağıdaki ayrıntılı olarak ana fikri @mrgoos ile.

Açısal ön-2.3.0

Aşağıdaki uygulama Angular 2.2.x FINALve için çalışır RxJS 5.0.0-beta.12.

401 veya 403 HTTP kodu döndürülürse, geçerli sayfaya (ayrıca benzersiz bir URL almak ve önbelleğe almayı önlemek için bir parametre) yönlendirir.

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;
    }

}

aşağıdaki modül dosyası ile.

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 {
}

2
Teşekkürler! Sorunumu anladım ... Bu satırı kaçırıyordum, bu yüzden catch()bulunamadı. (smh) import "rxjs/add/operator/catch";
hartpdx

1
Navigasyon yapmak için Yönlendirici modülünü kullanmak mümkün mü?
Yuanfei Zhu

1
Auth Guard ile paketlemek için harika bir çözüm! 1. Auth Guard yetkili kullanıcıyı kontrol eder (örn. LocalStorage'a bakarak). 2. 401/403 yanıtında, Guard için yetkili kullanıcıyı temizlersiniz (örneğin, LocalStorage'daki ilgili parametreleri kaldırarak). 3. Bu erken aşamada, oturum açma sayfasına yönlendirmek için Yönlendiriciye erişemeyeceğinizden, aynı sayfayı yenilemek, sizi oturum açma ekranına yönlendirecek (ve isteğe bağlı olarak başlangıç ​​URL'nizi koruyacak şekilde) Koruma kontrollerini tetikleyecektir. başarılı kimlik doğrulamasından sonra istenen sayfaya yönlendirilecektir).
Alex Klaus

1
Hey @NicolasHenneaux - neden geçersiz kılmaktan daha iyi olduğunu düşünüyorsun http? Gördüğüm tek fayda, onu basitçe bir sağlayıcı olarak koyabilmenizdir: { provide: XHRBackend, useClass: AuthenticationConnectionBackend }Http'yi geçersiz kılarken, useFactory'yeni' çağırarak ve belirli argümanlar göndererek kendinizi sınırlandırmak gibi daha garip kodlar yazmanız gerekir . WDYT? 2. yönteme bir referans: adonespitogo.com/articles/angular-2-extending-http-provider
mrgoos

3
@Brett - Bunun için size yardımcı olacak bir özet
oluşturdum

84

Açısal 4.3+

HttpClient'in tanıtılmasıyla, tüm istekleri / yanıtları kolayca engelleme yeteneği geldi. HttpInterceptors'ın genel kullanımı iyi belgelenmiştir , temel kullanımı ve durdurucunun nasıl sağlanacağını görün. Aşağıda, 401 hatalarını işleyebilen bir HttpInterceptor örneği verilmiştir.

RxJS 6+ için güncellendi

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);
        }
      })
    );
  }

}

RxJS <6

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
            }
        });
    }
}

1
Bu hala sizin için çalışıyor mu? Dün benim için çalışıyordu ancak diğer modülleri yükledikten sonra şu hatayı alıyorum: next.handle (…) .do bir işlev değil
Multitut

Bence bu, http gibi sınıfların uzantısı olarak kullanılmalı, neredeyse her zaman bir koku
kboom

1
HTTP_INTERCEPTORS ile sağlayıcı listenize eklemeyi unutmayın. Dokümanlarda
Bruno Peres

2
Harika ama Routerburada kullanmak işe yaramıyor. Örneğin, 401-403 aldıklarında kullanıcılarımı oturum açma sayfasına yönlendirmek istiyorum, ancak this.router.navigate(['/login']benim için çalışmıyor. Hiçbir şey yapmaz
CodyBugstein

".Do bir işlev değil" alıyorsanız, import 'rxjs/add/operator/do';rxj'leri içe aktardıktan sonra ekleyin .
amoss

20

Ön uç API'leri, Angular 6+ ve RxJS 5.5+ ile sütten daha hızlı sona erdiğinden, şunları kullanmanız gerekir 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+ ve rxjs 6+ için güncelleme

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: request.url } });
            return of(err as any);
          }
          throw err;
        })
      );
  }
}


Elde error TS2322: Type 'Observable<{}>' is not assignable to type 'Observable<HttpEvent<any>>'.zaman .pipeiçeride, hiç hata ne zaman kaldırmak.pipe
Blackıce

2
@BlackICE Sanırım cevabımdaki ilk cümleyi tekrar doğruluyor. En yeni sürüm için bir cevapla güncelledim.
Saeb Amini

1
Sizin ng7 + örneğinizde reqaslında request- düzenleme benim için çok küçük
sordu_io

neden interceprot kullanıyoruz ve neden post yöntemiyle oturum açma
api'sini çağırarak bunu işleyemiyoruz

12

ObservableHer istek yönteminden elde tiptedir Observable<Response>. ResponseNesne, bir sahiptir statusyapacak özelliğini 401sunucu bu kodu döndürdü IF. Dolayısıyla, onu haritalamadan veya dönüştürmeden önce geri almak isteyebilirsiniz.

Her çağrıda bu işlevi yapmaktan kaçınmak istiyorsanız, Angular 2'nin Httpsınıfını genişletmeniz superve normal Httpişlevsellik için parent ( ) öğesini çağıran kendi uygulamanızı enjekte etmeniz ve ardından 401nesneyi döndürmeden önce hatayı işlemeniz gerekebilir .

Görmek:

https://angular.io/docs/ts/latest/api/http/index/Response-class.html


Öyleyse, Http'yi genişletirsem, o zaman Http'nin içinden bir "oturum açma" yoluna yeniden yönlendirebilmeliyim?
pbz

Teori bu. Bunu yapmak için yönlendiriciyi Http uygulamanıza enjekte etmeniz gerekecek.
Langley

Yardımınız için teşekkürler. Soruyu örnek bir kodla güncelledim. Muhtemelen yanlış bir şey yapıyorum (Angular'da yeniyim). Ne olabileceği hakkında fikri olan? Teşekkürler.
pbz

Varsayılan Http sağlayıcılarını kullanıyorsunuz, varsayılan yerine sınıfınızın bir örneğine çözümlenen kendi sağlayıcınızı oluşturmanız gerekir. Görmek: angular.io/docs/ts/latest/api/core/Provider-class.html
Langley

1
@ Langley, teşekkürler. Haklısınız: subscribe ((result) => {}, (error) => {console.log (error.status);}. Hata parametresi hala Response türünde.
abedurftig

9

Açısal 4.3+

Gilbert Arenas Dagger cevabını tamamlamak için :

İhtiyacınız olan şey herhangi bir hatayı yakalamaksa, ona bir tedavi uygulamak ve onu zincire iletmekse (ve sadece bir yan etki eklemekle kalmayıp .do), HttpClient ve onun önleyicilerini şu türden bir şey yapmak için kullanabilirsiniz :

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'));
        });
    }
}

9

"Yönlendirici" gibi hizmetlerin bir Http türetilmiş sınıfa enjekte edilmesinin neden olduğu döngüsel başvuru sorununu önlemek için, post-constructor Injector yöntemi kullanılmalıdır. Aşağıdaki kod, bir REST API her "Token_Expired" döndürdüğünde Login rotasına yeniden yönlendiren bir Http hizmetinin çalışan bir uygulamasıdır. Normal Http'nin bir ikamesi olarak kullanılabileceğini ve bu nedenle uygulamanızın halihazırda var olan bileşenlerinde veya hizmetlerinde herhangi bir değişiklik gerektirmediğini unutmayın.

app.module.ts

  providers: [  
    {provide: Http, useClass: ExtendedHttpService },
    AuthService,
    PartService,
    AuthGuard
  ],

extended-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());
  }
}


8

Angular> = 2.3.0 seçeneğinden,HTTP modülü ve hizmetlerinizi enjekte edebilirsiniz. 2.3.0 sürümünden önce, temel bir hata nedeniyle enjekte edilen hizmetlerinizi kullanamıyordunuz.

Nasıl yapıldığını göstermek için bir öz oluşturdum .


Bunu bir araya getirdiğin için teşekkürler. App.module.ts'de "'Http' adı bulunamıyor" şeklinde bir yapı hatası alıyordum, bu yüzden içe aktardım ve şu hatayı alıyorum: "Döngüsel bağımlılık başlatılamıyor! Http: NgModule AppModule'da"
Bryan

Hey @ Brett - app.modulekodunuzu paylaşır mısınız? Teşekkürler.
mrgoos

Tamam görünüyor. Özüne genişletilmiş HTTP'yi ekleyebilir misiniz? Ek olarak, HTTPbaşka bir yere ithal ediyor musunuz?
mrgoos

Gecikme için üzgünüm. Şimdi Angular 2.4'teyim ve aynı hatayı alıyorum. Http'yi birkaç dosyada içe aktarıyorum. İşte güncellenmiş ana fikrim
Bryan

Burada da aynı sorun ... Görünüşe göre bu öz işe yaramıyor, bu yüzden belki de bu şekilde işaretlemeliyiz?
Tuthmosis

2

Angular> 4.3: Temel hizmet için 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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.