【问题标题】:Alamofire/RxSwift how to refresh token and retry requests automatically on status code 401Alamofire/RxSwift 如何在状态码 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 上以某种方式重试请求并在此之前触发更新令牌,我将不胜感激。

【问题讨论】:

    标签: ios swift networking alamofire rx-swift


    【解决方案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>
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-26
      • 1970-01-01
      • 1970-01-01
      • 2021-01-10
      • 2021-08-23
      • 2018-05-05
      • 2020-01-19
      相关资源
      最近更新 更多