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

Posted

技术标签:

【中文标题】Alamofire/RxSwift 如何在状态码 401 上自动刷新令牌和重试请求【英文标题】:Alamofire/RxSwift how to refresh token and retry requests automatically on status code 401 【发布时间】:2021-12-14 08:19:33 【问题描述】:

在我获得任何请求的第一个 401 状态代码后,我需要自动重试请求的帮助。我正在使用 RxSwift 和 Alamofire,所以调用看起来像这样:

public func getSomeEndpointInfo() -> Observable<PKKInfo> 
    return Observable.create( observer in
        let request = AF.request(Router.info)
        request
            .responseDecodable(of: Info.self)  response in
                print("response: \(response)")
                if response.response?.statusCode == 401 
                    observer.onError(NetworkError.unauthorized)
                
                guard let decodedItems = response.value else 
                    observer.onError(NetworkError.invalidJSON)
                    return
                
                observer.onNext(decodedItems)
                observer.onCompleted()
            
        return Disposables.create()
    )

现在在某些服务中,我有以下代码:

service.getSomeEndpointInfo()
.observe(on: MainScheduler.instance)
.subscribe  [unowned self] info in
    self._state.accept(.loaded)
 onError:  [unowned self] error in
    print("---> Error")
    self.sessionManager
        .renewToken()
        .observe(on: MainScheduler.instance)
        .subscribe  token in
            print("---> recieved new token")
            self.service.getSomeEndpointInfo()
         onError:  error in
            print("---> error generating token")
        .disposed(by: self.disposeBag)
.disposed(by: disposeBag)

使用此代码可以工作,但我必须在每个请求上调用更新令牌并将其嵌入到错误订阅中,这感觉不太好。如果您有其他建议,我会在 401 上以某种方式重试请求并在此之前触发更新令牌,我将不胜感激。

【问题讨论】:

【参考方案1】:

我写了一篇关于如何做到这一点的文章。 RxSwift and Handling Invalid Tokens.

本文附带完整的代码和测试证明功能。关键是这个答案底部的类。

你可以这样使用它:

typealias Response = (URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)>

func getData<T>(response: @escaping Response, tokenAcquisitionService: TokenAcquisitionService<T>, request: @escaping (T) throws -> URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)> 
    return Observable
        .deferred  tokenAcquisitionService.token.take(1) 
        .map  try request($0) 
        .flatMap  response($0) 
        .map  response in
            guard response.response.statusCode != 401 else  throw TokenAcquisitionError.unauthorized 
            return response
        
        .retryWhen  $0.renewToken(with: tokenAcquisitionService) 

您可以使用柯里化来制作共享服务的函数...

func makeRequest(builder: @escaping (MyTokenType) -> URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)> 
    getData(
        response:  URLSession.shared.rx.response(request: $0) /* or however Moya makes network requests */ ,
        tokenAcquisitionService: TokenAcquisitionService<MyTokenType>(
            initialToken: getSavedToken(),
            getToken: makeRenewTokenRequest(oldToken:),
            extractToken: extractTokenFromData(_:)),
        request: builder)

在代码中任何需要更新令牌的地方使用上述函数。

这里是上面使用的TokenAquisitionService。让您的所有请求都使用相同的服务对象。


public final class TokenAcquisitionService<T> 

    /// responds with the current token immediatly and emits a new token whenver a new one is aquired. You can, for example, subscribe to it in order to save the token as it's updated.
    public var token: Observable<T>  get 

    public typealias GetToken = (T) -> Observable<(response: HTTPURLResponse, data: Data)>

    /// Creates a `TokenAcquisitionService` object that will store the most recent authorization token acquired and will acquire new ones as needed.
    ///
    /// - Parameters:
    ///   - initialToken: The token the service should start with. Provide a token from storage or an empty string (object represting a missing token) if one has not been aquired yet.
    ///   - getToken: A function responsable for aquiring new tokens when needed.
    ///   - extractToken: A function that can extract a token from the data returned by `getToken`.
    public init(initialToken: T, getToken: @escaping GetToken, extractToken: @escaping (Data) throws -> T)

    /// Allows the token to be set imperativly if necessary.
    /// - Parameter token: The new token the service should use. It will immediatly be emitted to any subscribers to the service.
    public func setToken(_ token: T)


extension ObservableConvertibleType where Element == Error 
    /// Monitors self for `.unauthorized` error events and passes all other errors on. When an `.unauthorized` error is seen, the `service` will get a new token and emit a signal that it's safe to retry the request.
    ///
    /// - Parameter service: A `TokenAcquisitionService` object that is being used to store the auth token for the request.
    /// - Returns: A trigger that will emit when it's safe to retry the request.
    public func renewToken<T>(with service: TokenAcquisitionService<T>) -> Observable<Void>

【讨论】:

以上是关于Alamofire/RxSwift 如何在状态码 401 上自动刷新令牌和重试请求的主要内容,如果未能解决你的问题,请参考以下文章

如何理解HTTP响应的状态码

delphi中的webbrowser ,如何获取网站返回状态码

java如何返回各种状态码

如何理解HTTP响应的状态码

如何理解HTTP响应的状态码

在 RoR 中,通过代理请求网页时如何获取状态码?