Nano Hash - криптовалюты, майнинг, программирование

Как кэшировать HTTP-запросы в асинхронном стиле в угловом HTTP-приемнике?

Я кодирую приложение angular 5. refreshAccessToken в authentication service

refreshAccessToken(): Observable<ICredentials> {
     const refreshTokenUrl = this.urlsService.getUrl(Urls.TOKEN);
     const httpParams = new HttpParams()
       .append('grant_type', 'refresh_token')
       .append('refresh_token', this.credentials.refresh_token)
       .append('client_id', Constants.CLIENT_ID)
       .toString();

     const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');

     return this.http.post(refreshTokenUrl, httpParams, { headers })
       .map((response: any) => {
         this.setCredentials(response);
         localStorage.setItem(credentialsKey, JSON.stringify(this.getCredentials()));
         return response;
  });
}

Я хочу реализовать следующий алгоритм:

  1. Любой HTTP-запрос завершился неудачно из-за несанкционированного доступа со статусом 401
  2. Попробуйте получить новый токен доступа с сервера
  3. Повторить запрос

Во время получения нового токена доступа могут быть созданы новые HTTP-запросы, в этом случае я хочу сохранить их и повторить после получения нового токена доступа. Для этого я написал перехватчик.

import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { AuthenticationService } from '@app/core/authentication/authentication.service';
import { Urls, UrlsService } from '@app/shared/urls';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class UnauthorizedRequestInterceptor implements HttpInterceptor {
  newAccessToken$: Observable<ICredentials> = null;

  constructor(
    public authService: AuthenticationService,
    private router: Router,
    private urlsService: UrlsService) {
  }

  addAuthHeader(request: HttpRequest<any>) {
    if (this.authService.getCredentials()) {
      return request.clone({
        setHeaders: {
          'Authorization': 'Bearer ' + this.authService.getCredentials().access_token
        }
      });
    }
    return request;
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    request = this.addAuthHeader(request);

    return next.handle(request).catch((error: HttpErrorResponse) => {
      let handleRequests$ = null;

      if (this.isNeedNewAccessToken(error, request)) {
        handleRequests$ = this.handleRequestWithNewAccessToken(request, next);
      }

      return handleRequests$ ||
        (this.isUnathorizedError(error)
          ? Observable.empty()
          : Observable.throw(error));
    });
  }

  logout() {
    this.authService.logout();
    this.router.navigate(['login']);
  }

  private isNeedNewAccessToken(error: HttpErrorResponse, request: HttpRequest<any>): boolean {
    return this.isUnathorizedError(error)
      && this.authService.isAuthenticated()
      && this.isSignInRequest(request);
  }

  private getNewAccessToken(): Observable<ICredentials> {
    if (!this.newAccessToken$) {
      this.newAccessToken$ = this.authService.refreshAccessToken();
    }
    return this.newAccessToken$;
  }

  private isUnathorizedError(error: HttpErrorResponse) {
    return error.status === 401;
  }

  private handleRequestWithNewAccessToken(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
    return this.getNewAccessToken()
      .mergeMap(() => {
        request = this.addAuthHeader(request);
        return next.handle(request);
      })
      .catch((err: HttpErrorResponse) => {
        if (err.error.error === 'invalid_grant') {
          this.logout();
        }
        return Observable.empty();
      });
  }

  private isNotSignInRequest(request: HttpRequest<any>): boolean {
    return request.url !== this.urlsService.getUrl(Urls.TOKEN);
  }
}

Поведение этого перехватчика действительно странное. На каждом mergeMap на handleRequestWithNewAccessToken угловом начинается новый пост httpRequest. Я ожидал, что наблюдаемое, возвращаемое из refreshAccessToken (функция из authenticationService, код вверху), будет разрешено только один раз. Я не понимаю, почему он запускается для каждой карты слияния? Я ожидал следующего:

  1. У меня есть наблюдаемый - HTTP-запрос на токен
  2. Я использую mergeMap - после завершения http-запроса будут выполнены все обратные вызовы, добавленные с помощью mergeMap.

Я думал хранить запросы, которые мне нужно обрабатывать, в глобальной переменной и вызывать их в запросе subscribe() to http, но есть проблема, что каждый запрос должен быть разрешен в начальном потоке внутри перехватчика. Я не могу сделать что-то вроде этого: .subscribe(token => this.httpClient.request(storedRequest) потому что это создаст новый запрос, поэтому все действия должны происходить внутри наблюдаемой цепочки.

Не могли бы вы помочь мне найти решение?

PS Это решение работает, но я хочу избавиться от ненужных запросов TOKEN, например. если странице нужно сделать 5 запросов и токен истек - перехватчик сделает 5 запросов на токен.


Ответы:


1

Я думаю, что ваш код хорош, и все, что вам нужно сделать, это share запросить новый токен.

refreshAccessToken(): Observable<ICredentials> {
        const refreshTokenUrl = this.urlsService.getUrl(Urls.TOKEN);
        const httpParams = new HttpParams()
            .append('grant_type', 'refresh_token')
            .append('refresh_token', this.credentials.refresh_token)
            .append('client_id', Constants.CLIENT_ID)
            .toString();

        const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');

        return this.http.post(refreshTokenUrl, httpParams, { headers })
            .map((response: any) => {
                this.setCredentials(response);
                localStorage.setItem(credentialsKey, JSON.stringify(this.getCredentials()));
                return response;
            })
            .share(); // <- HERE
    }

Обратите внимание на оператор share в конце return

РЕДАКТИРОВАТЬ:

Я также думаю, что ты никогда не откладываешь this.newAccessToken$ на null. Возможно, подумайте о добавлении набора от null к finally следующим образом:

private getNewAccessToken(): Observable<ICredentials> {
    if (!this.newAccessToken$) {
        this.newAccessToken$ = this.authService.refreshAccessToken()
            .finally(() => {
                this.newAccessToken$ = null;
            });
    }
    return this.newAccessToken$;
}
23.08.2018
  • Спасибо, share сделал свое дело, я добавил его внутрь getNewAccessToken: this.newAccessToken$ = this.authService.refreshAccessToken().share(); this.newAccessToken$.finally(() => this.newAccessToken$ = null). Также для включения finally я добавил импорт import 'rxjs/add/operator/finally'; 24.08.2018
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..