swift - 如何保证在使用 AlamoFire 的 RequestInterceptor 类时一次只运行一次重试?

标签 swift alamofire

我正在实现一种使用带有 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/

相关文章:

swift - 我正在尝试将值附加到字符串数组

ios - webservice的输出没有使用alamofire快速提供所需的json

swift - 尝试将 double 从一个 View 传递到另一个 View ,但它不起作用

ios - 调度组不允许执行 Alamofire 请求

ios - 将 slider 关联到 Float 属性

ios - 为什么我不能在 Alamofire 4.0 中使用 SessionManager 初始化或发出请求? (更新)

ios - 如何等到 alamofire 图像请求完成

Swift/iOS8 顺序 HTTP 请求

ios - 解析没有有效凭据的登录过程

ios - didReceiveRemoteNotification 函数不使用 FCM 通知服务器调用