Подтвердить что ты не робот

Обработка токенов обновления с использованием rxjs

Так как я начал с angular2, у меня были настройки моих сервисов, чтобы вернуть Observable of T. В службе у меня будет вызов map(), а компоненты, использующие эти службы, будут просто использовать subscribe(), чтобы дождаться ответ. Для этих простых сценариев мне действительно не нужно копаться в rxjs, так что все было в порядке.

Теперь я хочу добиться следующего: я использую аутентификацию Oauth2 с токенами обновления. Я хочу создать службу api, которую будут использовать все другие службы, и которая будет прозрачно обрабатывать токен обновления, когда возвращается ошибка 401. Итак, в случае с 401 я сначала извлекаю новый токен из конечной точки OAuth2, а затем повторю свой запрос с новым токеном. Ниже приведен код, который отлично работает, с promises:

request(url: string, request: RequestOptionsArgs): Promise<Response> {
    var me = this;

    request.headers = request.headers || new Headers();
    var isSecureCall: boolean =  true; //url.toLowerCase().startsWith('https://');
    if (isSecureCall === true) {
        me.authService.setAuthorizationHeader(request.headers);
    }
    request.headers.append('Content-Type', 'application/json');
    request.headers.append('Accept', 'application/json');

    return this.http.request(url, request).toPromise()
        .catch(initialError => {
            if (initialError && initialError.status === 401 && isSecureCall === true) {
                // token might be expired, try to refresh token. 
                return me.authService.refreshAuthentication().then((authenticationResult:AuthenticationResult) => {
                    if (authenticationResult.IsAuthenticated == true) {
                        // retry with new token
                        me.authService.setAuthorizationHeader(request.headers);
                        return this.http.request(url, request).toPromise();
                    }
                    return <any>Promise.reject(initialError);
                });
            }
            else {
                return <any>Promise.reject(initialError);
            }
        });
}

В приведенном выше коде authService.refreshAuthentication() выберет новый токен и сохранит его в localStorage. authService.setAuthorizationHeader установит заголовок "Авторизация" на ранее обновленный токен. Если вы посмотрите на метод catch, вы увидите, что он возвращает обещание (для токена обновления), которое в свою очередь вернет другое обещание (для фактической 2-й попытки запроса).

Я попытался сделать это, не прибегая к promises:

request(url: string, request: RequestOptionsArgs): Observable<Response> {
    var me = this;

    request.headers = request.headers || new Headers();
    var isSecureCall: boolean =  true; //url.toLowerCase().startsWith('https://');
    if (isSecureCall === true) {
        me.authService.setAuthorizationHeader(request.headers);
    }
    request.headers.append('Content-Type', 'application/json');
    request.headers.append('Accept', 'application/json');

    return this.http.request(url, request)
        .catch(initialError => {
            if (initialError && initialError.status === 401 && isSecureCall === true) {
                // token might be expired, try to refresh token
                return me.authService.refreshAuthenticationObservable().map((authenticationResult:AuthenticationResult) => {
                    if (authenticationResult.IsAuthenticated == true) {
                        // retry with new token
                        me.authService.setAuthorizationHeader(request.headers);
                        return this.http.request(url, request);
                    }
                    return Observable.throw(initialError);
                });
            }
            else {
                return Observable.throw(initialError);
            }
        });
}

Приведенный выше код не выполняет то, что я ожидаю: в случае ответа 200 он правильно возвращает ответ. Однако, если он поймает 401, он успешно извлечет новый токен, но подписка wil в конечном итоге получит наблюдаемое вместо ответа. Я предполагаю, что это неисследованный Observable, который должен повторить попытку.

Я понимаю, что перевод обетованного способа работы с библиотекой rxjs, вероятно, не самый лучший способ, но я не мог понять, что "все это поток". Я попробовал несколько других решений, в том числе flatmap, retryWhen и т.д., Но не получил далеко, поэтому некоторые помогают оценить.

4b9b3361

Ответ 1

Из быстрого обзора вашего кода я бы сказал, что ваша проблема заключается в том, что вы не сглаживаете Observable, который возвращается из службы refresh.

Оператор catch ожидает, что вы вернете Observable, который он соединит в конце неудавшегося Observable, чтобы нисходящий поток Observer не знал разницы.

В случае с не-401 вы делаете это правильно, возвращая Observable, который перезапускает начальную ошибку. Однако в обновленном случае вы возвращаете Observable, а вместо одиночных значений больше Observables.

Я бы предложил вам изменить логику обновления:

    return me.authService
             .refreshAuthenticationObservable()
             //Use flatMap instead of map
             .flatMap((authenticationResult:AuthenticationResult) => {
                   if (authenticationResult.IsAuthenticated == true) {
                     // retry with new token
                     me.authService.setAuthorizationHeader(request.headers);
                     return this.http.request(url, request);
                   }
                   return Observable.throw(initialError);
    });

flatMap преобразует промежуточный Observables в один поток.

Ответ 2

В последней версии RxJs оператор flatMap был переименован в mergeMap.