Moya rxswift : 刷新令牌并重启请求

Posted

技术标签:

【中文标题】Moya rxswift : 刷新令牌并重启请求【英文标题】:Moya rxswift : Refresh token and restart request 【发布时间】:2018-07-27 14:27:27 【问题描述】:

我正在使用 Moya Rx swift,如果状态码是 401 或 403,我想捕捉响应,然后调用刷新令牌请求,然后再次调用/重试原始请求,为此我遵循了这个 Link 但我稍微调整一下以满足我的需要

public extension ObservableType where E == Response 

/// Tries to refresh auth token on 401 errors and retry the request.
/// If the refresh fails, the signal errors.
public func retryWithAuthIfNeeded(sessionServiceDelegate : SessionProtocol) -> Observable<E> 
    return self.retryWhen  (e: Observable<Error>) in
        return Observable
                .zip(e, Observable.range(start: 1, count: 3),resultSelector:  $1 )
                .flatMap  i in
                           return sessionServiceDelegate
                                    .getTokenObservable()?
                                    .filterSuccessfulStatusAndRedirectCodes()
                                    .mapString()
                                    .catchError 
                                        error in
                                            log.debug("ReAuth error: \(error)")
                                            if case Error.StatusCode(let response) = error 
                                                if response.statusCode == 401 || response.statusCode == 403 
                                                    // Force logout after failed attempt
                                                    sessionServiceDelegate.doLogOut()
                                                
                                            
                                            return Observable.error(error)
                                    
                                    .flatMapLatest( responseString in
                                        sessionServiceDelegate.refreshToken(responseString: responseString)
                                        return Observable.just(responseString)
                                    )
        
    

还有我的协议:

import RxSwift

public protocol SessionProtocol 
    func doLogOut()
    func refreshToken(responseString : String)
    func getTokenObservable() -> Observable<Response>? 

但它不起作用并且代码没有编译,我得到以下信息:

'Observable' 不能转换为 'Observable<_>'

我只是在谈论我与 RX-swift 的第一步,所以它可能很简单,但我不知道出了什么问题,除了我必须返回我要返回的类型之外的类型,但我不知道如何以及在哪里这样做。

非常感谢您的帮助,如果您有更好的想法来实现我想要做的事情,欢迎您提出建议。

提前感谢您的帮助。

【问题讨论】:

构建错误在哪里?您可能希望向 retryWhen 添加显式返回类型以揭示潜在问题。 感谢您的评论,我解决了,现在我想在令牌刷新请求成功后重新启动我的请求,您知道如何在不使用 sup 类 Moya 的情况下执行此操作 【参考方案1】:

您可以枚举错误并从您的 flatMap 返回 String 类型。如果请求成功,则返回字符串,否则返回可观察到的错误

public func retryWithAuthIfNeeded(sessionServiceDelegate: SessionProtocol) -> Observable<E> 
    return self.retryWhen  (error: Observable<Error>) -> Observable<String> in
        return error.enumerated().flatMap  (index, error) -> Observable<String> in
            guard let moyaError = error as? MoyaError, let response = moyaError.response, index <= 3  else 
                throw error
            
            if response.statusCode == 401 || response.statusCode == 403 
                // Force logout after failed attempt
                sessionServiceDelegate.doLogOut()
                return Observable.error(error)
             else 
                return sessionServiceDelegate
                    .getTokenObservable()!
                    .filterSuccessfulStatusAndRedirectCodes()
                    .mapString()
                    .flatMapLatest  (responseString: String) -> Observable<String> in
                        sessionServiceDelegate.refreshToken(responseString: responseString)
                        return Observable.just(responseString)
                    
            
        
    

【讨论】:

太棒了,但是有一个问题,请求无限地调用自身并且不会在任何时候停止+你知道如果刷新令牌请求如何重新启动第一个请求(即结果到达内部平面图)成功了?!再次感谢你:) 您的问题不清楚。我们有保护条件来检查索引是否 作为回应做了一些修改。您可以预先检查状态代码,然后请求令牌,否则在流中发送错误。 如果token刷新请求成功我想重启请求 我想在调用刷新令牌请求成功后重新开始原来的请求【参考方案2】:

最后我能够通过执行以下操作来解决这个问题:

首先像这样创建一个协议(这些功能是强制性的,不是可选的)。

import RxSwift
            
public protocol SessionProtocol 
    func getTokenRefreshService() -> Single<Response>
    func didFailedToRefreshToken()
    func tokenDidRefresh (response : String)

在编写网络请求的类中遵守协议SessionProtocol 非常重要,如下所示:

import RxSwift
    
class API_Connector : SessionProtocol 
        //
        private final var apiProvider : APIsProvider<APIs>!
        
        required override init() 
            super.init()
            apiProvider = APIsProvider<APIs>()
        
        // Very very important
        func getTokenRefreshService() -> Single<Response> 
             return apiProvider.rx.request(.doRefreshToken())
        
        
        // Parse and save your token locally or do any thing with the new token here
        func tokenDidRefresh(response: String) 
        
        // Log the user out or do anything related here
        public func didFailedToRefreshToken() 
        
        func getUsers (page : Int, completion: @escaping completionHandler<Page>) 
            let _ = apiProvider.rx
                .request(.getUsers(page: String(page)))
                .filterSuccessfulStatusAndRedirectCodes()
                .refreshAuthenticationTokenIfNeeded(sessionServiceDelegate: self)
                .map(Page.self)
                .subscribe  event in
                    switch event 
                    case .success(let page) :
                        completion(.success(page))
                    case .error(let error):
                        completion(.failure(error.localizedDescription))
                    
                
        
        

