Рубрики
Без рубрики

Rx.js Retryingewhen Case-Case в Iframe-d угловой SPA

Узнайте о RX.js Ответить Оператор Оператор подробное использование в формате iframe-d угловой SPA.

Автор оригинала: Alexander Poshtaruk.

Реактивный

Пререквизит: Вы должны быть знакомы с NgrxStore, Angular 4, httpClient и наблюдаемыми.

Я должен признаться – мне нравятся rxjs, обе стороны: мне нравится Js (свобода как функционала, так и ООП), и мне нравится Rx, что приятно превращает мой мозг. Если вы тоже себя чувствуете, – эта статья для вас .

Однажды мне попросили сделать некоторую процедуру обновления токена истекшего токена с предварительным условиями:

  • Наш угловой 4 Спа находится в Ифраме (дальше спа).
  • Родитель Приложение (родитель) разрешено Аксессуары (Чтобы получить это нам нужны учетные данные) с временем истечения. После того, как токен истекает, мы можем использовать Освежает Чтобы сделать HTTP-запрос для обновления Accessoken (и соответственно, чтобы получить новое обновление).
  • На Родитель Событие нагрузки на странице, родитель отправляет Accessokenokake и Refreshtoken в SPA (в iframe, запомнить?) С окном. Метод PostMessage (больше здесь ).

Итак, теперь SPA может сделать HTTP-запросы к корпоративным ресурсам (это требует accesstoken в ‘ авторизация ‘ header в каждом запросе).

Так что задача:

  • Спа Делает HTTP-запросы получать данные (с A).
  • Каждый раз, когда SPA HTTP-запрос Возвращает ошибку с Статус 401 (не разрешено), мы должны продлить наш доступ в токен с обновлением. Если это какая-то другая ошибка, мы должны предоставить абонент запроса с этой ошибкой.
  • Если Освежает это не Действительно больше (мы получили ошибку по запросу HTTP Post на соответствующее URL), мы должны задать родителю (с Window.PostMessage), чтобы запросить новый Аксессуары И предоставить ему SPA (так что SPA не нуждается в кредитах, чтобы возобновить доступ к доступу).
  • После этого Спа Следует повторять начальный HTTP-запрос и предоставить данные для абонента или ошибки возврата (в том числе по таймеуту).

План решения

Вот немного кратко из потока:

Как вы знаете, угловой 4 httpClient получает метод возвращается наблюдаемым.

В простейшем случае функция HTTP-запроса без какой-либо логики будет выглядеть так:

