|
@@ -0,0 +1,1893 @@
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+import Foundation
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+public class Request {
|
|
|
+
|
|
|
+
|
|
|
+ public enum State {
|
|
|
+
|
|
|
+ case initialized
|
|
|
+
|
|
|
+
|
|
|
+ case resumed
|
|
|
+
|
|
|
+
|
|
|
+ case suspended
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ case cancelled
|
|
|
+
|
|
|
+
|
|
|
+ case finished
|
|
|
+
|
|
|
+
|
|
|
+ func canTransitionTo(_ state: State) -> Bool {
|
|
|
+ switch (self, state) {
|
|
|
+ case (.initialized, _):
|
|
|
+ return true
|
|
|
+ case (_, .initialized), (.cancelled, _), (.finished, _):
|
|
|
+ return false
|
|
|
+ case (.resumed, .cancelled), (.suspended, .cancelled), (.resumed, .suspended), (.suspended, .resumed):
|
|
|
+ return true
|
|
|
+ case (.suspended, .suspended), (.resumed, .resumed):
|
|
|
+ return false
|
|
|
+ case (_, .finished):
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public let id: UUID
|
|
|
+
|
|
|
+ public let underlyingQueue: DispatchQueue
|
|
|
+
|
|
|
+ public let serializationQueue: DispatchQueue
|
|
|
+
|
|
|
+ public let eventMonitor: EventMonitor?
|
|
|
+
|
|
|
+ public let interceptor: RequestInterceptor?
|
|
|
+
|
|
|
+ public private(set) weak var delegate: RequestDelegate?
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ struct MutableState {
|
|
|
+
|
|
|
+ var state: State = .initialized
|
|
|
+
|
|
|
+ var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)?
|
|
|
+
|
|
|
+ var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)?
|
|
|
+
|
|
|
+ var redirectHandler: RedirectHandler?
|
|
|
+
|
|
|
+ var cachedResponseHandler: CachedResponseHandler?
|
|
|
+
|
|
|
+ var cURLHandler: (queue: DispatchQueue, handler: (String) -> Void)?
|
|
|
+
|
|
|
+ var urlRequestHandler: (queue: DispatchQueue, handler: (URLRequest) -> Void)?
|
|
|
+
|
|
|
+ var urlSessionTaskHandler: (queue: DispatchQueue, handler: (URLSessionTask) -> Void)?
|
|
|
+
|
|
|
+ var responseSerializers: [() -> Void] = []
|
|
|
+
|
|
|
+ var responseSerializerCompletions: [() -> Void] = []
|
|
|
+
|
|
|
+ var responseSerializerProcessingFinished = false
|
|
|
+
|
|
|
+ var credential: URLCredential?
|
|
|
+
|
|
|
+ var requests: [URLRequest] = []
|
|
|
+
|
|
|
+ var tasks: [URLSessionTask] = []
|
|
|
+
|
|
|
+
|
|
|
+ var metrics: [URLSessionTaskMetrics] = []
|
|
|
+
|
|
|
+ var retryCount = 0
|
|
|
+
|
|
|
+ var error: AFError?
|
|
|
+
|
|
|
+
|
|
|
+ var isFinishing = false
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Protected
|
|
|
+ fileprivate var mutableState = MutableState()
|
|
|
+
|
|
|
+
|
|
|
+ public var state: State { mutableState.state }
|
|
|
+
|
|
|
+ public var isInitialized: Bool { state == .initialized }
|
|
|
+
|
|
|
+ public var isResumed: Bool { state == .resumed }
|
|
|
+
|
|
|
+ public var isSuspended: Bool { state == .suspended }
|
|
|
+
|
|
|
+ public var isCancelled: Bool { state == .cancelled }
|
|
|
+
|
|
|
+ public var isFinished: Bool { state == .finished }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public typealias ProgressHandler = (Progress) -> Void
|
|
|
+
|
|
|
+
|
|
|
+ public let uploadProgress = Progress(totalUnitCount: 0)
|
|
|
+
|
|
|
+ public let downloadProgress = Progress(totalUnitCount: 0)
|
|
|
+
|
|
|
+ private var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? {
|
|
|
+ get { mutableState.uploadProgressHandler }
|
|
|
+ set { mutableState.uploadProgressHandler = newValue }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ fileprivate var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? {
|
|
|
+ get { mutableState.downloadProgressHandler }
|
|
|
+ set { mutableState.downloadProgressHandler = newValue }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public private(set) var redirectHandler: RedirectHandler? {
|
|
|
+ get { mutableState.redirectHandler }
|
|
|
+ set { mutableState.redirectHandler = newValue }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public private(set) var cachedResponseHandler: CachedResponseHandler? {
|
|
|
+ get { mutableState.cachedResponseHandler }
|
|
|
+ set { mutableState.cachedResponseHandler = newValue }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public private(set) var credential: URLCredential? {
|
|
|
+ get { mutableState.credential }
|
|
|
+ set { mutableState.credential = newValue }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @Protected
|
|
|
+ fileprivate var validators: [() -> Void] = []
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public var requests: [URLRequest] { mutableState.requests }
|
|
|
+
|
|
|
+ public var firstRequest: URLRequest? { requests.first }
|
|
|
+
|
|
|
+ public var lastRequest: URLRequest? { requests.last }
|
|
|
+
|
|
|
+ public var request: URLRequest? { lastRequest }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public var performedRequests: [URLRequest] { $mutableState.read { $0.tasks.compactMap { $0.currentRequest } } }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public var response: HTTPURLResponse? { lastTask?.response as? HTTPURLResponse }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public var tasks: [URLSessionTask] { mutableState.tasks }
|
|
|
+
|
|
|
+ public var firstTask: URLSessionTask? { tasks.first }
|
|
|
+
|
|
|
+ public var lastTask: URLSessionTask? { tasks.last }
|
|
|
+
|
|
|
+ public var task: URLSessionTask? { lastTask }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public var allMetrics: [URLSessionTaskMetrics] { mutableState.metrics }
|
|
|
+
|
|
|
+ public var firstMetrics: URLSessionTaskMetrics? { allMetrics.first }
|
|
|
+
|
|
|
+ public var lastMetrics: URLSessionTaskMetrics? { allMetrics.last }
|
|
|
+
|
|
|
+ public var metrics: URLSessionTaskMetrics? { lastMetrics }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public var retryCount: Int { mutableState.retryCount }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public fileprivate(set) var error: AFError? {
|
|
|
+ get { mutableState.error }
|
|
|
+ set { mutableState.error = newValue }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ init(id: UUID = UUID(),
|
|
|
+ underlyingQueue: DispatchQueue,
|
|
|
+ serializationQueue: DispatchQueue,
|
|
|
+ eventMonitor: EventMonitor?,
|
|
|
+ interceptor: RequestInterceptor?,
|
|
|
+ delegate: RequestDelegate) {
|
|
|
+ self.id = id
|
|
|
+ self.underlyingQueue = underlyingQueue
|
|
|
+ self.serializationQueue = serializationQueue
|
|
|
+ self.eventMonitor = eventMonitor
|
|
|
+ self.interceptor = interceptor
|
|
|
+ self.delegate = delegate
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didCreateInitialURLRequest(_ request: URLRequest) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ $mutableState.write { $0.requests.append(request) }
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didCreateInitialURLRequest: request)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didFailToCreateURLRequest(with error: AFError) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ self.error = error
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didFailToCreateURLRequestWithError: error)
|
|
|
+
|
|
|
+ callCURLHandlerIfNecessary()
|
|
|
+
|
|
|
+ retryOrFinish(error: error)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didAdaptInitialRequest(_ initialRequest: URLRequest, to adaptedRequest: URLRequest) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ $mutableState.write { $0.requests.append(adaptedRequest) }
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didAdaptInitialRequest: initialRequest, to: adaptedRequest)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didFailToAdaptURLRequest(_ request: URLRequest, withError error: AFError) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ self.error = error
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didFailToAdaptURLRequest: request, withError: error)
|
|
|
+
|
|
|
+ callCURLHandlerIfNecessary()
|
|
|
+
|
|
|
+ retryOrFinish(error: error)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didCreateURLRequest(_ request: URLRequest) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ $mutableState.read { state in
|
|
|
+ state.urlRequestHandler?.queue.async { state.urlRequestHandler?.handler(request) }
|
|
|
+ }
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didCreateURLRequest: request)
|
|
|
+
|
|
|
+ callCURLHandlerIfNecessary()
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private func callCURLHandlerIfNecessary() {
|
|
|
+ $mutableState.write { mutableState in
|
|
|
+ guard let cURLHandler = mutableState.cURLHandler else { return }
|
|
|
+
|
|
|
+ cURLHandler.queue.async { cURLHandler.handler(self.cURLDescription()) }
|
|
|
+
|
|
|
+ mutableState.cURLHandler = nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didCreateTask(_ task: URLSessionTask) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ $mutableState.write { state in
|
|
|
+ state.tasks.append(task)
|
|
|
+
|
|
|
+ guard let urlSessionTaskHandler = state.urlSessionTaskHandler else { return }
|
|
|
+
|
|
|
+ urlSessionTaskHandler.queue.async { urlSessionTaskHandler.handler(task) }
|
|
|
+ }
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didCreateTask: task)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ func didResume() {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ eventMonitor?.requestDidResume(self)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didResumeTask(_ task: URLSessionTask) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didResumeTask: task)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ func didSuspend() {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ eventMonitor?.requestDidSuspend(self)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didSuspendTask(_ task: URLSessionTask) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didSuspendTask: task)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ func didCancel() {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ error = error ?? AFError.explicitlyCancelled
|
|
|
+
|
|
|
+ eventMonitor?.requestDidCancel(self)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didCancelTask(_ task: URLSessionTask) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didCancelTask: task)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didGatherMetrics(_ metrics: URLSessionTaskMetrics) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ $mutableState.write { $0.metrics.append(metrics) }
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didGatherMetrics: metrics)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didFailTask(_ task: URLSessionTask, earlyWithError error: AFError) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ self.error = error
|
|
|
+
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didFailTask: task, earlyWithError: error)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didCompleteTask(_ task: URLSessionTask, with error: AFError?) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ self.error = self.error ?? error
|
|
|
+
|
|
|
+ validators.forEach { $0() }
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didCompleteTask: task, with: error)
|
|
|
+
|
|
|
+ retryOrFinish(error: self.error)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ func prepareForRetry() {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ $mutableState.write { $0.retryCount += 1 }
|
|
|
+
|
|
|
+ reset()
|
|
|
+
|
|
|
+ eventMonitor?.requestIsRetrying(self)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func retryOrFinish(error: AFError?) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ guard let error = error, let delegate = delegate else { finish(); return }
|
|
|
+
|
|
|
+ delegate.retryResult(for: self, dueTo: error) { retryResult in
|
|
|
+ switch retryResult {
|
|
|
+ case .doNotRetry:
|
|
|
+ self.finish()
|
|
|
+ case let .doNotRetryWithError(retryError):
|
|
|
+ self.finish(error: retryError.asAFError(orFailWith: "Received retryError was not already AFError"))
|
|
|
+ case .retry, .retryWithDelay:
|
|
|
+ delegate.retryRequest(self, withDelay: retryResult.delay)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func finish(error: AFError? = nil) {
|
|
|
+ dispatchPrecondition(condition: .onQueue(underlyingQueue))
|
|
|
+
|
|
|
+ guard !mutableState.isFinishing else { return }
|
|
|
+
|
|
|
+ mutableState.isFinishing = true
|
|
|
+
|
|
|
+ if let error = error { self.error = error }
|
|
|
+
|
|
|
+
|
|
|
+ processNextResponseSerializer()
|
|
|
+
|
|
|
+ eventMonitor?.requestDidFinish(self)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func appendResponseSerializer(_ closure: @escaping () -> Void) {
|
|
|
+ $mutableState.write { mutableState in
|
|
|
+ mutableState.responseSerializers.append(closure)
|
|
|
+
|
|
|
+ if mutableState.state == .finished {
|
|
|
+ mutableState.state = .resumed
|
|
|
+ }
|
|
|
+
|
|
|
+ if mutableState.responseSerializerProcessingFinished {
|
|
|
+ underlyingQueue.async { self.processNextResponseSerializer() }
|
|
|
+ }
|
|
|
+
|
|
|
+ if mutableState.state.canTransitionTo(.resumed) {
|
|
|
+ underlyingQueue.async { if self.delegate?.startImmediately == true { self.resume() } }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func nextResponseSerializer() -> (() -> Void)? {
|
|
|
+ var responseSerializer: (() -> Void)?
|
|
|
+
|
|
|
+ $mutableState.write { mutableState in
|
|
|
+ let responseSerializerIndex = mutableState.responseSerializerCompletions.count
|
|
|
+
|
|
|
+ if responseSerializerIndex < mutableState.responseSerializers.count {
|
|
|
+ responseSerializer = mutableState.responseSerializers[responseSerializerIndex]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return responseSerializer
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ func processNextResponseSerializer() {
|
|
|
+ guard let responseSerializer = nextResponseSerializer() else {
|
|
|
+
|
|
|
+ var completions: [() -> Void] = []
|
|
|
+
|
|
|
+ $mutableState.write { mutableState in
|
|
|
+ completions = mutableState.responseSerializerCompletions
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ mutableState.responseSerializers.removeAll()
|
|
|
+ mutableState.responseSerializerCompletions.removeAll()
|
|
|
+
|
|
|
+ if mutableState.state.canTransitionTo(.finished) {
|
|
|
+ mutableState.state = .finished
|
|
|
+ }
|
|
|
+
|
|
|
+ mutableState.responseSerializerProcessingFinished = true
|
|
|
+ mutableState.isFinishing = false
|
|
|
+ }
|
|
|
+
|
|
|
+ completions.forEach { $0() }
|
|
|
+
|
|
|
+
|
|
|
+ cleanup()
|
|
|
+
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ serializationQueue.async { responseSerializer() }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func responseSerializerDidComplete(completion: @escaping () -> Void) {
|
|
|
+ $mutableState.write { $0.responseSerializerCompletions.append(completion) }
|
|
|
+ processNextResponseSerializer()
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ func reset() {
|
|
|
+ error = nil
|
|
|
+
|
|
|
+ uploadProgress.totalUnitCount = 0
|
|
|
+ uploadProgress.completedUnitCount = 0
|
|
|
+ downloadProgress.totalUnitCount = 0
|
|
|
+ downloadProgress.completedUnitCount = 0
|
|
|
+
|
|
|
+ $mutableState.write { state in
|
|
|
+ state.isFinishing = false
|
|
|
+ state.responseSerializerCompletions = []
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func updateUploadProgress(totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
|
|
|
+ uploadProgress.totalUnitCount = totalBytesExpectedToSend
|
|
|
+ uploadProgress.completedUnitCount = totalBytesSent
|
|
|
+
|
|
|
+ uploadProgressHandler?.queue.async { self.uploadProgressHandler?.handler(self.uploadProgress) }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func withState(perform: (State) -> Void) {
|
|
|
+ $mutableState.withState(perform: perform)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func task(for request: URLRequest, using session: URLSession) -> URLSessionTask {
|
|
|
+ fatalError("Subclasses must override.")
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func cancel() -> Self {
|
|
|
+ $mutableState.write { mutableState in
|
|
|
+ guard mutableState.state.canTransitionTo(.cancelled) else { return }
|
|
|
+
|
|
|
+ mutableState.state = .cancelled
|
|
|
+
|
|
|
+ underlyingQueue.async { self.didCancel() }
|
|
|
+
|
|
|
+ guard let task = mutableState.tasks.last, task.state != .completed else {
|
|
|
+ underlyingQueue.async { self.finish() }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ task.resume()
|
|
|
+ task.cancel()
|
|
|
+ underlyingQueue.async { self.didCancelTask(task) }
|
|
|
+ }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func suspend() -> Self {
|
|
|
+ $mutableState.write { mutableState in
|
|
|
+ guard mutableState.state.canTransitionTo(.suspended) else { return }
|
|
|
+
|
|
|
+ mutableState.state = .suspended
|
|
|
+
|
|
|
+ underlyingQueue.async { self.didSuspend() }
|
|
|
+
|
|
|
+ guard let task = mutableState.tasks.last, task.state != .completed else { return }
|
|
|
+
|
|
|
+ task.suspend()
|
|
|
+ underlyingQueue.async { self.didSuspendTask(task) }
|
|
|
+ }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func resume() -> Self {
|
|
|
+ $mutableState.write { mutableState in
|
|
|
+ guard mutableState.state.canTransitionTo(.resumed) else { return }
|
|
|
+
|
|
|
+ mutableState.state = .resumed
|
|
|
+
|
|
|
+ underlyingQueue.async { self.didResume() }
|
|
|
+
|
|
|
+ guard let task = mutableState.tasks.last, task.state != .completed else { return }
|
|
|
+
|
|
|
+ task.resume()
|
|
|
+ underlyingQueue.async { self.didResumeTask(task) }
|
|
|
+ }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func authenticate(username: String, password: String, persistence: URLCredential.Persistence = .forSession) -> Self {
|
|
|
+ let credential = URLCredential(user: username, password: password, persistence: persistence)
|
|
|
+
|
|
|
+ return authenticate(with: credential)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func authenticate(with credential: URLCredential) -> Self {
|
|
|
+ mutableState.credential = credential
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func downloadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self {
|
|
|
+ mutableState.downloadProgressHandler = (handler: closure, queue: queue)
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func uploadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self {
|
|
|
+ mutableState.uploadProgressHandler = (handler: closure, queue: queue)
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func redirect(using handler: RedirectHandler) -> Self {
|
|
|
+ $mutableState.write { mutableState in
|
|
|
+ precondition(mutableState.redirectHandler == nil, "Redirect handler has already been set.")
|
|
|
+ mutableState.redirectHandler = handler
|
|
|
+ }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func cacheResponse(using handler: CachedResponseHandler) -> Self {
|
|
|
+ $mutableState.write { mutableState in
|
|
|
+ precondition(mutableState.cachedResponseHandler == nil, "Cached response handler has already been set.")
|
|
|
+ mutableState.cachedResponseHandler = handler
|
|
|
+ }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func cURLDescription(on queue: DispatchQueue, calling handler: @escaping (String) -> Void) -> Self {
|
|
|
+ $mutableState.write { mutableState in
|
|
|
+ if mutableState.requests.last != nil {
|
|
|
+ queue.async { handler(self.cURLDescription()) }
|
|
|
+ } else {
|
|
|
+ mutableState.cURLHandler = (queue, handler)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func cURLDescription(calling handler: @escaping (String) -> Void) -> Self {
|
|
|
+ $mutableState.write { mutableState in
|
|
|
+ if mutableState.requests.last != nil {
|
|
|
+ underlyingQueue.async { handler(self.cURLDescription()) }
|
|
|
+ } else {
|
|
|
+ mutableState.cURLHandler = (underlyingQueue, handler)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func onURLRequestCreation(on queue: DispatchQueue = .main, perform handler: @escaping (URLRequest) -> Void) -> Self {
|
|
|
+ $mutableState.write { state in
|
|
|
+ if let request = state.requests.last {
|
|
|
+ queue.async { handler(request) }
|
|
|
+ }
|
|
|
+
|
|
|
+ state.urlRequestHandler = (queue, handler)
|
|
|
+ }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func onURLSessionTaskCreation(on queue: DispatchQueue = .main, perform handler: @escaping (URLSessionTask) -> Void) -> Self {
|
|
|
+ $mutableState.write { state in
|
|
|
+ if let task = state.tasks.last {
|
|
|
+ queue.async { handler(task) }
|
|
|
+ }
|
|
|
+
|
|
|
+ state.urlSessionTaskHandler = (queue, handler)
|
|
|
+ }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func cleanup() {
|
|
|
+ delegate?.cleanup(after: self)
|
|
|
+
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+extension Request: Equatable {
|
|
|
+ public static func ==(lhs: Request, rhs: Request) -> Bool {
|
|
|
+ lhs.id == rhs.id
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension Request: Hashable {
|
|
|
+ public func hash(into hasher: inout Hasher) {
|
|
|
+ hasher.combine(id)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension Request: CustomStringConvertible {
|
|
|
+
|
|
|
+
|
|
|
+ public var description: String {
|
|
|
+ guard let request = performedRequests.last ?? lastRequest,
|
|
|
+ let url = request.url,
|
|
|
+ let method = request.httpMethod else { return "No request created yet." }
|
|
|
+
|
|
|
+ let requestDescription = "\(method) \(url.absoluteString)"
|
|
|
+
|
|
|
+ return response.map { "\(requestDescription) (\($0.statusCode))" } ?? requestDescription
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension Request {
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public func cURLDescription() -> String {
|
|
|
+ guard
|
|
|
+ let request = lastRequest,
|
|
|
+ let url = request.url,
|
|
|
+ let host = url.host,
|
|
|
+ let method = request.httpMethod else { return "$ curl command could not be created" }
|
|
|
+
|
|
|
+ var components = ["$ curl -v"]
|
|
|
+
|
|
|
+ components.append("-X \(method)")
|
|
|
+
|
|
|
+ if let credentialStorage = delegate?.sessionConfiguration.urlCredentialStorage {
|
|
|
+ let protectionSpace = URLProtectionSpace(host: host,
|
|
|
+ port: url.port ?? 0,
|
|
|
+ protocol: url.scheme,
|
|
|
+ realm: host,
|
|
|
+ authenticationMethod: NSURLAuthenticationMethodHTTPBasic)
|
|
|
+
|
|
|
+ if let credentials = credentialStorage.credentials(for: protectionSpace)?.values {
|
|
|
+ for credential in credentials {
|
|
|
+ guard let user = credential.user, let password = credential.password else { continue }
|
|
|
+ components.append("-u \(user):\(password)")
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if let credential = credential, let user = credential.user, let password = credential.password {
|
|
|
+ components.append("-u \(user):\(password)")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if let configuration = delegate?.sessionConfiguration, configuration.httpShouldSetCookies {
|
|
|
+ if
|
|
|
+ let cookieStorage = configuration.httpCookieStorage,
|
|
|
+ let cookies = cookieStorage.cookies(for: url), !cookies.isEmpty {
|
|
|
+ let allCookies = cookies.map { "\($0.name)=\($0.value)" }.joined(separator: ";")
|
|
|
+
|
|
|
+ components.append("-b \"\(allCookies)\"")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var headers = HTTPHeaders()
|
|
|
+
|
|
|
+ if let sessionHeaders = delegate?.sessionConfiguration.headers {
|
|
|
+ for header in sessionHeaders where header.name != "Cookie" {
|
|
|
+ headers[header.name] = header.value
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for header in request.headers where header.name != "Cookie" {
|
|
|
+ headers[header.name] = header.value
|
|
|
+ }
|
|
|
+
|
|
|
+ for header in headers {
|
|
|
+ let escapedValue = header.value.replacingOccurrences(of: "\"", with: "\\\"")
|
|
|
+ components.append("-H \"\(header.name): \(escapedValue)\"")
|
|
|
+ }
|
|
|
+
|
|
|
+ if let httpBodyData = request.httpBody {
|
|
|
+ let httpBody = String(decoding: httpBodyData, as: UTF8.self)
|
|
|
+ var escapedBody = httpBody.replacingOccurrences(of: "\\\"", with: "\\\\\"")
|
|
|
+ escapedBody = escapedBody.replacingOccurrences(of: "\"", with: "\\\"")
|
|
|
+
|
|
|
+ components.append("-d \"\(escapedBody)\"")
|
|
|
+ }
|
|
|
+
|
|
|
+ components.append("\"\(url.absoluteString)\"")
|
|
|
+
|
|
|
+ return components.joined(separator: " \\\n\t")
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+public protocol RequestDelegate: AnyObject {
|
|
|
+
|
|
|
+ var sessionConfiguration: URLSessionConfiguration { get }
|
|
|
+
|
|
|
+
|
|
|
+ var startImmediately: Bool { get }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func cleanup(after request: Request)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func retryResult(for request: Request, dueTo error: AFError, completion: @escaping (RetryResult) -> Void)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func retryRequest(_ request: Request, withDelay timeDelay: TimeInterval?)
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+public class DataRequest: Request {
|
|
|
+
|
|
|
+ public let convertible: URLRequestConvertible
|
|
|
+
|
|
|
+ public var data: Data? { mutableData }
|
|
|
+
|
|
|
+
|
|
|
+ @Protected
|
|
|
+ private var mutableData: Data? = nil
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ init(id: UUID = UUID(),
|
|
|
+ convertible: URLRequestConvertible,
|
|
|
+ underlyingQueue: DispatchQueue,
|
|
|
+ serializationQueue: DispatchQueue,
|
|
|
+ eventMonitor: EventMonitor?,
|
|
|
+ interceptor: RequestInterceptor?,
|
|
|
+ delegate: RequestDelegate) {
|
|
|
+ self.convertible = convertible
|
|
|
+
|
|
|
+ super.init(id: id,
|
|
|
+ underlyingQueue: underlyingQueue,
|
|
|
+ serializationQueue: serializationQueue,
|
|
|
+ eventMonitor: eventMonitor,
|
|
|
+ interceptor: interceptor,
|
|
|
+ delegate: delegate)
|
|
|
+ }
|
|
|
+
|
|
|
+ override func reset() {
|
|
|
+ super.reset()
|
|
|
+
|
|
|
+ mutableData = nil
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didReceive(data: Data) {
|
|
|
+ if self.data == nil {
|
|
|
+ mutableData = data
|
|
|
+ } else {
|
|
|
+ $mutableData.write { $0?.append(data) }
|
|
|
+ }
|
|
|
+
|
|
|
+ updateDownloadProgress()
|
|
|
+ }
|
|
|
+
|
|
|
+ override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask {
|
|
|
+ let copiedRequest = request
|
|
|
+ return session.dataTask(with: copiedRequest)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ func updateDownloadProgress() {
|
|
|
+ let totalBytesReceived = Int64(data?.count ?? 0)
|
|
|
+ let totalBytesExpected = task?.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
|
|
|
+
|
|
|
+ downloadProgress.totalUnitCount = totalBytesExpected
|
|
|
+ downloadProgress.completedUnitCount = totalBytesReceived
|
|
|
+
|
|
|
+ downloadProgressHandler?.queue.async { self.downloadProgressHandler?.handler(self.downloadProgress) }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func validate(_ validation: @escaping Validation) -> Self {
|
|
|
+ let validator: () -> Void = { [unowned self] in
|
|
|
+ guard self.error == nil, let response = self.response else { return }
|
|
|
+
|
|
|
+ let result = validation(self.request, response, self.data)
|
|
|
+
|
|
|
+ if case let .failure(error) = result { self.error = error.asAFError(or: .responseValidationFailed(reason: .customValidationFailed(error: error))) }
|
|
|
+
|
|
|
+ self.eventMonitor?.request(self,
|
|
|
+ didValidateRequest: self.request,
|
|
|
+ response: response,
|
|
|
+ data: self.data,
|
|
|
+ withResult: result)
|
|
|
+ }
|
|
|
+
|
|
|
+ $validators.write { $0.append(validator) }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+public final class DataStreamRequest: Request {
|
|
|
+
|
|
|
+ public typealias Handler<Success, Failure: Error> = (Stream<Success, Failure>) throws -> Void
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public struct Stream<Success, Failure: Error> {
|
|
|
+
|
|
|
+ public let event: Event<Success, Failure>
|
|
|
+
|
|
|
+ public let token: CancellationToken
|
|
|
+
|
|
|
+
|
|
|
+ public func cancel() {
|
|
|
+ token.cancel()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public enum Event<Success, Failure: Error> {
|
|
|
+
|
|
|
+
|
|
|
+ case stream(Result<Success, Failure>)
|
|
|
+
|
|
|
+
|
|
|
+ case complete(Completion)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public struct Completion {
|
|
|
+
|
|
|
+ public let request: URLRequest?
|
|
|
+
|
|
|
+ public let response: HTTPURLResponse?
|
|
|
+
|
|
|
+ public let metrics: URLSessionTaskMetrics?
|
|
|
+
|
|
|
+ public let error: AFError?
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public struct CancellationToken {
|
|
|
+ weak var request: DataStreamRequest?
|
|
|
+
|
|
|
+ init(_ request: DataStreamRequest) {
|
|
|
+ self.request = request
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public func cancel() {
|
|
|
+ request?.cancel()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public let convertible: URLRequestConvertible
|
|
|
+
|
|
|
+ public let automaticallyCancelOnStreamError: Bool
|
|
|
+
|
|
|
+
|
|
|
+ struct StreamMutableState {
|
|
|
+
|
|
|
+ var outputStream: OutputStream?
|
|
|
+
|
|
|
+ var streams: [(_ data: Data) -> Void] = []
|
|
|
+
|
|
|
+
|
|
|
+ var numberOfExecutingStreams = 0
|
|
|
+
|
|
|
+ var enqueuedCompletionEvents: [() -> Void] = []
|
|
|
+ }
|
|
|
+
|
|
|
+ @Protected
|
|
|
+ var streamMutableState = StreamMutableState()
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ init(id: UUID = UUID(),
|
|
|
+ convertible: URLRequestConvertible,
|
|
|
+ automaticallyCancelOnStreamError: Bool,
|
|
|
+ underlyingQueue: DispatchQueue,
|
|
|
+ serializationQueue: DispatchQueue,
|
|
|
+ eventMonitor: EventMonitor?,
|
|
|
+ interceptor: RequestInterceptor?,
|
|
|
+ delegate: RequestDelegate) {
|
|
|
+ self.convertible = convertible
|
|
|
+ self.automaticallyCancelOnStreamError = automaticallyCancelOnStreamError
|
|
|
+
|
|
|
+ super.init(id: id,
|
|
|
+ underlyingQueue: underlyingQueue,
|
|
|
+ serializationQueue: serializationQueue,
|
|
|
+ eventMonitor: eventMonitor,
|
|
|
+ interceptor: interceptor,
|
|
|
+ delegate: delegate)
|
|
|
+ }
|
|
|
+
|
|
|
+ override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask {
|
|
|
+ let copiedRequest = request
|
|
|
+ return session.dataTask(with: copiedRequest)
|
|
|
+ }
|
|
|
+
|
|
|
+ override func finish(error: AFError? = nil) {
|
|
|
+ $streamMutableState.write { state in
|
|
|
+ state.outputStream?.close()
|
|
|
+ }
|
|
|
+
|
|
|
+ super.finish(error: error)
|
|
|
+ }
|
|
|
+
|
|
|
+ func didReceive(data: Data) {
|
|
|
+ $streamMutableState.write { state in
|
|
|
+ #if !(os(Linux) || os(Windows))
|
|
|
+ if let stream = state.outputStream {
|
|
|
+ underlyingQueue.async {
|
|
|
+ var bytes = Array(data)
|
|
|
+ stream.write(&bytes, maxLength: bytes.count)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ #endif
|
|
|
+ state.numberOfExecutingStreams += state.streams.count
|
|
|
+ let localState = state
|
|
|
+ underlyingQueue.async { localState.streams.forEach { $0(data) } }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func validate(_ validation: @escaping Validation) -> Self {
|
|
|
+ let validator: () -> Void = { [unowned self] in
|
|
|
+ guard self.error == nil, let response = self.response else { return }
|
|
|
+
|
|
|
+ let result = validation(self.request, response)
|
|
|
+
|
|
|
+ if case let .failure(error) = result {
|
|
|
+ self.error = error.asAFError(or: .responseValidationFailed(reason: .customValidationFailed(error: error)))
|
|
|
+ }
|
|
|
+
|
|
|
+ self.eventMonitor?.request(self,
|
|
|
+ didValidateRequest: self.request,
|
|
|
+ response: response,
|
|
|
+ withResult: result)
|
|
|
+ }
|
|
|
+
|
|
|
+ $validators.write { $0.append(validator) }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+ #if !(os(Linux) || os(Windows))
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public func asInputStream(bufferSize: Int = 1024) -> InputStream? {
|
|
|
+ defer { resume() }
|
|
|
+
|
|
|
+ var inputStream: InputStream?
|
|
|
+ $streamMutableState.write { state in
|
|
|
+ Foundation.Stream.getBoundStreams(withBufferSize: bufferSize,
|
|
|
+ inputStream: &inputStream,
|
|
|
+ outputStream: &state.outputStream)
|
|
|
+ state.outputStream?.open()
|
|
|
+ }
|
|
|
+
|
|
|
+ return inputStream
|
|
|
+ }
|
|
|
+ #endif
|
|
|
+
|
|
|
+ func capturingError(from closure: () throws -> Void) {
|
|
|
+ do {
|
|
|
+ try closure()
|
|
|
+ } catch {
|
|
|
+ self.error = error.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: error)))
|
|
|
+ cancel()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ func appendStreamCompletion<Success, Failure>(on queue: DispatchQueue,
|
|
|
+ stream: @escaping Handler<Success, Failure>) {
|
|
|
+ appendResponseSerializer {
|
|
|
+ self.underlyingQueue.async {
|
|
|
+ self.responseSerializerDidComplete {
|
|
|
+ self.$streamMutableState.write { state in
|
|
|
+ guard state.numberOfExecutingStreams == 0 else {
|
|
|
+ state.enqueuedCompletionEvents.append {
|
|
|
+ self.enqueueCompletion(on: queue, stream: stream)
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ self.enqueueCompletion(on: queue, stream: stream)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ func enqueueCompletion<Success, Failure>(on queue: DispatchQueue,
|
|
|
+ stream: @escaping Handler<Success, Failure>) {
|
|
|
+ queue.async {
|
|
|
+ do {
|
|
|
+ let completion = Completion(request: self.request,
|
|
|
+ response: self.response,
|
|
|
+ metrics: self.metrics,
|
|
|
+ error: self.error)
|
|
|
+ try stream(.init(event: .complete(completion), token: .init(self)))
|
|
|
+ } catch {
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension DataStreamRequest.Stream {
|
|
|
+
|
|
|
+ public var result: Result<Success, Failure>? {
|
|
|
+ guard case let .stream(result) = event else { return nil }
|
|
|
+
|
|
|
+ return result
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public var value: Success? {
|
|
|
+ guard case let .success(value) = result else { return nil }
|
|
|
+
|
|
|
+ return value
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public var error: Failure? {
|
|
|
+ guard case let .failure(error) = result else { return nil }
|
|
|
+
|
|
|
+ return error
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public var completion: DataStreamRequest.Completion? {
|
|
|
+ guard case let .complete(completion) = event else { return nil }
|
|
|
+
|
|
|
+ return completion
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+public class DownloadRequest: Request {
|
|
|
+
|
|
|
+
|
|
|
+ public struct Options: OptionSet {
|
|
|
+
|
|
|
+ public static let createIntermediateDirectories = Options(rawValue: 1 << 0)
|
|
|
+
|
|
|
+ public static let removePreviousFile = Options(rawValue: 1 << 1)
|
|
|
+
|
|
|
+ public let rawValue: Int
|
|
|
+
|
|
|
+ public init(rawValue: Int) {
|
|
|
+ self.rawValue = rawValue
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public typealias Destination = (_ temporaryURL: URL,
|
|
|
+ _ response: HTTPURLResponse) -> (destinationURL: URL, options: Options)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public class func suggestedDownloadDestination(for directory: FileManager.SearchPathDirectory = .documentDirectory,
|
|
|
+ in domain: FileManager.SearchPathDomainMask = .userDomainMask,
|
|
|
+ options: Options = []) -> Destination {
|
|
|
+ { temporaryURL, response in
|
|
|
+ let directoryURLs = FileManager.default.urls(for: directory, in: domain)
|
|
|
+ let url = directoryURLs.first?.appendingPathComponent(response.suggestedFilename!) ?? temporaryURL
|
|
|
+
|
|
|
+ return (url, options)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ static let defaultDestination: Destination = { url, _ in
|
|
|
+ (defaultDestinationURL(url), [])
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ static let defaultDestinationURL: (URL) -> URL = { url in
|
|
|
+ let filename = "Alamofire_\(url.lastPathComponent)"
|
|
|
+ let destination = url.deletingLastPathComponent().appendingPathComponent(filename)
|
|
|
+
|
|
|
+ return destination
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public enum Downloadable {
|
|
|
+
|
|
|
+ case request(URLRequestConvertible)
|
|
|
+
|
|
|
+ case resumeData(Data)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ private struct DownloadRequestMutableState {
|
|
|
+
|
|
|
+ var resumeData: Data?
|
|
|
+
|
|
|
+ var fileURL: URL?
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Protected
|
|
|
+ private var mutableDownloadState = DownloadRequestMutableState()
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public var resumeData: Data? {
|
|
|
+ #if !(os(Linux) || os(Windows))
|
|
|
+ return mutableDownloadState.resumeData ?? error?.downloadResumeData
|
|
|
+ #else
|
|
|
+ return mutableDownloadState.resumeData
|
|
|
+ #endif
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public var fileURL: URL? { mutableDownloadState.fileURL }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public let downloadable: Downloadable
|
|
|
+
|
|
|
+ let destination: Destination
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ init(id: UUID = UUID(),
|
|
|
+ downloadable: Downloadable,
|
|
|
+ underlyingQueue: DispatchQueue,
|
|
|
+ serializationQueue: DispatchQueue,
|
|
|
+ eventMonitor: EventMonitor?,
|
|
|
+ interceptor: RequestInterceptor?,
|
|
|
+ delegate: RequestDelegate,
|
|
|
+ destination: @escaping Destination) {
|
|
|
+ self.downloadable = downloadable
|
|
|
+ self.destination = destination
|
|
|
+
|
|
|
+ super.init(id: id,
|
|
|
+ underlyingQueue: underlyingQueue,
|
|
|
+ serializationQueue: serializationQueue,
|
|
|
+ eventMonitor: eventMonitor,
|
|
|
+ interceptor: interceptor,
|
|
|
+ delegate: delegate)
|
|
|
+ }
|
|
|
+
|
|
|
+ override func reset() {
|
|
|
+ super.reset()
|
|
|
+
|
|
|
+ $mutableDownloadState.write {
|
|
|
+ $0.resumeData = nil
|
|
|
+ $0.fileURL = nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didFinishDownloading(using task: URLSessionTask, with result: Result<URL, AFError>) {
|
|
|
+ eventMonitor?.request(self, didFinishDownloadingUsing: task, with: result)
|
|
|
+
|
|
|
+ switch result {
|
|
|
+ case let .success(url): mutableDownloadState.fileURL = url
|
|
|
+ case let .failure(error): self.error = error
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func updateDownloadProgress(bytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
|
|
+ downloadProgress.totalUnitCount = totalBytesExpectedToWrite
|
|
|
+ downloadProgress.completedUnitCount += bytesWritten
|
|
|
+
|
|
|
+ downloadProgressHandler?.queue.async { self.downloadProgressHandler?.handler(self.downloadProgress) }
|
|
|
+ }
|
|
|
+
|
|
|
+ override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask {
|
|
|
+ session.downloadTask(with: request)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public func task(forResumeData data: Data, using session: URLSession) -> URLSessionTask {
|
|
|
+ session.downloadTask(withResumeData: data)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ override public func cancel() -> Self {
|
|
|
+ cancel(producingResumeData: false)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func cancel(producingResumeData shouldProduceResumeData: Bool) -> Self {
|
|
|
+ cancel(optionallyProducingResumeData: shouldProduceResumeData ? { _ in } : nil)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func cancel(byProducingResumeData completionHandler: @escaping (_ data: Data?) -> Void) -> Self {
|
|
|
+ cancel(optionallyProducingResumeData: completionHandler)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ private func cancel(optionallyProducingResumeData completionHandler: ((_ resumeData: Data?) -> Void)?) -> Self {
|
|
|
+ $mutableState.write { mutableState in
|
|
|
+ guard mutableState.state.canTransitionTo(.cancelled) else { return }
|
|
|
+
|
|
|
+ mutableState.state = .cancelled
|
|
|
+
|
|
|
+ underlyingQueue.async { self.didCancel() }
|
|
|
+
|
|
|
+ guard let task = mutableState.tasks.last as? URLSessionDownloadTask, task.state != .completed else {
|
|
|
+ underlyingQueue.async { self.finish() }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if let completionHandler = completionHandler {
|
|
|
+
|
|
|
+ task.resume()
|
|
|
+ task.cancel { resumeData in
|
|
|
+ self.mutableDownloadState.resumeData = resumeData
|
|
|
+ self.underlyingQueue.async { self.didCancelTask(task) }
|
|
|
+ completionHandler(resumeData)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+
|
|
|
+ task.resume()
|
|
|
+ task.cancel(byProducingResumeData: { _ in })
|
|
|
+ self.underlyingQueue.async { self.didCancelTask(task) }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @discardableResult
|
|
|
+ public func validate(_ validation: @escaping Validation) -> Self {
|
|
|
+ let validator: () -> Void = { [unowned self] in
|
|
|
+ guard self.error == nil, let response = self.response else { return }
|
|
|
+
|
|
|
+ let result = validation(self.request, response, self.fileURL)
|
|
|
+
|
|
|
+ if case let .failure(error) = result {
|
|
|
+ self.error = error.asAFError(or: .responseValidationFailed(reason: .customValidationFailed(error: error)))
|
|
|
+ }
|
|
|
+
|
|
|
+ self.eventMonitor?.request(self,
|
|
|
+ didValidateRequest: self.request,
|
|
|
+ response: response,
|
|
|
+ fileURL: self.fileURL,
|
|
|
+ withResult: result)
|
|
|
+ }
|
|
|
+
|
|
|
+ $validators.write { $0.append(validator) }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+public class UploadRequest: DataRequest {
|
|
|
+
|
|
|
+ public enum Uploadable {
|
|
|
+
|
|
|
+ case data(Data)
|
|
|
+
|
|
|
+
|
|
|
+ case file(URL, shouldRemove: Bool)
|
|
|
+
|
|
|
+ case stream(InputStream)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public let upload: UploadableConvertible
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public let fileManager: FileManager
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public var uploadable: Uploadable?
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ init(id: UUID = UUID(),
|
|
|
+ convertible: UploadConvertible,
|
|
|
+ underlyingQueue: DispatchQueue,
|
|
|
+ serializationQueue: DispatchQueue,
|
|
|
+ eventMonitor: EventMonitor?,
|
|
|
+ interceptor: RequestInterceptor?,
|
|
|
+ fileManager: FileManager,
|
|
|
+ delegate: RequestDelegate) {
|
|
|
+ upload = convertible
|
|
|
+ self.fileManager = fileManager
|
|
|
+
|
|
|
+ super.init(id: id,
|
|
|
+ convertible: convertible,
|
|
|
+ underlyingQueue: underlyingQueue,
|
|
|
+ serializationQueue: serializationQueue,
|
|
|
+ eventMonitor: eventMonitor,
|
|
|
+ interceptor: interceptor,
|
|
|
+ delegate: delegate)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didCreateUploadable(_ uploadable: Uploadable) {
|
|
|
+ self.uploadable = uploadable
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didCreateUploadable: uploadable)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func didFailToCreateUploadable(with error: AFError) {
|
|
|
+ self.error = error
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didFailToCreateUploadableWithError: error)
|
|
|
+
|
|
|
+ retryOrFinish(error: error)
|
|
|
+ }
|
|
|
+
|
|
|
+ override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask {
|
|
|
+ guard let uploadable = uploadable else {
|
|
|
+ fatalError("Attempting to create a URLSessionUploadTask when Uploadable value doesn't exist.")
|
|
|
+ }
|
|
|
+
|
|
|
+ switch uploadable {
|
|
|
+ case let .data(data): return session.uploadTask(with: request, from: data)
|
|
|
+ case let .file(url, _): return session.uploadTask(with: request, fromFile: url)
|
|
|
+ case .stream: return session.uploadTask(withStreamedRequest: request)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override func reset() {
|
|
|
+
|
|
|
+ uploadable = nil
|
|
|
+
|
|
|
+ super.reset()
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func inputStream() -> InputStream {
|
|
|
+ guard let uploadable = uploadable else {
|
|
|
+ fatalError("Attempting to access the input stream but the uploadable doesn't exist.")
|
|
|
+ }
|
|
|
+
|
|
|
+ guard case let .stream(stream) = uploadable else {
|
|
|
+ fatalError("Attempted to access the stream of an UploadRequest that wasn't created with one.")
|
|
|
+ }
|
|
|
+
|
|
|
+ eventMonitor?.request(self, didProvideInputStream: stream)
|
|
|
+
|
|
|
+ return stream
|
|
|
+ }
|
|
|
+
|
|
|
+ override public func cleanup() {
|
|
|
+ defer { super.cleanup() }
|
|
|
+
|
|
|
+ guard
|
|
|
+ let uploadable = self.uploadable,
|
|
|
+ case let .file(url, shouldRemove) = uploadable,
|
|
|
+ shouldRemove
|
|
|
+ else { return }
|
|
|
+
|
|
|
+ try? fileManager.removeItem(at: url)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+public protocol UploadableConvertible {
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func createUploadable() throws -> UploadRequest.Uploadable
|
|
|
+}
|
|
|
+
|
|
|
+extension UploadRequest.Uploadable: UploadableConvertible {
|
|
|
+ public func createUploadable() throws -> UploadRequest.Uploadable {
|
|
|
+ self
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+public protocol UploadConvertible: UploadableConvertible & URLRequestConvertible {}
|