使用rxjs处理刷新令牌
因为我已经开始使用angular2了,我已经设置了我的服务来返回T的Observable。在服务中,我将调用map(),使用这些服务的组件只使用subscribe()等待响应。 对于这些简单的情况,我真的不需要深入到rxjs,所以一切都好。
我现在想要实现以下内容:我正在使用刷新令牌的Oauth2身份validation。 我想构build一个所有其他服务将使用的api服务,并且在返回401错误时将透明地处理刷新令牌。 因此,在401的情况下,我首先从OAuth2端点获取新令牌,然后使用新令牌重试我的请求。 下面是可以正常工作的代码,并承诺:
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方法,你会看到它返回一个promise(对于refresh标记),轮到它将最终返回另一个promise(对于请求的第二次尝试)。
我试图做到这一点,而不是诉诸承诺:
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,它将成功检索新的令牌,但订阅将最终检索observable而不是响应。 我猜这是未执行的Observable应该做的重试。
我意识到将工作的承诺方式转化到rxjs库可能不是最好的方法,但我还没有能够把握“一切都是stream”的东西。 我已经尝试了一些其他的解决scheme,包括flatmap,retryWhen等…但没有得到太多,所以一些帮助表示赞赏。
从快速浏览你的代码,我会说你的问题似乎是你没有扁平从refresh
服务返回的Observable
。
catch
操作符期望您将返回一个Observable
,它将连接到失败的Observable的末尾,以便下游的Observer
不知道区别。
在非401情况下,您通过返回抛出初始错误的Observable来正确执行此操作。 然而,在刷新的情况下,你正在返回一个Observable
产生更多的 Observables
而不是单个值。
我build议你改变刷新逻辑是:
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
转换成一个单一的stream。
在RxJs的最新版本中, flatMap
运算符已被重命名为mergeMap
。