SessionDelegate.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. //
  2. // SessionDelegate.swift
  3. //
  4. // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. //
  24. import Foundation
  25. open class SessionDelegate: NSObject {
  26. private(set) var requestTaskMap = RequestTaskMap()
  27. // TODO: Better way to connect delegate to manager, including queue?
  28. private weak var manager: SessionManager?
  29. private var eventMonitor: EventMonitor?
  30. private var queue: DispatchQueue? { return manager?.rootQueue }
  31. let startRequestsImmediately: Bool
  32. public init(startRequestsImmediately: Bool = true) {
  33. self.startRequestsImmediately = startRequestsImmediately
  34. }
  35. func didCreateSessionManager(_ manager: SessionManager, withEventMonitor eventMonitor: EventMonitor) {
  36. self.manager = manager
  37. self.eventMonitor = eventMonitor
  38. }
  39. func didCreateURLRequest(_ urlRequest: URLRequest, for request: Request) {
  40. guard let manager = manager else { fatalError("Received didCreateURLRequest but there is no manager.") }
  41. guard !request.isCancelled else { return }
  42. let task = request.task(for: urlRequest, using: manager.session)
  43. requestTaskMap[request] = task
  44. request.didCreateTask(task)
  45. resumeOrSuspendTask(task, ifNecessaryForRequest: request)
  46. }
  47. func didReceiveResumeData(_ data: Data, for request: DownloadRequest) {
  48. guard let manager = manager else { fatalError("Received didReceiveResumeData but there is no manager.") }
  49. guard !request.isCancelled else { return }
  50. let task = request.task(forResumeData: data, using: manager.session)
  51. requestTaskMap[request] = task
  52. request.didCreateTask(task)
  53. resumeOrSuspendTask(task, ifNecessaryForRequest: request)
  54. }
  55. func resumeOrSuspendTask(_ task: URLSessionTask, ifNecessaryForRequest request: Request) {
  56. if startRequestsImmediately || request.isResumed {
  57. task.resume()
  58. request.didResume()
  59. }
  60. if request.isSuspended {
  61. task.suspend()
  62. request.didSuspend()
  63. }
  64. }
  65. }
  66. extension SessionDelegate: RequestDelegate {
  67. public var sessionConfiguration: URLSessionConfiguration {
  68. guard let manager = manager else { fatalError("Attempted to access sessionConfiguration without a manager.") }
  69. return manager.session.configuration
  70. }
  71. public func isRetryingRequest(_ request: Request, ifNecessaryWithError error: Error) -> Bool {
  72. guard let manager = manager, let retrier = manager.retrier else { return false }
  73. retrier.should(manager, retry: request, with: error) { (shouldRetry, retryInterval) in
  74. guard !request.isCancelled else { return }
  75. self.queue?.async {
  76. guard shouldRetry else {
  77. request.finish()
  78. return
  79. }
  80. self.queue?.after(retryInterval) {
  81. guard !request.isCancelled else { return }
  82. self.eventMonitor?.requestIsRetrying(request)
  83. self.manager?.perform(request)
  84. }
  85. }
  86. }
  87. return true
  88. }
  89. public func cancelRequest(_ request: Request) {
  90. queue?.async {
  91. guard let task = self.requestTaskMap[request] else {
  92. request.didCancel()
  93. request.finish()
  94. return
  95. }
  96. request.didCancel()
  97. task.cancel()
  98. }
  99. }
  100. public func cancelDownloadRequest(_ request: DownloadRequest, byProducingResumeData: @escaping (Data?) -> Void) {
  101. queue?.async {
  102. guard let downloadTask = self.requestTaskMap[request] as? URLSessionDownloadTask else {
  103. request.didCancel()
  104. request.finish()
  105. return
  106. }
  107. downloadTask.cancel { (data) in
  108. self.queue?.async {
  109. byProducingResumeData(data)
  110. request.didCancel()
  111. }
  112. }
  113. }
  114. }
  115. public func suspendRequest(_ request: Request) {
  116. queue?.async {
  117. defer { request.didSuspend() }
  118. guard !request.isCancelled, let task = self.requestTaskMap[request] else { return }
  119. task.suspend()
  120. }
  121. }
  122. public func resumeRequest(_ request: Request) {
  123. queue?.async {
  124. defer { request.didResume() }
  125. guard !request.isCancelled, let task = self.requestTaskMap[request] else { return }
  126. task.resume()
  127. }
  128. }
  129. }
  130. extension SessionDelegate: URLSessionDelegate {
  131. open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
  132. eventMonitor?.urlSession(session, didBecomeInvalidWithError: error)
  133. }
  134. }
  135. extension SessionDelegate: URLSessionTaskDelegate {
  136. // Auth challenge, will be received always since the URLSessionDelegate method isn't implemented.
  137. typealias ChallengeEvaluation = (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?, error: Error?)
  138. open func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
  139. eventMonitor?.urlSession(session, task: task, didReceive: challenge)
  140. let evaluation: ChallengeEvaluation
  141. switch challenge.protectionSpace.authenticationMethod {
  142. case NSURLAuthenticationMethodServerTrust:
  143. evaluation = attemptServerTrustAuthentication(with: challenge)
  144. case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest:
  145. evaluation = attemptHTTPAuthentication(for: challenge, belongingTo: task)
  146. // TODO: Error explaining AF doesn't support client certificates?
  147. // case NSURLAuthenticationMethodClientCertificate:
  148. default:
  149. evaluation = (.performDefaultHandling, nil, nil)
  150. }
  151. if let error = evaluation.error {
  152. requestTaskMap[task]?.didFailTask(task, earlyWithError: error)
  153. }
  154. completionHandler(evaluation.disposition, evaluation.credential)
  155. }
  156. func attemptServerTrustAuthentication(with challenge: URLAuthenticationChallenge) -> ChallengeEvaluation {
  157. let host = challenge.protectionSpace.host
  158. guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
  159. let evaluator = manager?.serverTrustManager?.serverTrustEvaluators(forHost: host),
  160. let serverTrust = challenge.protectionSpace.serverTrust
  161. else {
  162. return (.performDefaultHandling, nil, nil)
  163. }
  164. guard evaluator.evaluate(serverTrust, forHost: host) else {
  165. let error = AFError.certificatePinningFailed
  166. return (.cancelAuthenticationChallenge, nil, error)
  167. }
  168. return (.useCredential, URLCredential(trust: serverTrust), nil)
  169. }
  170. func attemptHTTPAuthentication(for challenge: URLAuthenticationChallenge, belongingTo task: URLSessionTask) -> ChallengeEvaluation {
  171. guard challenge.previousFailureCount == 0 else {
  172. return (.rejectProtectionSpace, nil, nil)
  173. }
  174. guard let credential = requestTaskMap[task]?.credential ??
  175. manager?.session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace) else {
  176. return (.performDefaultHandling, nil, nil)
  177. }
  178. return (.useCredential, credential, nil)
  179. }
  180. open func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
  181. eventMonitor?.urlSession(session,
  182. task: task,
  183. didSendBodyData: bytesSent,
  184. totalBytesSent: totalBytesSent,
  185. totalBytesExpectedToSend: totalBytesExpectedToSend)
  186. requestTaskMap[task]?.updateUploadProgress(totalBytesSent: totalBytesSent,
  187. totalBytesExpectedToSend: totalBytesExpectedToSend)
  188. }
  189. open func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) {
  190. eventMonitor?.urlSession(session, taskNeedsNewBodyStream: task)
  191. guard let request = requestTaskMap[task] as? UploadRequest else {
  192. fatalError("needNewBodyStream for request that isn't UploadRequest.")
  193. }
  194. completionHandler(request.inputStream())
  195. }
  196. open func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
  197. eventMonitor?.urlSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request)
  198. completionHandler(request)
  199. }
  200. open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
  201. eventMonitor?.urlSession(session, task: task, didFinishCollecting: metrics)
  202. requestTaskMap[task]?.didGatherMetrics(metrics)
  203. }
  204. // Task finished transferring data or had a client error.
  205. open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
  206. eventMonitor?.urlSession(session, task: task, didCompleteWithError: error)
  207. requestTaskMap[task]?.didCompleteTask(task, with: error)
  208. requestTaskMap[task] = nil
  209. }
  210. // Only used when background sessions are resuming a delayed task.
  211. // func urlSession(_ session: URLSession, task: URLSessionTask, willBeginDelayedRequest request: URLRequest, completionHandler: @escaping (URLSession.DelayedRequestDisposition, URLRequest?) -> Void) {
  212. //
  213. // }
  214. // This method is called if the waitsForConnectivity property of URLSessionConfiguration is true, and sufficient
  215. // connectivity is unavailable. The delegate can use this opportunity to update the user interface; for example, by
  216. // presenting an offline mode or a cellular-only mode.
  217. //
  218. // This method is called, at most, once per task, and only if connectivity is initially unavailable. It is never
  219. // called for background sessions because waitsForConnectivity is ignored for those sessions.
  220. @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)
  221. open func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
  222. eventMonitor?.urlSession(session, taskIsWaitingForConnectivity: task)
  223. // Post Notification?
  224. // Update Request state?
  225. // Only once? How to know when it's done waiting and resumes the task?
  226. }
  227. }
  228. extension SessionDelegate: URLSessionDataDelegate {
  229. open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
  230. eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data)
  231. // TODO: UploadRequest will need this too, only works now because it's a subclass.
  232. guard let request = requestTaskMap[dataTask] as? DataRequest else {
  233. fatalError("dataTask received data for incorrect Request subclass: \(String(describing: requestTaskMap[dataTask]))")
  234. }
  235. request.didRecieve(data: data)
  236. }
  237. open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) {
  238. eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse)
  239. completionHandler(proposedResponse)
  240. }
  241. }
  242. extension SessionDelegate: URLSessionDownloadDelegate {
  243. open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
  244. eventMonitor?.urlSession(session,
  245. downloadTask: downloadTask,
  246. didResumeAtOffset: fileOffset,
  247. expectedTotalBytes: expectedTotalBytes)
  248. guard let downloadRequest = requestTaskMap[downloadTask] as? DownloadRequest else {
  249. fatalError("No DownloadRequest found for downloadTask: \(downloadTask)")
  250. }
  251. downloadRequest.updateDownloadProgress(bytesWritten: fileOffset,
  252. totalBytesExpectedToWrite: expectedTotalBytes)
  253. }
  254. open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
  255. eventMonitor?.urlSession(session,
  256. downloadTask: downloadTask,
  257. didWriteData: bytesWritten,
  258. totalBytesWritten: totalBytesWritten,
  259. totalBytesExpectedToWrite: totalBytesExpectedToWrite)
  260. guard let downloadRequest = requestTaskMap[downloadTask] as? DownloadRequest else {
  261. fatalError("No DownloadRequest found for downloadTask: \(downloadTask)")
  262. }
  263. downloadRequest.updateDownloadProgress(bytesWritten: bytesWritten,
  264. totalBytesExpectedToWrite: totalBytesExpectedToWrite)
  265. }
  266. open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
  267. eventMonitor?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
  268. guard let request = requestTaskMap[downloadTask] as? DownloadRequest else {
  269. fatalError("download finished but either no request found or request wasn't DownloadRequest")
  270. }
  271. request.didComplete(task: downloadTask, with: location)
  272. }
  273. }