【问题标题】:Angular 6: Calling service observer.next from Http Interceptor causes infinite request loopAngular 6:从 Http Interceptor 调用 service observer.next 会导致无限请求循环
【发布时间】:2019-04-25 14:36:21
【问题描述】:

我正在开发一个使用 JWT 进行身份验证的网站。我创建了一个 HTTP 拦截器类,它将令牌添加到所有请求标头并用于捕获 401 错误。

import {Injectable} from '@angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Observable, of} from 'rxjs';
import {JwtService} from '../service/jwt.service';
import {catchError} from 'rxjs/operators';
import {AlertService} from '../../shared/service/alert.service';
import {Router} from '@angular/router';
import {AlertType} from '../../shared/model/alert.model';

@Injectable()
export class HttpTokenInterceptor implements HttpInterceptor {

  constructor(private jwtService: JwtService, private alertService: AlertService, private router: Router) {
  }

  /**
   * Intercept HTTP requests and return a cloned version with added headers
   *
   * @param req incoming request
   * @param next observable next request
   */
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    // Add headers to all requests
    const headersConfig = {
      'Accept': 'application/json'
    };

    // Add token bearer to header when it's available
    const token = this.jwtService.getToken();
    if (token) {
      headersConfig['Authorization'] = `Bearer ${token}`;
      headersConfig['Content-Type'] = 'application/json';
    }

    const request = req.clone({setHeaders: headersConfig});

    // Return adjusted http request with added headers
    return next.handle(request).pipe(
      catchError((error: any) => {

        // Unauthorized response
        if (error.status === 401) {

          this.handleError();
          return of(error);
        }
        throw error;
      })
    );
  }

  /**
   * Handle http errors
   */
  private handleError() {

    // Destroy the token
    this.jwtService.destroyToken();

    // Redirect to login page
    this.router.navigate(['/login']);

    // This is causing infinite loops in HTTP requests
    this.alertService.showAlert({
      message: 'Your token is invalid, please login again.',
      type: AlertType.Warning
    });

  }

}

该类使用我的 JwtToken 类从本地存储中删除令牌,并使用 Angular 路由器将用户重定向到登录页面。 alertService 中的 showAlert 方法导致 http 请求无限重复。

我认为这是由警报服务中的 Observer 实现引起的。但是我尝试了很多不同的实现,我真的不知道出了什么问题。

import {Injectable} from '@angular/core';
import {Alert} from '../model/alert.model';
import {Subject} from 'rxjs';

/**
 * Alert Service: Used for showing alerts all over the website
 * Callable from all components
 */
@Injectable()
export class AlertService {

  public alertEvent: Subject<Alert>;

  /**
   * AlertService constructor
   */
  constructor() {
    this.alertEvent = new Subject<Alert>();
  }

  /**
   * Emit event containing an Alert object
   *
   * @param alert
   */
  public showAlert(alert: Alert) {
    this.alertEvent.next(alert);
  }
}

alertService 类正被一个显示所有警报消息的警报组件使用。该组件用于两个主要组件:仪表板和登录。

import {Component} from '@angular/core';
import {AlertService} from '../../shared/service/alert.service';
import {Alert} from '../../shared/model/alert.model';

@Component({
  selector: '*brand*-alerts',
  templateUrl: './alerts.component.html',
})
export class AlertsComponent {

  // Keep list in global component
  public alerts: Array<Alert> = [];

  constructor(private alertService: AlertService) {

    // Hook to alertEvents and add to class list
    alertService.alertEvent.asObservable().subscribe(alerts => {
      // console.log(alerts);
      this.alerts.push(alerts);
    });
  }

}

在下图中,问题清晰可见:

video of loop

亲切的问候。

编辑:已解决

在发出请求的页面中,警报服务上已初始化订阅,这导致 http 请求再次触发。我现在只是让警报组件成为 alertService 的唯一订阅者,并为刷新创建了一个新服务。 @incNick 的答案确实是一个正确的实现。谢谢!

【问题讨论】:

    标签: angular jwt angular6 angular-httpclient angular-http-interceptors


    【解决方案1】:

    很抱歉我的工作很忙,但希望我的消息来源能有所帮助。

    import { Observable, throwError } from 'rxjs';
    import { tap, catchError } from 'rxjs/operators';
    ...
    return httpHandler.handle(request).pipe(
              tap((event: HttpEvent<any>) => {
                  if (event instanceof HttpResponse) { 
                      //this.loadingService.endLoading();
                  }
              },
              (err: any) => {
                  //this.loadingService.endLoading();
              }),
              catchError((err: any) => {
                  if (err.status === 401) {
                      /*
                      this.modalController.create({
                          component: LoginComponent,
                          componentProps: {returnUrl: this.router.url},
                          showBackdrop: true
                      }).then(modal => modal.present());
                      */
                  } else {
                      //this.messageService.showToast(`Some error happen, please try again. (Error-${err.status})`, 'error');
                  }
                  return throwError(err);
              })
          );
    

    最后我会返回 throwError(err)。

    【讨论】:

    • 这似乎不是问题。无论如何,我确实删除了该参数,因为它没有在方法本身中使用。我已经编辑了我的 OP
    • 希望没事:(
    • 不幸的是,这并没有什么不同。当请求失败并返回 401 时,用户仍会被路由到登录页面。但是使用 alertService 添加警报仍然会导致请求重试并发送另一个警报。
    • 我已经追踪到服务类。每当调用 this.alertEvent.next(alert); 操作时,请求就会再次被调用,失败并再次发送警报。
    • 别担心,只有在你有时间的时候才能回答。我会调查一下并回复你!
    猜你喜欢
    • 1970-01-01
    • 2018-05-06
    • 2011-07-31
    • 2011-08-23
    • 2019-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多