#swift #alamofire
#swift #alamofire
Вопрос:
Я реализую способ обновления токена сеанса с помощью OAuth2 с помощью AlamoFire5, и я пытаюсь выяснить, как решить этот сценарий:
1 — При сбое какого-либо запроса должен запускаться запрос refreshToken, который должен быть единственным выполняемым одновременно запросом refreshToken. т. е. Другие запросы, которые завершились неудачей, не должны повторяться до завершения этого запроса.
2 — Если refreshToken завершается с ошибкой, приложение должно перезапускаться, а все остальные ожидающие запросы должны быть отменены.
3 — Если запрос refreshToken выполнен успешно, токен должен быть обновлен, и все остальные ожидающие запросы должны теперь продолжаться.
Я использую класс RequestInterceptor от AlamoFire, чтобы попытаться решить эту проблему, и моя реализация пока такова:
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?
Ответ №1:
Вы можете использовать массив для хранения замыканий повторных попыток для запросов, которые могут возникать до завершения обновления токена, и логическое значение, чтобы знать, что выполняется действие обновления.
В итоге вы получите что-то вроде этого:
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:
defer
Оператор используется для выполнения кода непосредственно перед передачей управления программой за пределы области, в которой появляется оператор defer .
Итак, закрытие первого defer
оператора будет выполнено до onSuccess
onError
закрытия or.
Вот почему нам нужно снова заблокировать источник внутри onSuccess
и onError
замыкания.