然后,我创建了一个返回 Single&lt;Response&gt; 的函数。

import RxSwift
    
extension PrimitiveSequence where TraitType == SingleTrait, ElementType == Response 
        
        // Tries to refresh auth token on 401 error and retry the request.
        // If the refresh fails it returns an error .
        public func refreshAuthenticationTokenIfNeeded(sessionServiceDelegate : SessionProtocol) -> Single<Response> 
            return
                // Retry and process the request if any error occurred
                self.retryWhen  responseFromFirstRequest in
                    responseFromFirstRequest.flatMap  originalRequestResponseError -> PrimitiveSequence<SingleTrait, ElementType> in
                            if let lucidErrorOfOriginalRequest : LucidMoyaError = originalRequestResponseError as? LucidMoyaError 
                            let statusCode = lucidErrorOfOriginalRequest.statusCode!
                            if statusCode == 401 
                                // Token expired >> Call refresh token request
                                return sessionServiceDelegate
                                    .getTokenRefreshService()
                                    .filterSuccessfulStatusCodesAndProcessErrors()
                                    .catchError  tokeRefreshRequestError -> Single<Response> in
                                        // Failed to refresh token
                                        if let lucidErrorOfTokenRefreshRequest : LucidMoyaError = tokeRefreshRequestError as? LucidMoyaError 
                                            //
                                            // Logout or do any thing related
                                            sessionServiceDelegate.didFailedToRefreshToken()
                                            //
                                            return Single.error(lucidErrorOfTokenRefreshRequest)
                                        
                                        return Single.error(tokeRefreshRequestError)
                                    
                                    .flatMap  tokenRefreshResponseString -> Single<Response> in
                                        // Refresh token response string
                                        // Save new token locally to use with any request from now on
                                        sessionServiceDelegate.tokenDidRefresh(response: try! tokenRefreshResponseString.mapString())
                                        // Retry the original request one more time
                                        return self.retry(1)
                                
                            
                            else 
                                // Retuen errors other than 401 & 403 of the original request
                                return Single.error(lucidErrorOfOriginalRequest)
                            
                        
                        // Return any other error
                        return Single.error(originalRequestResponseError)
                    
            
        

这个函数的作用是它从响应中捕获错误然后检查状态码,如果它不是401,那么它会将该错误返回到原始请求的onError块,但如果它是401(您可以更改它以满足您的需求,但这是标准)然后它将执行刷新令牌请求。

完成刷新令牌请求后,它会检查响应。

=> 如果状态码大于或等于400,则这意味着刷新令牌请求也失败,因此将该请求的结果返回到原始请求OnError 块。 => 如果状态码在200..300 范围内,则表示刷新令牌请求成功,因此它将重试原始请求一次,如果原始请求再次失败,则失败将正常转到OnError 块。

注意事项:

=> 在刷新令牌请求成功并返回新令牌后解析并保存新令牌非常重要,因此在重复原始请求时它将使用新令牌而不是旧令牌.

在重复原始请求之前,此回调将返回令牌响应。 func tokenDidRefresh (response : String)

=> 如果刷新令牌请求失败,则令牌可能已过期,因此除了将失败重定向到原始请求的onError,您还会收到此失败回调 func didFailedToRefreshToken(),您可以使用它来通知用户他的会话丢失或将他注销或其他任何事情。

=> 返回执行令牌请求的函数非常重要,因为这是refreshAuthenticationTokenIfNeeded 函数知道调用哪个请求以执行刷新令牌的唯一方法。

func getTokenRefreshService() -> Single<Response> 
    return apiProvider.rx.request(.doRefreshToken())

【讨论】:

【参考方案3】:

除了在 Observable 上编写扩展之外,还有另一种解决方案。 它是在纯 RxSwift 上编写的,并且在失败的情况下返回一个经典错误。

The easy way to refresh session token of Auth0 with RxSwift and Moya

该解决方案的主要优点是它可以很容易地适用于类似于 Auth0 的不同服务,允许在移动应用中对用户进行身份验证。

【讨论】:

你应该回答被问到的问题,不推荐其他处理问题的方法, 喜欢它,非常感谢你,但原始请求不会在令牌刷新后自行重启,而且我必须写 CustomMoyaProvider 但使用我的代码,它可以与任何 Moya 提供商一起使用,还是我错过了什么?

以上是关于Moya rxswift : 刷新令牌并重启请求的主要内容,如果未能解决你的问题,请参考以下文章

RxSwift 更新身份验证令牌

Alamofire/RxSwift 如何在状态码 401 上自动刷新令牌和重试请求

RxSwift 和 Moya 同步请求

使用 Moya 刷新身份验证令牌

在使用 RxSwift 和 mvvm 发出 moya 请求时添加微调器,并在用户收到响应时将其关闭

RxSwift/Moya - 如果通过函数返回序列将不会启动