//someComponent.ts
class SomeComponent {

constructor(private httpService: HttpService) {} 

public getImportantData() {
    let url = 'some_data_api_url';
    this.httpService.getWithRetryLogic(url)
        .subscribe(
            (data: any) => console.log('We got data! ', data),
            (err: any) => console.log

//httpService.ts
import { HttpClient } from '@angular/common/http';

…

@Injectable()
class HttpService {
    constructor(private http: HttpClient) {}
    public getWithRetryLogic(url) {
        return this.ht

Но это не то, что мы хотим, так что пусть партия начинается

Добавление обработки 401 ошибка только

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

this.http.get(url)
**    .catch((err) => {
        if (error.status === 401) {
            return Observable.throw(error);
        }
        return Observable.of(error);
    })**

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

Этот код делает дальше:

  • Если это http.get (URL) возвращает наблюдаемые с ошибкой, catch выполнит свой обратный вызов.
  • В Catch Callback мы можем проверить ошибку StatusCode. Если это не 401 – возвращает нормальный наблюдаемый (так возможные подписчики получат эти данные в Onnelext Callback, а не в OneRor, Читать дальше ).
  • В противном случае мы должны вернуть наблюдаемые. Строкать (в этом случае подписчики OneRror Callback будут выполняться, но нам нужно, чтобы для сработания оператора RetryWhen, но больше об этом немного позже).

Добавление Retrywhen, чтобы иметь возможность обрабатывать повторную логику HTTP-запроса

Таким образом, после фильтрации отклика с ошибкой 401 нам нужно реализовать логику Retry. До этого прочитайте Маленькое объяснение RetryWhen Оператор Отказ

В двух словах – это выздоровеет ошибку в наблюдаемой последовательности. Функция обратного вызова должна возвращаться повторной попытки. Каждый раз повторяемый повторный, получает это значение – наблюдаемая последовательность источника будет повторяться.

Чтобы применить его к нашей задаче, нам нужно:

  • Если ошибка 401 – RetryWhen Callback начнет процедуру обновления обновления.
  • И создаст экземпляр предмет (читать дальше здесь ). Субъект можно использовать как наблюдаемый, так и наблюдатель. Мы используем его, чтобы вызвать логику повторной попытки, испуская (любые) значения.

Давайте код это:

private retryLogicSubject: Subject < any > ;

…

public getWithRetryLogic(url) {
    this.http.get(url)
        .catch((err) => {
            if (error.status === 401) {
                return Observable.throw(error);
            }
            return Observable.of(error);
        })
**        .retryWhen((error) => {
            this.retryLogicSubject = new Subject();
            this.startRefreshTokensProcess();
            return this.retryLogicSubject.asObservable()
        })**
}

Просто FYI – RetryWhen Callback называется только один раз при первой ошибке. После этого он будет только начать источник наблюдаемого повторения на каждом этой. RretryLogicsUbject.Next ({любое: «значение»}).

Обработка ошибки без 401

Теперь обрабатываем 401 (ошибка аутентификации) ошибки, но как насчет других ошибок? Мы перемещаем все их, чтобы добиться успеха по потоку, но нам нужно его исправить, поэтому подписчики GetWithRetryLogic могут правильно обработать их (данные или ошибки).

Мы можем сделать это с обычными карта Оператор:

private retryLogicSubject: Subject < any > ;
…
public getWithRetryLogic(url) {
    this.http.get(url)
        .catch((err) => {
            if (error.status === 401) {
                return Observable.throw(error);
            }
            return Observable.of(error);
        })
        .retryWhen((error) => {
            this.retryLogicSubject = new Subject();
            this.startRefreshTokensProcess();
            return this.retryLogicSubject.asObservable()
        })
        .map(() => {
            if (data.status < 400 && data.status >= 200) { //check if not err 
                return data.json();
            }
            throw data; // back to error stream 
        })
}

Добавление тайм-аута

Зачем нам это нужно?

В случае Startrefreshtokensproces или gettokenfromparentandsavetostore Методы (см. Далее) займет некоторое время или не возвращает значения, мы должны выделять ошибку подписчикам.

Это самое простое в этой цепи – мы будем использовать Тайм-аут Оператор:

private retryLogicSubject: Subject < any > ;
…
public getWithRetryLogic(url) {
    this.http.get(url)
        .catch((err) => {
            if (error.status === 401) {
                return Observable.throw(error);
            }
            return Observable.of(error);
        })
        .retryWhen((error) => {
            this.retryLogicSubject = new Subject();
            this.startRefreshTokensProcess();
            return this.retryLogicSubject.asObservable()
        })
        .map(() => {
            if (data.status < 400 && data.status >= 200) { //check if not err 

                return data.json();
            }
            throw data; // back to error stream 
        })
       .timeout(5000)
}

Мы закончили наш метод GetWithRetryLogic. Теперь пришло время реализовать, как повторная логика будет вызвана после Это .startrefreshtokensprocess готово.

Пример StartrefreshtokensProcess Метод:

private startRefreshTokensProcess() {
    this.http.post('refreshTokenUrl', this.data) //data contains refreshToken 
        .subscribe(
            (data: Response) => this.saveTokensToStore(data.json()),
            (error) => this.getTokenFro

Что оно делает:

  • Отправить запрос со значением обновления на API.
  • Если ошибка (refreshtoken истек также, например) – затем вызовите родительское приложение, чтобы получить новые токены и вернуть их. Механизм PostMessage и PostMessage Shieser сохранит его на NgrxStore.
  • Если мы получим новые токены – просто сохраните их для хранения.

Я не буду писать SavetokenStoStore и gettokenfromparentandsavetostore Методы здесь, потому что это не в объеме статьи.

Что мы должны сделать сейчас – просто подписаться на обновления хранения. Поэтому каждый раз, когда появляются новые токены, мы должны позвонить RecryLogic (если это нужно).

Чтобы начать повторную логику, мы выделяем значение с нашим экземпляром объекта, мы вернулись с RetryWhen Callback:

constructor(private http: HttpClient, private store: Store < any > ) {}

ngOnInit() {
    this.store.map((state) => state.tokens)
        .subscribe((tokens) => {
            if (this.retryLogicSubject) {
                this.retryLogicSubject.next({})
            }
        });
}

Так что это сделано сейчас. Полный список класса HTTPService:

//httpService.tsimport { HttpClient } from' @angular / common / http';

…

@Injectable()
class HttpService {
    private retryLogicSubject: Subject < any > ;
    constructor(private http: HttpClient, private store: Store < any > ) {}
    ngOnInit() {
        this.store
            .map((state) => state.tokens)
            .subscribe((tokens) => {
                if (this.retryLogicSubject) {
                    this.retryLogicSubject.next({})
                }
            });
    }
    public getWithRetryLogic(url) {
        this.http.get(url)
            .catch((err) => {
                if (error.status === 401) {
                    return Observable.throw(error);
                }
                return Observable.of(error);
            })
            .retryWhen((error) => {
                this.retryLogicSubject = new Subject();
                this.startRefreshTokensProcess();
                return this.retryLogicSubject.asObservable()
            })
            .map(() => {
                if (data.status < 400 && data.status >= 200) { //check for errors
                    this.retryLogicSubject = undefined;
                    return data.json();
                }
                throw data; // back to error stream
            })
            .timeout(5000)
    }
    private startRefreshTokensProcess() {
        let data = { refreshToken: this.refreshToken }
        this.http
            .post('refreshTokenUrl', data)
            .subscribe(
                (data: Response) => this.saveTokensToStore(data.json()),
                (error) => this.getTokenFromParentAndSaveToStore()
            );
    }
}

Вопросы, которые я не трогал в объеме этой статьи:

  • Если сервер auth возвращает недействительные новые токены – тогда мы просто получаем ошибку по таймауте. Это может добавить несколько счетчиков поймать Оператор обратный вызов – но я не хотел загромождать статью.
  • Политика отписки – Хорошая статья об этом Отказ
  • Обратные вызовы могут быть извлечены для отдельных функций для повышения читаемости кода – я не делал этого, чтобы сохранить основную идею очевидной.
  • Нет разделения обработки повторной логики, если у нас есть несколько одновременных запросов GET. Легко реализовать с некоторым объектом CustanceManager, в котором мы можем генерировать символы в качестве клавиш для каждого GetWithretryLogic Call, которые имеют значение в качестве значения S, а затем используют его в обратном вызове .map, чтобы удалить соответствующую клавишу от Diestmanager.

Заключение

Код быстро, умирать молодым … хаха, шутит! Все эти идеи о том, как улучшить логику приветствуются! Давайте пойдем этот реактивный способ вместе.