AutorÃa | Ultima modificación | Ver Log |
//// SessionDelegate.swift//// Copyright (c) 2014-2018 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/// Class which implements the various `URLSessionDelegate` methods to connect various Alamofire features.open class SessionDelegate: NSObject {private let fileManager: FileManagerweak var stateProvider: SessionStateProvider?var eventMonitor: EventMonitor?/// Creates an instance from the given `FileManager`.////// - Parameter fileManager: `FileManager` to use for underlying file management, such as moving downloaded files./// `.default` by default.public init(fileManager: FileManager = .default) {self.fileManager = fileManager}/// Internal method to find and cast requests while maintaining some integrity checking.////// - Parameters:/// - task: The `URLSessionTask` for which to find the associated `Request`./// - type: The `Request` subclass type to cast any `Request` associate with `task`.func request<R: Request>(for task: URLSessionTask, as type: R.Type) -> R? {guard let provider = stateProvider else {assertionFailure("StateProvider is nil.")return nil}return provider.request(for: task) as? R}}/// Type which provides various `Session` state values.protocol SessionStateProvider: AnyObject {var serverTrustManager: ServerTrustManager? { get }var redirectHandler: RedirectHandler? { get }var cachedResponseHandler: CachedResponseHandler? { get }func request(for task: URLSessionTask) -> Request?func didGatherMetricsForTask(_ task: URLSessionTask)func didCompleteTask(_ task: URLSessionTask, completion: @escaping () -> Void)func credential(for task: URLSessionTask, in protectionSpace: URLProtectionSpace) -> URLCredential?func cancelRequestsForSessionInvalidation(with error: Error?)}// MARK: URLSessionDelegateextension SessionDelegate: URLSessionDelegate {open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {eventMonitor?.urlSession(session, didBecomeInvalidWithError: error)stateProvider?.cancelRequestsForSessionInvalidation(with: error)}}// MARK: URLSessionTaskDelegateextension SessionDelegate: URLSessionTaskDelegate {/// Result of a `URLAuthenticationChallenge` evaluation.typealias ChallengeEvaluation = (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?, error: AFError?)open func urlSession(_ session: URLSession,task: URLSessionTask,didReceive challenge: URLAuthenticationChallenge,completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {eventMonitor?.urlSession(session, task: task, didReceive: challenge)let evaluation: ChallengeEvaluationswitch challenge.protectionSpace.authenticationMethod {case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest, NSURLAuthenticationMethodNTLM,NSURLAuthenticationMethodNegotiate:evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task)#if !(os(Linux) || os(Windows))case NSURLAuthenticationMethodServerTrust:evaluation = attemptServerTrustAuthentication(with: challenge)case NSURLAuthenticationMethodClientCertificate:evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task)#endifdefault:evaluation = (.performDefaultHandling, nil, nil)}if let error = evaluation.error {stateProvider?.request(for: task)?.didFailTask(task, earlyWithError: error)}completionHandler(evaluation.disposition, evaluation.credential)}#if !(os(Linux) || os(Windows))/// Evaluates the server trust `URLAuthenticationChallenge` received.////// - Parameter challenge: The `URLAuthenticationChallenge`.////// - Returns: The `ChallengeEvaluation`.func attemptServerTrustAuthentication(with challenge: URLAuthenticationChallenge) -> ChallengeEvaluation {let host = challenge.protectionSpace.hostguard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,let trust = challenge.protectionSpace.serverTrustelse {return (.performDefaultHandling, nil, nil)}do {guard let evaluator = try stateProvider?.serverTrustManager?.serverTrustEvaluator(forHost: host) else {return (.performDefaultHandling, nil, nil)}try evaluator.evaluate(trust, forHost: host)return (.useCredential, URLCredential(trust: trust), nil)} catch {return (.cancelAuthenticationChallenge, nil, error.asAFError(or: .serverTrustEvaluationFailed(reason: .customEvaluationFailed(error: error))))}}#endif/// Evaluates the credential-based authentication `URLAuthenticationChallenge` received for `task`.////// - Parameters:/// - challenge: The `URLAuthenticationChallenge`./// - task: The `URLSessionTask` which received the challenge.////// - Returns: The `ChallengeEvaluation`.func attemptCredentialAuthentication(for challenge: URLAuthenticationChallenge,belongingTo task: URLSessionTask) -> ChallengeEvaluation {guard challenge.previousFailureCount == 0 else {return (.rejectProtectionSpace, nil, nil)}guard let credential = stateProvider?.credential(for: task, in: challenge.protectionSpace) else {return (.performDefaultHandling, nil, nil)}return (.useCredential, credential, nil)}open func urlSession(_ session: URLSession,task: URLSessionTask,didSendBodyData bytesSent: Int64,totalBytesSent: Int64,totalBytesExpectedToSend: Int64) {eventMonitor?.urlSession(session,task: task,didSendBodyData: bytesSent,totalBytesSent: totalBytesSent,totalBytesExpectedToSend: totalBytesExpectedToSend)stateProvider?.request(for: task)?.updateUploadProgress(totalBytesSent: totalBytesSent,totalBytesExpectedToSend: totalBytesExpectedToSend)}open func urlSession(_ session: URLSession,task: URLSessionTask,needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) {eventMonitor?.urlSession(session, taskNeedsNewBodyStream: task)guard let request = request(for: task, as: UploadRequest.self) else {assertionFailure("needNewBodyStream did not find UploadRequest.")completionHandler(nil)return}completionHandler(request.inputStream())}open func urlSession(_ session: URLSession,task: URLSessionTask,willPerformHTTPRedirection response: HTTPURLResponse,newRequest request: URLRequest,completionHandler: @escaping (URLRequest?) -> Void) {eventMonitor?.urlSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request)if let redirectHandler = stateProvider?.request(for: task)?.redirectHandler ?? stateProvider?.redirectHandler {redirectHandler.task(task, willBeRedirectedTo: request, for: response, completion: completionHandler)} else {completionHandler(request)}}open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {eventMonitor?.urlSession(session, task: task, didFinishCollecting: metrics)stateProvider?.request(for: task)?.didGatherMetrics(metrics)stateProvider?.didGatherMetricsForTask(task)}open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {eventMonitor?.urlSession(session, task: task, didCompleteWithError: error)let request = stateProvider?.request(for: task)stateProvider?.didCompleteTask(task) {request?.didCompleteTask(task, with: error.map { $0.asAFError(or: .sessionTaskFailed(error: $0)) })}}@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)open func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {eventMonitor?.urlSession(session, taskIsWaitingForConnectivity: task)}}// MARK: URLSessionDataDelegateextension SessionDelegate: URLSessionDataDelegate {open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data)if let request = request(for: dataTask, as: DataRequest.self) {request.didReceive(data: data)} else if let request = request(for: dataTask, as: DataStreamRequest.self) {request.didReceive(data: data)} else {assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive")return}}open func urlSession(_ session: URLSession,dataTask: URLSessionDataTask,willCacheResponse proposedResponse: CachedURLResponse,completionHandler: @escaping (CachedURLResponse?) -> Void) {eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse)if let handler = stateProvider?.request(for: dataTask)?.cachedResponseHandler ?? stateProvider?.cachedResponseHandler {handler.dataTask(dataTask, willCacheResponse: proposedResponse, completion: completionHandler)} else {completionHandler(proposedResponse)}}}// MARK: URLSessionDownloadDelegateextension SessionDelegate: URLSessionDownloadDelegate {open func urlSession(_ session: URLSession,downloadTask: URLSessionDownloadTask,didResumeAtOffset fileOffset: Int64,expectedTotalBytes: Int64) {eventMonitor?.urlSession(session,downloadTask: downloadTask,didResumeAtOffset: fileOffset,expectedTotalBytes: expectedTotalBytes)guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else {assertionFailure("downloadTask did not find DownloadRequest.")return}downloadRequest.updateDownloadProgress(bytesWritten: fileOffset,totalBytesExpectedToWrite: expectedTotalBytes)}open func urlSession(_ session: URLSession,downloadTask: URLSessionDownloadTask,didWriteData bytesWritten: Int64,totalBytesWritten: Int64,totalBytesExpectedToWrite: Int64) {eventMonitor?.urlSession(session,downloadTask: downloadTask,didWriteData: bytesWritten,totalBytesWritten: totalBytesWritten,totalBytesExpectedToWrite: totalBytesExpectedToWrite)guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else {assertionFailure("downloadTask did not find DownloadRequest.")return}downloadRequest.updateDownloadProgress(bytesWritten: bytesWritten,totalBytesExpectedToWrite: totalBytesExpectedToWrite)}open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {eventMonitor?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)guard let request = request(for: downloadTask, as: DownloadRequest.self) else {assertionFailure("downloadTask did not find DownloadRequest.")return}let (destination, options): (URL, DownloadRequest.Options)if let response = request.response {(destination, options) = request.destination(location, response)} else {// If there's no response this is likely a local file download, so generate the temporary URL directly.(destination, options) = (DownloadRequest.defaultDestinationURL(location), [])}eventMonitor?.request(request, didCreateDestinationURL: destination)do {if options.contains(.removePreviousFile), fileManager.fileExists(atPath: destination.path) {try fileManager.removeItem(at: destination)}if options.contains(.createIntermediateDirectories) {let directory = destination.deletingLastPathComponent()try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)}try fileManager.moveItem(at: location, to: destination)request.didFinishDownloading(using: downloadTask, with: .success(destination))} catch {request.didFinishDownloading(using: downloadTask, with: .failure(.downloadedFileMoveFailed(error: error,source: location,destination: destination)))}}}