我正在实现一种使用带有 AlamoFire5 的 OAuth2 刷新 session token 的方法,并且我正在尝试找出如何解决这种情况:
1 - When some request fail a refreshToken request must start, that must be the only one refreshToken request running at a time. i.e. the other requests that failed should not be retried until that request finishes.
2 - If the refreshToken finishes with an error the app must restarts and all the other request that were waiting must be cancelled.
3 - If the the refreshToken request succeeds the token must be updated and all the other requests waiting must now continue.
我正在使用 AlamoFire 的 RequestInterceptor 类来尝试解决这个问题,到目前为止我的实现是这样的:
final class RequestInterceptor: Alamofire.RequestInterceptor {
private let disposeBag = DisposeBag()
private let lock = NSRecursiveLock()
private var refreshTokenParameters: TokenParameters {
TokenParameters(clientId: "pdappclient",
grantType: "refresh_token",
refreshToken: KeychainManager.shared.refreshToken)
}
private let storage: AccessTokenStorage
init(storage: AccessTokenStorage) {
self.storage = storage
}
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var urlRequest = urlRequest
urlRequest.setValue("Bearer " + storage.accessToken, forHTTPHeaderField: "Authorization")
completion(.success(urlRequest))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
lock.lock()
defer { lock.unlock() }
guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else {
return completion(.doNotRetryWithError(error))
}
let refreshTokenRequest: Single<TokenResponse> = NetworkManager.shared
.fetchData(fromApi: IdentityServerAPI.token(parameters: self.refreshTokenParameters))
refreshTokenRequest.subscribe(onSuccess: { token in
self.lock.unlock()
self.storage.accessToken = token.accessToken ?? ""
completion(.retry)
}, onError: { error in
self.lock.unlock()
completion(.doNotRetryWithError(error))
}).disposed(by: disposeBag)
}
}
如何使用 RequestInterceptor 解决这种情况?
最佳答案
您可以使用数组来存储重试闭包,用于在 token 刷新完成之前可能发生的请求,并使用 bool 值来了解正在进行的刷新操作。
你最终会得到这样的结果:
final class RequestInterceptor: Alamofire.RequestInterceptor {
private let disposeBag = DisposeBag()
private let lock = NSRecursiveLock()
private var refreshTokenParameters: TokenParameters {
TokenParameters(
clientId: "pdappclient",
grantType: "refresh_token",
refreshToken: KeychainManager.shared.refreshToken
)
}
private let storage: AccessTokenStorage
private var retryQueue = [(RetryResult) -> Void]()
private var isTokenRefreshing = false
init(storage: AccessTokenStorage) {
self.storage = storage
}
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var urlRequest = urlRequest
urlRequest.setValue("Bearer " + storage.accessToken, forHTTPHeaderField: "Authorization")
completion(.success(urlRequest))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
lock.lock()
defer { lock.unlock() }
guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else {
completion(.doNotRetryWithError(error))
return
}
retryQueue.append(completion)
if !isTokenRefreshing {
isTokenRefreshing = true
let refreshTokenRequest: Single<TokenResponse> = NetworkManager.shared
.fetchData(fromApi: IdentityServerAPI.token(parameters: self.refreshTokenParameters))
refreshTokenRequest.subscribe(onSuccess: { token in
self.lock.lock()
defer { self.lock.unlock() }
self.storage.accessToken = token.accessToken ?? ""
self.retryQueue.forEach { $0(.retry) }
self.retryQueue.removeAll()
self.isTokenRefreshing = false
}, onError: { error in
self.lock.lock()
defer { self.lock.unlock() }
self.retryQueue.forEach { $0(.doNotRetryWithError(error)) }
self.retryQueue.removeAll()
self.isTokenRefreshing = false
}).disposed(by: disposeBag)
}
}
}
请注意,如 defer文件指出:A
defer
statement is used for executing code just before transferring program control outside of the scope that the defer statement appears in.
所以,先封
defer
语句将在 onSuccess
之前执行或 onError
关闭。这就是为什么我们需要再次锁定
onSuccess
内的源和 onError
关闭。
关于swift - 如何保证在使用 AlamoFire 的 RequestInterceptor 类时一次只运行一次重试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64173789/