You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
weibo/Pods/Alamofire/Source/RequestInterceptor.swift

245 lines
9.6 KiB

4 years ago
//
// RequestInterceptor.swift
//
// Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary.
public protocol RequestAdapter {
/// Inspects and adapts the specified `URLRequest` in some manner and calls the completion handler with the Result.
///
/// - Parameters:
/// - urlRequest: The `URLRequest` to adapt.
/// - session: The `Session` that will execute the `URLRequest`.
/// - completion: The completion handler that must be called when adaptation is complete.
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void)
}
// MARK: -
/// Outcome of determination whether retry is necessary.
public enum RetryResult {
/// Retry should be attempted immediately.
case retry
/// Retry should be attempted after the associated `TimeInterval`.
case retryWithDelay(TimeInterval)
/// Do not retry.
case doNotRetry
/// Do not retry due to the associated `Error`.
case doNotRetryWithError(Error)
}
extension RetryResult {
var retryRequired: Bool {
switch self {
case .retry, .retryWithDelay: return true
default: return false
}
}
var delay: TimeInterval? {
switch self {
case let .retryWithDelay(delay): return delay
default: return nil
}
}
var error: Error? {
guard case let .doNotRetryWithError(error) = self else { return nil }
return error
}
}
/// A type that determines whether a request should be retried after being executed by the specified session manager
/// and encountering an error.
public protocol RequestRetrier {
/// Determines whether the `Request` should be retried by calling the `completion` closure.
///
/// This operation is fully asynchronous. Any amount of time can be taken to determine whether the request needs
/// to be retried. The one requirement is that the completion closure is called to ensure the request is properly
/// cleaned up after.
///
/// - Parameters:
/// - request: `Request` that failed due to the provided `Error`.
/// - session: `Session` that produced the `Request`.
/// - error: `Error` encountered while executing the `Request`.
/// - completion: Completion closure to be executed when a retry decision has been determined.
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void)
}
// MARK: -
/// Type that provides both `RequestAdapter` and `RequestRetrier` functionality.
public protocol RequestInterceptor: RequestAdapter, RequestRetrier {}
extension RequestInterceptor {
public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
completion(.success(urlRequest))
}
public func retry(_ request: Request,
for session: Session,
dueTo error: Error,
completion: @escaping (RetryResult) -> Void) {
completion(.doNotRetry)
}
}
/// `RequestAdapter` closure definition.
public typealias AdaptHandler = (URLRequest, Session, _ completion: @escaping (Result<URLRequest, Error>) -> Void) -> Void
/// `RequestRetrier` closure definition.
public typealias RetryHandler = (Request, Session, Error, _ completion: @escaping (RetryResult) -> Void) -> Void
// MARK: -
/// Closure-based `RequestAdapter`.
open class Adapter: RequestInterceptor {
private let adaptHandler: AdaptHandler
/// Creates an instance using the provided closure.
///
/// - Parameter adaptHandler: `AdaptHandler` closure to be executed when handling request adaptation.
public init(_ adaptHandler: @escaping AdaptHandler) {
self.adaptHandler = adaptHandler
}
open func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
adaptHandler(urlRequest, session, completion)
}
}
// MARK: -
/// Closure-based `RequestRetrier`.
open class Retrier: RequestInterceptor {
private let retryHandler: RetryHandler
/// Creates an instance using the provided closure.
///
/// - Parameter retryHandler: `RetryHandler` closure to be executed when handling request retry.
public init(_ retryHandler: @escaping RetryHandler) {
self.retryHandler = retryHandler
}
open func retry(_ request: Request,
for session: Session,
dueTo error: Error,
completion: @escaping (RetryResult) -> Void) {
retryHandler(request, session, error, completion)
}
}
// MARK: -
/// `RequestInterceptor` which can use multiple `RequestAdapter` and `RequestRetrier` values.
open class Interceptor: RequestInterceptor {
/// All `RequestAdapter`s associated with the instance. These adapters will be run until one fails.
public let adapters: [RequestAdapter]
/// All `RequestRetrier`s associated with the instance. These retriers will be run one at a time until one triggers retry.
public let retriers: [RequestRetrier]
/// Creates an instance from `AdaptHandler` and `RetryHandler` closures.
///
/// - Parameters:
/// - adaptHandler: `AdaptHandler` closure to be used.
/// - retryHandler: `RetryHandler` closure to be used.
public init(adaptHandler: @escaping AdaptHandler, retryHandler: @escaping RetryHandler) {
adapters = [Adapter(adaptHandler)]
retriers = [Retrier(retryHandler)]
}
/// Creates an instance from `RequestAdapter` and `RequestRetrier` values.
///
/// - Parameters:
/// - adapter: `RequestAdapter` value to be used.
/// - retrier: `RequestRetrier` value to be used.
public init(adapter: RequestAdapter, retrier: RequestRetrier) {
adapters = [adapter]
retriers = [retrier]
}
/// Creates an instance from the arrays of `RequestAdapter` and `RequestRetrier` values.
///
/// - Parameters:
/// - adapters: `RequestAdapter` values to be used.
/// - retriers: `RequestRetrier` values to be used.
/// - interceptors: `RequestInterceptor`s to be used.
public init(adapters: [RequestAdapter] = [], retriers: [RequestRetrier] = [], interceptors: [RequestInterceptor] = []) {
self.adapters = adapters + interceptors
self.retriers = retriers + interceptors
}
open func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
adapt(urlRequest, for: session, using: adapters, completion: completion)
}
private func adapt(_ urlRequest: URLRequest,
for session: Session,
using adapters: [RequestAdapter],
completion: @escaping (Result<URLRequest, Error>) -> Void) {
var pendingAdapters = adapters
guard !pendingAdapters.isEmpty else { completion(.success(urlRequest)); return }
let adapter = pendingAdapters.removeFirst()
adapter.adapt(urlRequest, for: session) { result in
switch result {
case let .success(urlRequest):
self.adapt(urlRequest, for: session, using: pendingAdapters, completion: completion)
case .failure:
completion(result)
}
}
}
open func retry(_ request: Request,
for session: Session,
dueTo error: Error,
completion: @escaping (RetryResult) -> Void) {
retry(request, for: session, dueTo: error, using: retriers, completion: completion)
}
private func retry(_ request: Request,
for session: Session,
dueTo error: Error,
using retriers: [RequestRetrier],
completion: @escaping (RetryResult) -> Void) {
var pendingRetriers = retriers
guard !pendingRetriers.isEmpty else { completion(.doNotRetry); return }
let retrier = pendingRetriers.removeFirst()
retrier.retry(request, for: session, dueTo: error) { result in
switch result {
case .retry, .retryWithDelay, .doNotRetryWithError:
completion(result)
case .doNotRetry:
// Only continue to the next retrier if retry was not triggered and no error was encountered
self.retry(request, for: session, dueTo: error, using: pendingRetriers, completion: completion)
}
}
}
}