SessionDelegate.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  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. // TODO: Investigate queueing active tasks?
  27. private(set) var requestTaskMap = RequestTaskMap()
  28. // TODO: Better way to connect delegate to manager, including queue?
  29. private weak var manager: SessionManager?
  30. private var eventMonitor: EventMonitor?
  31. private var queue: DispatchQueue? { return manager?.rootQueue }
  32. let startRequestsImmediately: Bool
  33. public init(startRequestsImmediately: Bool = true) {
  34. self.startRequestsImmediately = startRequestsImmediately
  35. }
  36. func didCreateSessionManager(_ manager: SessionManager, withEventMonitor eventMonitor: EventMonitor) {
  37. self.manager = manager
  38. self.eventMonitor = eventMonitor
  39. }
  40. func didCreateURLRequest(_ urlRequest: URLRequest, for request: Request) {
  41. guard let manager = manager else { fatalError("Received didCreateURLRequest but there is no manager.") }
  42. guard !request.isCancelled else { return }
  43. let task = request.task(for: urlRequest, using: manager.session)
  44. requestTaskMap[request] = task
  45. request.didCreateTask(task)
  46. resumeOrSuspendTask(task, ifNecessaryForRequest: request)
  47. }
  48. func didReceiveResumeData(_ data: Data, for request: DownloadRequest) {
  49. guard let manager = manager else { fatalError("Received didReceiveResumeData but there is no manager.") }
  50. guard !request.isCancelled else { return }
  51. let task = request.task(forResumeData: data, using: manager.session)
  52. requestTaskMap[request] = task
  53. request.didCreateTask(task)
  54. resumeOrSuspendTask(task, ifNecessaryForRequest: request)
  55. }
  56. func resumeOrSuspendTask(_ task: URLSessionTask, ifNecessaryForRequest request: Request) {
  57. if startRequestsImmediately || request.isResumed {
  58. task.resume()
  59. request.didResume()
  60. }
  61. if request.isSuspended {
  62. task.suspend()
  63. request.didSuspend()
  64. }
  65. }
  66. }
  67. extension SessionDelegate: RequestDelegate {
  68. func isRetryingRequest(_ request: Request, ifNecessaryWithError error: Error) -> Bool {
  69. guard let manager = manager, let retrier = manager.retrier else { return false }
  70. retrier.should(manager, retry: request, with: error) { (shouldRetry, retryInterval) in
  71. guard !request.isCancelled else { return }
  72. self.queue?.async {
  73. guard shouldRetry else {
  74. request.finish()
  75. return
  76. }
  77. self.queue?.after(retryInterval) {
  78. guard !request.isCancelled else { return }
  79. self.manager?.perform(request)
  80. }
  81. }
  82. }
  83. return true
  84. }
  85. func cancelRequest(_ request: Request) {
  86. queue?.async {
  87. guard let task = self.requestTaskMap[request] else {
  88. request.didCancel()
  89. request.finish()
  90. return
  91. }
  92. request.didCancel()
  93. task.cancel()
  94. }
  95. }
  96. func cancelDownloadRequest(_ request: DownloadRequest, byProducingResumeData: @escaping (Data?) -> Void) {
  97. queue?.async {
  98. guard let downloadTask = self.requestTaskMap[request] as? URLSessionDownloadTask else {
  99. request.didCancel()
  100. request.finish()
  101. return
  102. }
  103. downloadTask.cancel { (data) in
  104. self.queue?.async {
  105. byProducingResumeData(data)
  106. request.didCancel()
  107. }
  108. }
  109. }
  110. }
  111. func suspendRequest(_ request: Request) {
  112. queue?.async {
  113. defer { request.didSuspend() }
  114. guard !request.isCancelled, let task = self.requestTaskMap[request] else { return }
  115. task.suspend()
  116. }
  117. }
  118. func resumeRequest(_ request: Request) {
  119. queue?.async {
  120. defer { request.didResume() }
  121. guard !request.isCancelled, let task = self.requestTaskMap[request] else { return }
  122. task.resume()
  123. }
  124. }
  125. }
  126. extension SessionDelegate: URLSessionDelegate {
  127. open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
  128. eventMonitor?.urlSession(session, didBecomeInvalidWithError: error)
  129. }
  130. }
  131. extension SessionDelegate: URLSessionTaskDelegate {
  132. // Auth challenge, will be received always since the URLSessionDelegate method isn't implemented.
  133. typealias ChallengeEvaluation = (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?, error: Error?)
  134. open func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
  135. eventMonitor?.urlSession(session, task: task, didReceive: challenge)
  136. let evaluation: ChallengeEvaluation
  137. switch challenge.protectionSpace.authenticationMethod {
  138. case NSURLAuthenticationMethodServerTrust:
  139. evaluation = attemptServerTrustAuthentication(with: challenge)
  140. case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest:
  141. evaluation = attemptHTTPAuthentication(for: challenge, belongingTo: task)
  142. // TODO: Error explaining AF doesn't support client certificates?
  143. // case NSURLAuthenticationMethodClientCertificate:
  144. default:
  145. evaluation = (.performDefaultHandling, nil, nil)
  146. }
  147. if let error = evaluation.error {
  148. requestTaskMap[task]?.didFailTask(task, earlyWithError: error)
  149. }
  150. completionHandler(evaluation.disposition, evaluation.credential)
  151. }
  152. func attemptServerTrustAuthentication(with challenge: URLAuthenticationChallenge) -> ChallengeEvaluation {
  153. let host = challenge.protectionSpace.host
  154. guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
  155. let evaluator = manager?.serverTrustManager?.serverTrustEvaluators(forHost: host),
  156. let serverTrust = challenge.protectionSpace.serverTrust
  157. else {
  158. return (.performDefaultHandling, nil, nil)
  159. }
  160. guard evaluator.evaluate(serverTrust, forHost: host) else {
  161. let error = AFError.certificatePinningFailed
  162. return (.cancelAuthenticationChallenge, nil, error)
  163. }
  164. return (.useCredential, URLCredential(trust: serverTrust), nil)
  165. }
  166. func attemptHTTPAuthentication(for challenge: URLAuthenticationChallenge, belongingTo task: URLSessionTask) -> ChallengeEvaluation {
  167. // TODO: Consider custom error, depending on error we get from session.
  168. guard challenge.previousFailureCount == 0 else {
  169. return (.rejectProtectionSpace, nil, nil)
  170. }
  171. // TODO: Get credential from session's configuration's defaultCredential too.
  172. guard let credential = requestTaskMap[task]?.credential else {
  173. return (.performDefaultHandling, nil, nil)
  174. }
  175. return (.useCredential, credential, nil)
  176. }
  177. // Progress of sending the body data.
  178. open func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
  179. eventMonitor?.urlSession(session,
  180. task: task,
  181. didSendBodyData: bytesSent,
  182. totalBytesSent: totalBytesSent,
  183. totalBytesExpectedToSend: totalBytesExpectedToSend)
  184. requestTaskMap[task]?.updateUploadProgress(totalBytesSent: totalBytesSent,
  185. totalBytesExpectedToSend: totalBytesExpectedToSend)
  186. // if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
  187. // NSLog("URLSession: \(session), task: \(task), progress: \(task.progress)")
  188. // }
  189. }
  190. // This delegate method is called under two circumstances:
  191. // To provide the initial request body stream if the task was created with uploadTaskWithStreamedRequest:
  192. //To provide a replacement request body stream if the task needs to resend a request that has a body stream because of an authentication challenge or other recoverable server error.
  193. // You do not need to implement this if your code provides the request body using a file URL or an NSData object.
  194. // Don't enable if streamed bodies aren't supported.
  195. open func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) {
  196. eventMonitor?.urlSession(session, taskNeedsNewBodyStream: task)
  197. guard let request = requestTaskMap[task] as? UploadRequest else {
  198. fatalError("needNewBodyStream for request that isn't UploadRequest.")
  199. }
  200. completionHandler(request.inputStream())
  201. }
  202. // This method is called only for tasks in default and ephemeral sessions. Tasks in background sessions automatically follow redirects.
  203. // Only code should be customization closure?
  204. open func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
  205. eventMonitor?.urlSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request)
  206. completionHandler(request)
  207. }
  208. open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
  209. eventMonitor?.urlSession(session, task: task, didFinishCollecting: metrics)
  210. requestTaskMap[task]?.didGatherMetrics(metrics)
  211. }
  212. // Task finished transferring data or had a client error.
  213. open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
  214. eventMonitor?.urlSession(session, task: task, didCompleteWithError: error)
  215. requestTaskMap[task]?.didCompleteTask(task, with: error)
  216. requestTaskMap[task] = nil
  217. }
  218. // Only used when background sessions are resuming a delayed task.
  219. // func urlSession(_ session: URLSession, task: URLSessionTask, willBeginDelayedRequest request: URLRequest, completionHandler: @escaping (URLSession.DelayedRequestDisposition, URLRequest?) -> Void) {
  220. //
  221. // }
  222. // This method is called if the waitsForConnectivity property of URLSessionConfiguration is true, and sufficient
  223. // connectivity is unavailable. The delegate can use this opportunity to update the user interface; for example, by
  224. // presenting an offline mode or a cellular-only mode.
  225. //
  226. // This method is called, at most, once per task, and only if connectivity is initially unavailable. It is never
  227. // called for background sessions because waitsForConnectivity is ignored for those sessions.
  228. @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)
  229. open func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
  230. eventMonitor?.urlSession(session, taskIsWaitingForConnectivity: task)
  231. // Post Notification?
  232. // Update Request state?
  233. // Only once? How to know when it's done waiting and resumes the task?
  234. }
  235. }
  236. extension SessionDelegate: URLSessionDataDelegate {
  237. // This method is optional unless you need to support the (relatively obscure) multipart/x-mixed-replace content type.
  238. // With that content type, the server sends a series of parts, each of which is intended to replace the previous part.
  239. // The session calls this method at the beginning of each part, and you should then display, discard, or otherwise process the previous part, as appropriate.
  240. // Don't support?
  241. // func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
  242. // NSLog("URLSession: \(session), dataTask: \(dataTask), didReceive: \(response)")
  243. //
  244. // completionHandler(.allow)
  245. // }
  246. // Only called if didReceiveResponse is called.
  247. // func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecome downloadTask: URLSessionDownloadTask) {
  248. // NSLog("URLSession: \(session), dataTask: \(dataTask), didBecomeDownloadTask")
  249. // }
  250. // Only called if didReceiveResponse is called.
  251. // func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecome streamTask: URLSessionStreamTask) {
  252. // NSLog("URLSession: \(session), dataTask: \(dataTask), didBecomeStreamTask: \(streamTask)")
  253. // }
  254. // Called, possibly more than once, to accumulate the data for a response.
  255. open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
  256. eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data)
  257. // TODO: UploadRequest will need this too, only works now because it's a subclass.
  258. guard let request = requestTaskMap[dataTask] as? DataRequest else {
  259. fatalError("dataTask received data for incorrect Request subclass: \(String(describing: requestTaskMap[dataTask]))")
  260. }
  261. request.didRecieve(data: data)
  262. // Update Request progress?
  263. }
  264. // The session calls this delegate method after the task finishes receiving all of the expected data. If you do not implement this method, the default behavior is to use the caching policy specified in the session’s configuration object. The primary purpose of this method is to prevent caching of specific URLs or to modify the userInfo dictionary associated with the URL response.
  265. //
  266. // This method is called only if the NSURLProtocol handling the request decides to cache the response. As a rule, responses are cached only when all of the following are true:
  267. //
  268. // The request is for an HTTP or HTTPS URL (or your own custom networking protocol that supports caching).
  269. //
  270. // The request was successful (with a status code in the 200–299 range).
  271. //
  272. // The provided response came from the server, rather than out of the cache.
  273. //
  274. // The session configuration’s cache policy allows caching.
  275. //
  276. // The provided NSURLRequest object's cache policy (if applicable) allows caching.
  277. //
  278. // The cache-related headers in the server’s response (if present) allow caching.
  279. //
  280. // The response size is small enough to reasonably fit within the cache. (For example, if you provide a disk cache, the response must be no larger than about 5% of the disk cache size.)
  281. // Only for customization of caching?
  282. open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) {
  283. eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse)
  284. completionHandler(proposedResponse)
  285. }
  286. }
  287. extension SessionDelegate: URLSessionDownloadDelegate {
  288. // Indicates resume data was used to start a download task. Use for ?
  289. open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
  290. eventMonitor?.urlSession(session,
  291. downloadTask: downloadTask,
  292. didResumeAtOffset: fileOffset,
  293. expectedTotalBytes: expectedTotalBytes)
  294. guard let downloadRequest = requestTaskMap[downloadTask] as? DownloadRequest else {
  295. fatalError("No DownloadRequest found for downloadTask: \(downloadTask)")
  296. }
  297. downloadRequest.updateDownloadProgress(bytesWritten: fileOffset,
  298. totalBytesExpectedToWrite: expectedTotalBytes)
  299. }
  300. // Download progress, as provided by the `Content-Length` header. `totalBytesExpectedToWrite` will be `NSURLSessionTransferSizeUnknown` when there's no header.
  301. open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
  302. eventMonitor?.urlSession(session,
  303. downloadTask: downloadTask,
  304. didWriteData: bytesWritten,
  305. totalBytesWritten: totalBytesWritten,
  306. totalBytesExpectedToWrite: totalBytesExpectedToWrite)
  307. guard let downloadRequest = requestTaskMap[downloadTask] as? DownloadRequest else {
  308. fatalError("No DownloadRequest found for downloadTask: \(downloadTask)")
  309. }
  310. downloadRequest.updateDownloadProgress(bytesWritten: bytesWritten,
  311. totalBytesExpectedToWrite: totalBytesExpectedToWrite)
  312. }
  313. // When finished, open for reading or move the file.
  314. // A file URL for the temporary file. Because the file is temporary, you must either open the file for reading or
  315. // move it to a permanent location in your app’s sandbox container directory before returning from this delegate
  316. // method.
  317. //
  318. // If you choose to open the file for reading, you should do the actual reading in another thread to avoid blocking
  319. // the delegate queue.
  320. open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
  321. eventMonitor?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
  322. guard let request = requestTaskMap[downloadTask] as? DownloadRequest else {
  323. fatalError("download finished but either no request found or request wasn't DownloadRequest")
  324. }
  325. request.didComplete(task: downloadTask, with: location)
  326. }
  327. }