| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- //
- // 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
- open class SessionDelegate: NSObject {
- private(set) var requestTaskMap = RequestTaskMap()
- // TODO: Better way to connect delegate to manager, including queue?
- private weak var manager: SessionManager?
- private var eventMonitor: EventMonitor?
- private var queue: DispatchQueue? { return manager?.rootQueue }
- let startRequestsImmediately: Bool
- public init(startRequestsImmediately: Bool = true) {
- self.startRequestsImmediately = startRequestsImmediately
- }
- func didCreateSessionManager(_ manager: SessionManager, withEventMonitor eventMonitor: EventMonitor) {
- self.manager = manager
- self.eventMonitor = eventMonitor
- }
- func didCreateURLRequest(_ urlRequest: URLRequest, for request: Request) {
- guard let manager = manager else { fatalError("Received didCreateURLRequest but there is no manager.") }
- guard !request.isCancelled else { return }
- let task = request.task(for: urlRequest, using: manager.session)
- requestTaskMap[request] = task
- request.didCreateTask(task)
- resumeOrSuspendTask(task, ifNecessaryForRequest: request)
- }
- func didReceiveResumeData(_ data: Data, for request: DownloadRequest) {
- guard let manager = manager else { fatalError("Received didReceiveResumeData but there is no manager.") }
- guard !request.isCancelled else { return }
- let task = request.task(forResumeData: data, using: manager.session)
- requestTaskMap[request] = task
- request.didCreateTask(task)
- resumeOrSuspendTask(task, ifNecessaryForRequest: request)
- }
- func resumeOrSuspendTask(_ task: URLSessionTask, ifNecessaryForRequest request: Request) {
- if startRequestsImmediately || request.isResumed {
- task.resume()
- request.didResume()
- }
- if request.isSuspended {
- task.suspend()
- request.didSuspend()
- }
- }
- }
- extension SessionDelegate: RequestDelegate {
- public var sessionConfiguration: URLSessionConfiguration {
- guard let manager = manager else { fatalError("Attempted to access sessionConfiguration without a manager.") }
-
- return manager.session.configuration
- }
-
- public func isRetryingRequest(_ request: Request, ifNecessaryWithError error: Error) -> Bool {
- guard let manager = manager, let retrier = manager.retrier else { return false }
- retrier.should(manager, retry: request, with: error) { (shouldRetry, retryInterval) in
- guard !request.isCancelled else { return }
- self.queue?.async {
- guard shouldRetry else {
- request.finish()
- return
- }
- self.queue?.after(retryInterval) {
- guard !request.isCancelled else { return }
- self.eventMonitor?.requestIsRetrying(request)
- self.manager?.perform(request)
- }
- }
- }
- return true
- }
- public func cancelRequest(_ request: Request) {
- queue?.async {
- guard let task = self.requestTaskMap[request] else {
- request.didCancel()
- request.finish()
- return
- }
- request.didCancel()
- task.cancel()
- }
- }
- public func cancelDownloadRequest(_ request: DownloadRequest, byProducingResumeData: @escaping (Data?) -> Void) {
- queue?.async {
- guard let downloadTask = self.requestTaskMap[request] as? URLSessionDownloadTask else {
- request.didCancel()
- request.finish()
- return
- }
- downloadTask.cancel { (data) in
- self.queue?.async {
- byProducingResumeData(data)
- request.didCancel()
- }
- }
- }
- }
- public func suspendRequest(_ request: Request) {
- queue?.async {
- defer { request.didSuspend() }
- guard !request.isCancelled, let task = self.requestTaskMap[request] else { return }
- task.suspend()
- }
- }
- public func resumeRequest(_ request: Request) {
- queue?.async {
- defer { request.didResume() }
- guard !request.isCancelled, let task = self.requestTaskMap[request] else { return }
- task.resume()
- }
- }
- }
- extension SessionDelegate: URLSessionDelegate {
- open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
- eventMonitor?.urlSession(session, didBecomeInvalidWithError: error)
- }
- }
- extension SessionDelegate: URLSessionTaskDelegate {
- // Auth challenge, will be received always since the URLSessionDelegate method isn't implemented.
- typealias ChallengeEvaluation = (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?, error: Error?)
- 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: ChallengeEvaluation
- switch challenge.protectionSpace.authenticationMethod {
- case NSURLAuthenticationMethodServerTrust:
- evaluation = attemptServerTrustAuthentication(with: challenge)
- case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest:
- evaluation = attemptHTTPAuthentication(for: challenge, belongingTo: task)
- // TODO: Error explaining AF doesn't support client certificates?
- // case NSURLAuthenticationMethodClientCertificate:
- default:
- evaluation = (.performDefaultHandling, nil, nil)
- }
- if let error = evaluation.error {
- requestTaskMap[task]?.didFailTask(task, earlyWithError: error)
- }
- completionHandler(evaluation.disposition, evaluation.credential)
- }
- func attemptServerTrustAuthentication(with challenge: URLAuthenticationChallenge) -> ChallengeEvaluation {
- let host = challenge.protectionSpace.host
- guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
- let evaluator = manager?.serverTrustManager?.serverTrustEvaluators(forHost: host),
- let serverTrust = challenge.protectionSpace.serverTrust
- else {
- return (.performDefaultHandling, nil, nil)
- }
- guard evaluator.evaluate(serverTrust, forHost: host) else {
- let error = AFError.certificatePinningFailed
- return (.cancelAuthenticationChallenge, nil, error)
- }
- return (.useCredential, URLCredential(trust: serverTrust), nil)
- }
- func attemptHTTPAuthentication(for challenge: URLAuthenticationChallenge, belongingTo task: URLSessionTask) -> ChallengeEvaluation {
- guard challenge.previousFailureCount == 0 else {
- return (.rejectProtectionSpace, nil, nil)
- }
- guard let credential = requestTaskMap[task]?.credential ??
- manager?.session.configuration.urlCredentialStorage?.defaultCredential(for: 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)
- requestTaskMap[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 = requestTaskMap[task] as? UploadRequest else {
- fatalError("needNewBodyStream for request that isn't UploadRequest.")
- }
- 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)
- completionHandler(request)
- }
- open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
- eventMonitor?.urlSession(session, task: task, didFinishCollecting: metrics)
- requestTaskMap[task]?.didGatherMetrics(metrics)
- }
- // Task finished transferring data or had a client error.
- open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
- eventMonitor?.urlSession(session, task: task, didCompleteWithError: error)
- requestTaskMap[task]?.didCompleteTask(task, with: error)
- requestTaskMap[task] = nil
- }
- // Only used when background sessions are resuming a delayed task.
- // func urlSession(_ session: URLSession, task: URLSessionTask, willBeginDelayedRequest request: URLRequest, completionHandler: @escaping (URLSession.DelayedRequestDisposition, URLRequest?) -> Void) {
- //
- // }
- // This method is called if the waitsForConnectivity property of URLSessionConfiguration is true, and sufficient
- // connectivity is unavailable. The delegate can use this opportunity to update the user interface; for example, by
- // presenting an offline mode or a cellular-only mode.
- //
- // This method is called, at most, once per task, and only if connectivity is initially unavailable. It is never
- // called for background sessions because waitsForConnectivity is ignored for those sessions.
- @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)
- // Post Notification?
- // Update Request state?
- // Only once? How to know when it's done waiting and resumes the task?
- }
- }
- extension SessionDelegate: URLSessionDataDelegate {
- open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
- eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data)
- // TODO: UploadRequest will need this too, only works now because it's a subclass.
- guard let request = requestTaskMap[dataTask] as? DataRequest else {
- fatalError("dataTask received data for incorrect Request subclass: \(String(describing: requestTaskMap[dataTask]))")
- }
- request.didRecieve(data: data)
- }
- open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) {
- eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse)
- completionHandler(proposedResponse)
- }
- }
- extension 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 = requestTaskMap[downloadTask] as? DownloadRequest else {
- fatalError("No DownloadRequest found for downloadTask: \(downloadTask)")
- }
- 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 = requestTaskMap[downloadTask] as? DownloadRequest else {
- fatalError("No DownloadRequest found for downloadTask: \(downloadTask)")
- }
- 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 = requestTaskMap[downloadTask] as? DownloadRequest else {
- fatalError("download finished but either no request found or request wasn't DownloadRequest")
- }
- request.didComplete(task: downloadTask, with: location)
- }
- }
|