Moya rxswift :刷新令牌并重新启动请求
Moya rxswift : Refresh token and restart request
我正在使用 Moya Rx swift,如果状态代码是 401 或 403,我想捕获响应然后调用刷新令牌请求然后再次 recall/retry 原始请求并这样做我遵循了这个 但我对其进行了一些调整以满足我的需要
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: { })
.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' is not convertible to 'Observable<_>'
我只是在谈论我对 RX-swift 的第一步,所以它可能很简单,但我无法弄清楚哪里出了问题,除了我必须 return一个我正在 returning 但我不知道如何以及在哪里这样做。
非常感谢您的帮助,如果您有更好的想法来实现我正在尝试做的事情,欢迎您提出建议。
在此先感谢您的帮助。
您可以从 flatMap 中枚举错误和 return String 类型。如果请求成功,那么它将 return string 否则将 return error observable
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)
}
}
}
}
最后我通过执行以下操作解决了这个问题:
首先像这样创建一个协议(这些功能是强制性的,而不是可选的)。
import RxSwift
public protocol SessionProtocol {
func getTokenRefreshService() -> Single<Response>
func didFailedToRefreshToken()
func tokenDidRefresh (response : String)
}
遵守 class 中的协议 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))
}
}
}
}
然后,我创建了一个 return 是 Single<Response>
的函数。
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
以外的任何东西那么它将 return 那个错误到原来的请求的 onError
块,但如果它是 401
(您可以更改它以满足您的需要,但这是标准)那么它将执行刷新令牌请求。
完成刷新令牌请求后,它会检查响应。
=> 如果状态代码大于或等于 400
那么这意味着刷新令牌请求也失败了所以 return 该请求的结果与原始请求 OnError
块。
=> 如果状态代码在 200..300
范围内,那么这意味着刷新令牌请求成功,因此它将再次重试原始请求,如果原始请求再次失败,则失败将转到 OnError
正常阻止。
备注:
=> 在刷新令牌请求成功并且新令牌被 returned 后解析并保存新令牌非常重要,因此在重复原始请求时它将使用新的令牌而不是旧的。
令牌响应是在重复原始请求之前在此回调中 return 编辑的。
func tokenDidRefresh (response : String)
=> 如果刷新令牌请求失败,则令牌可能已过期,因此除了将失败重定向到原始请求的 onError
,您还会收到此失败回调
func didFailedToRefreshToken()
,你可以用它来通知用户他的会话丢失或者注销他等等。
=> 执行令牌请求的 return 函数非常重要,因为这是 refreshAuthenticationTokenIfNeeded
函数知道要调用哪个请求以执行刷新令牌的唯一方法。
func getTokenRefreshService() -> Single<Response> {
return apiProvider.rx.request(.doRefreshToken())
}
除了在 Observable 上编写扩展之外,还有另一种解决方案。 它是在纯 RxSwift 上编写的,returns 失败时的经典错误。
The easy way to refresh session token of Auth0 with RxSwift and Moya
该解决方案的主要优点是它可以很容易地应用于类似于 Auth0 的不同服务,允许在移动应用程序中对用户进行身份验证。
我正在使用 Moya Rx swift,如果状态代码是 401 或 403,我想捕获响应然后调用刷新令牌请求然后再次 recall/retry 原始请求并这样做我遵循了这个
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: { })
.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' is not convertible to 'Observable<_>'
我只是在谈论我对 RX-swift 的第一步,所以它可能很简单,但我无法弄清楚哪里出了问题,除了我必须 return一个我正在 returning 但我不知道如何以及在哪里这样做。
非常感谢您的帮助,如果您有更好的想法来实现我正在尝试做的事情,欢迎您提出建议。
在此先感谢您的帮助。
您可以从 flatMap 中枚举错误和 return String 类型。如果请求成功,那么它将 return string 否则将 return error observable
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)
}
}
}
}
最后我通过执行以下操作解决了这个问题:
首先像这样创建一个协议(这些功能是强制性的,而不是可选的)。
import RxSwift
public protocol SessionProtocol {
func getTokenRefreshService() -> Single<Response>
func didFailedToRefreshToken()
func tokenDidRefresh (response : String)
}
遵守 class 中的协议 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))
}
}
}
}
然后,我创建了一个 return 是 Single<Response>
的函数。
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
以外的任何东西那么它将 return 那个错误到原来的请求的 onError
块,但如果它是 401
(您可以更改它以满足您的需要,但这是标准)那么它将执行刷新令牌请求。
完成刷新令牌请求后,它会检查响应。
=> 如果状态代码大于或等于 400
那么这意味着刷新令牌请求也失败了所以 return 该请求的结果与原始请求 OnError
块。
=> 如果状态代码在 200..300
范围内,那么这意味着刷新令牌请求成功,因此它将再次重试原始请求,如果原始请求再次失败,则失败将转到 OnError
正常阻止。
备注:
=> 在刷新令牌请求成功并且新令牌被 returned 后解析并保存新令牌非常重要,因此在重复原始请求时它将使用新的令牌而不是旧的。
令牌响应是在重复原始请求之前在此回调中 return 编辑的。
func tokenDidRefresh (response : String)
=> 如果刷新令牌请求失败,则令牌可能已过期,因此除了将失败重定向到原始请求的 onError
,您还会收到此失败回调
func didFailedToRefreshToken()
,你可以用它来通知用户他的会话丢失或者注销他等等。
=> 执行令牌请求的 return 函数非常重要,因为这是 refreshAuthenticationTokenIfNeeded
函数知道要调用哪个请求以执行刷新令牌的唯一方法。
func getTokenRefreshService() -> Single<Response> {
return apiProvider.rx.request(.doRefreshToken())
}
除了在 Observable 上编写扩展之外,还有另一种解决方案。 它是在纯 RxSwift 上编写的,returns 失败时的经典错误。
The easy way to refresh session token of Auth0 with RxSwift and Moya
该解决方案的主要优点是它可以很容易地应用于类似于 Auth0 的不同服务,允许在移动应用程序中对用户进行身份验证。