Механизм повтора 401 с использованием Combine Publishers - PullRequest
1 голос
/ 02 мая 2020

Довольно новый для комбината. Распространенный сценарий с использованием токенов доступа и refre sh token.

Вы получаете 401, и вам нужно обработать его (позвонить в какую-либо службу, чтобы обновить sh токен) перед повторной попыткой первоначального вызова

func dataLoader(backendURL: URL) -> AnyPublisher<Data, Error> {
    let request = URLRequest(url: backendURL)
    return dataPublisher(for: request)
        // We get here when a request fails
        .tryCatch { (error) -> AnyPublisher<(data: Data, response: URLResponse), URLError> in
          guard error.errorCode == 401 else {  // UPS - Unauthorized request
                throw error
            }

          // We need to refresh token and retry -> HOW?
          // And try again 
          // return dataPublisher(for: request) 
        }
        .tryMap { data, response -> Data in
            guard let httpResponse = response as? HTTPURLResponse,
                httpResponse.statusCode == 200 else {

                throw CustomError.invalidServerResponse
            }
            return data
        }
        .eraseToAnyPublisher()
}

Как бы я go обернул это "token refre sh service"?

1 Ответ

2 голосов
/ 03 мая 2020

Ваш код упоминает «токен», но вы не объясняете, что это такое. Предположим, у вас есть тип токена:

struct Token: RawRepresentable {
    var rawValue: String
}

И, скажем, у вас есть функция, которая асинхронно получает токен fre sh, возвращая издателя токена fre sh:

func freshToken() -> AnyPublisher<Token, Error> {
    // Your code here, probably involving a URL request/response...
    fatalError()
}

И скажем, вы генерируете запрос URL для данных, комбинируя некоторый URL с токеном:

func backendRequest(with url: URL, token: Token) -> URLRequest {
    // Your code here, to somehow combine the url and the token into the real ...
    fatalError()
}

Теперь вы хотите повторить запрос с токеном fre sh каждый раз Если ответ 404. Вероятно, вам следует ограничить количество попыток. Итак, давайте напишем функцию для подсчета triesLeft. Если triesLeft > 1 и ответ 404, он запросит токен fre sh и использует его для повторного вызова (с уменьшением triesLeft).

Цель становится более сложной, поскольку URLSession.DataTaskPublisher не превращает ответ 404 в ошибку. Он обрабатывает его как нормальный вывод.

Поэтому мы будем использовать вложенную вспомогательную функцию для обработки вывода DataTaskPublisher, чтобы у нас не было такого большого количества кода, вложенного в замыкания. Вспомогательная функция с именем publisher(forDataTaskOutput:) решает, что делать на основе ответа.

  • Если ответ является ответом HTTP с кодом 200, он просто возвращает данные. Обратите внимание, что он должен вернуть издателя, чей Failure равен Error, поэтому он использует Result.Pubilsher и позволяет Swift выводить тип Failure.

  • Если ответ HTTP-ответ с кодом 404 и triesLeft > 1, он вызывает freshToken и использует flatMap для объединения этого в другой вызов внешней функции.

  • В противном случае он создает ошибка с ошибкой CustomError.invalidServerResponse.

func data(atBackendURL url: URL, token: Token, triesLeft: Int) -> AnyPublisher<Data, Error> {
    func publisher(forDataTaskOutput output: URLSession.DataTaskPublisher.Output) -> AnyPublisher<Data, Error> {
        switch (output.response as? HTTPURLResponse)?.statusCode {
        case .some(200):
            return Result.success(output.data).publisher.eraseToAnyPublisher()
        case .some(404) where triesLeft > 1:
            return freshToken()
                .flatMap { data(atBackendURL: url, token: $0, triesLeft: triesLeft - 1) }
                .eraseToAnyPublisher()
        default:
            return Fail(error: CustomError.invalidServerResponse).eraseToAnyPublisher()
        }
    }

    let request = backendRequest(with: url, token: token)
    return URLSession.shared.dataTaskPublisher(for: request)
        .mapError { $0 as Error }
        .flatMap(publisher(forDataTaskOutput:))
        .eraseToAnyPublisher()
}
...