Alamofire/RxSwift jak refresh token a opakování žádosti automaticky na stavový kód 401

0

Otázka

Potřebuji pomoc s automaticky opakování žádosti po tom, co jsem si první kód stavu 401 k jakékoliv žádosti. Používám RxSwift a Alamofire takže volání vypadá takto:

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

Teď v nějaké služby mám následující kód:

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)

S tento kód funguje, ale musím zavolat obnovit token na každý požadavek a jeho vložené do chybová předplatné, které se necítí dobře. Pokud máte nějaký jiný návrh, že na 401 jsem nějak opakovat požadavek a spustit obnovení tokenu, než že bych byl vděčný.

alamofire ios networking rx-swift
2021-10-29 08:28:24
1

Nejlepší odpověď

0

Napsal jsem článek o tom, jak to udělat. RxSwift a Manipulaci Neplatné Žetony.

Článek přichází s kompletní kód a testy prokazující funkčnost. Klíčem k úspěchu je třída v dolní části této odpovědi.

Můžete ji použít jako toto:

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

Můžete použít mazání, aby se funkce, která sdílí služby...

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

Pomocí výše uvedené funkce kdekoliv ve vašem kódu, který potřebuje token obnovení.

Zde je TokenAquisitionService používá výše. Mají všechny vaše požadavky používat stejné služby objektu.


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>
}
2021-10-29 11:54:59

V jiných jazycích

Tato stránka je v jiných jazycích

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................