简体   繁体   English

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

[英]Alamofire/RxSwift how to refresh token and retry requests automatically on status code 401

I need help with automatically retrying requests after i get first 401 status code on any request.在我收到任何请求的第一个 401 状态代码后,我需要有关自动重试请求的帮助。 I'm using RxSwift and Alamofire so the call looks like this:我正在使用 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()
    })
}

Now in some service I have the following code:现在在某些服务中,我有以下代码:

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)

With this code works but I have to call renew token on every request and its embedded into error subscription which doesn't feel well.使用此代码有效,但我必须在每个请求上调用更新令牌并将其嵌入到错误订阅中,这感觉不太好。 If you have some other suggestion that on 401 I somehow retry requests and trigger renew token before that i would be grateful.如果您有其他建议,即在 401 上我以某种方式重试请求并在此之前触发更新令牌,我将不胜感激。

I wrote an article on how to do this.我写了一篇关于如何做到这一点的文章。 RxSwift and Handling Invalid Tokens . RxSwift 和处理无效令牌

The article comes complete with code and tests proving functionality.本文附带完整的代码和测试证明功能。 The key is the class at the bottom of this answer.关键是这个答案底部的类。

You use it like this:你像这样使用它:

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) }
}

You can use currying to make a function that shares the service...你可以使用currying来制作一个共享服务的函数......

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)
}

Use the above function anywhere in your code that needs token renewal.在需要令牌更新的代码中的任何位置使用上述函数。

Here is the TokenAquisitionService used above.这是上面使用的 TokenAquisitionService。 Have all your requests use the same service object.让您的所有请求都使用相同的服务对象。


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>
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM