Request.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. //
  2. // Request.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. protocol RequestDelegate: AnyObject {
  26. func isRetryingRequest(_ request: Request, ifNecessaryWithError error: Error) -> Bool
  27. func cancelRequest(_ request: Request)
  28. func cancelDownloadRequest(_ request: DownloadRequest, byProducingResumeData: @escaping (Data?) -> Void)
  29. func suspendRequest(_ request: Request)
  30. func resumeRequest(_ request: Request)
  31. }
  32. open class Request {
  33. /// A closure executed when monitoring upload or download progress of a request.
  34. public typealias ProgressHandler = (Progress) -> Void
  35. // TODO: Make publicly readable properties protected?
  36. public enum State {
  37. case initialized, resumed, suspended, cancelled
  38. func canTransitionTo(_ state: State) -> Bool {
  39. switch (self, state) {
  40. case (.initialized, _): return true
  41. case (_, .initialized), (.cancelled, _): return false
  42. case (.resumed, .cancelled), (.suspended, .cancelled),
  43. (.resumed, .suspended), (.suspended, .resumed): return true
  44. case (.suspended, .suspended), (.resumed, .resumed): return false
  45. }
  46. }
  47. }
  48. // MARK: - Initial State
  49. let id: UUID
  50. let underlyingQueue: DispatchQueue
  51. let serializationQueue: DispatchQueue
  52. let eventMonitor: RequestEventMonitor?
  53. weak var delegate: RequestDelegate?
  54. // TODO: Do we still want to expose the queue(s?) as public API?
  55. open let internalQueue: OperationQueue
  56. // MARK: - Updated State
  57. let uploadProgress = Progress()
  58. let downloadProgress = Progress()
  59. var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)?
  60. var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)?
  61. private var protectedState: Protector<State> = Protector(.initialized)
  62. public fileprivate(set) var state: State {
  63. get { return protectedState.directValue }
  64. set { protectedState.directValue = newValue }
  65. }
  66. public var isCancelled: Bool { return state == .cancelled }
  67. public var isResumed: Bool { return state == .resumed }
  68. public var isSuspended: Bool { return state == .suspended }
  69. public var isInitialized: Bool { return state == .initialized }
  70. public var retryCount: Int { return protectedTasks.read { max($0.count - 1, 0) } }
  71. private(set) var initialRequest: URLRequest?
  72. open var request: URLRequest? {
  73. return finalTask?.currentRequest
  74. }
  75. open var response: HTTPURLResponse? {
  76. return finalTask?.response as? HTTPURLResponse
  77. }
  78. private(set) var metrics: URLSessionTaskMetrics?
  79. // TODO: How to expose task progress on iOS 11?
  80. private var protectedTasks = Protector<[URLSessionTask]>([])
  81. public var tasks: [URLSessionTask] {
  82. get { return protectedTasks.directValue }
  83. }
  84. public var initialTask: URLSessionTask? { return tasks.first }
  85. public var finalTask: URLSessionTask? { return tasks.last }
  86. public var task: URLSessionTask? { return finalTask }
  87. fileprivate(set) public var error: Error?
  88. private(set) var credential: URLCredential?
  89. fileprivate(set) var validators: [() -> Void] = []
  90. init(id: UUID = UUID(),
  91. underlyingQueue: DispatchQueue,
  92. serializationQueue: DispatchQueue? = nil,
  93. eventMonitor: RequestEventMonitor?,
  94. delegate: RequestDelegate) {
  95. self.id = id
  96. self.underlyingQueue = underlyingQueue
  97. self.serializationQueue = serializationQueue ?? underlyingQueue
  98. internalQueue = OperationQueue(maxConcurrentOperationCount: 1,
  99. underlyingQueue: underlyingQueue,
  100. name: "org.alamofire.request-\(id)",
  101. startSuspended: true)
  102. self.eventMonitor = eventMonitor
  103. self.delegate = delegate
  104. }
  105. // MARK: - Internal API
  106. // Called from internal queue.
  107. func didCreateURLRequest(_ request: URLRequest) {
  108. initialRequest = request
  109. eventMonitor?.request(self, didCreateURLRequest: request)
  110. }
  111. func didFailToCreateURLRequest(with error: Error) {
  112. self.error = error
  113. eventMonitor?.request(self, didFailToCreateURLRequestWithError: error)
  114. retryOrFinish(error: error)
  115. }
  116. func didAdaptInitialRequest(_ initialRequest: URLRequest, to adaptedRequest: URLRequest) {
  117. self.initialRequest = adaptedRequest
  118. // Set initialRequest or something else?
  119. eventMonitor?.request(self, didAdaptInitialRequest: initialRequest, to: adaptedRequest)
  120. }
  121. func didFailToAdaptURLRequest(_ request: URLRequest, withError error: Error) {
  122. self.error = error
  123. eventMonitor?.request(self, didFailToAdaptURLRequest: request, withError: error)
  124. retryOrFinish(error: error)
  125. }
  126. func didCreateTask(_ task: URLSessionTask) {
  127. protectedTasks.append(task)
  128. // TODO: Reset behavior?
  129. reset()
  130. eventMonitor?.request(self, didCreateTask: task)
  131. }
  132. func updateUploadProgress(totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
  133. uploadProgress.totalUnitCount = totalBytesExpectedToSend
  134. uploadProgress.completedUnitCount = totalBytesSent
  135. uploadProgressHandler?.queue.async { self.uploadProgressHandler?.handler(self.uploadProgress) }
  136. }
  137. // Resets task related state
  138. func reset() {
  139. error = nil
  140. uploadProgress.totalUnitCount = 0
  141. uploadProgress.completedUnitCount = 0
  142. downloadProgress.totalUnitCount = 0
  143. downloadProgress.completedUnitCount = 0
  144. }
  145. func didResume() {
  146. eventMonitor?.requestDidResume(self)
  147. }
  148. func didSuspend() {
  149. eventMonitor?.requestDidSuspend(self)
  150. }
  151. func didCancel() {
  152. error = AFError.explicitlyCancelled
  153. eventMonitor?.requestDidCancel(self)
  154. }
  155. func didGatherMetrics(_ metrics: URLSessionTaskMetrics) {
  156. self.metrics = metrics
  157. eventMonitor?.request(self, didGatherMetrics: metrics)
  158. }
  159. // Should only be triggered by internal AF code, never URLSession
  160. func didFailTask(_ task: URLSessionTask, earlyWithError error: Error) {
  161. self.error = error
  162. // Task will still complete, so didCompleteTask(_:with:) will handle retry.
  163. eventMonitor?.request(self, didFailTask: task, earlyWithError: error)
  164. }
  165. // Completion point for all tasks.
  166. func didCompleteTask(_ task: URLSessionTask, with error: Error?) {
  167. self.error = self.error ?? error
  168. validators.forEach { $0() }
  169. eventMonitor?.request(self, didCompleteTask: task, with: error)
  170. retryOrFinish(error: self.error)
  171. }
  172. func retryOrFinish(error: Error?) {
  173. if let error = error, delegate?.isRetryingRequest(self, ifNecessaryWithError: error) == true {
  174. return
  175. } else {
  176. finish()
  177. }
  178. }
  179. func finish() {
  180. // Start response handlers
  181. internalQueue.isSuspended = false
  182. eventMonitor?.requestDidFinish(self)
  183. }
  184. // MARK: Task Creation
  185. // Subclasses wanting something other than URLSessionDataTask should override.
  186. func task(for request: URLRequest, using session: URLSession) -> URLSessionTask {
  187. return session.dataTask(with: request)
  188. }
  189. // MARK: - Public API
  190. // Callable from any queue.
  191. @discardableResult
  192. public func cancel() -> Self {
  193. guard state.canTransitionTo(.cancelled) else { return self }
  194. state = .cancelled
  195. delegate?.cancelRequest(self)
  196. return self
  197. }
  198. @discardableResult
  199. public func suspend() -> Self {
  200. guard state.canTransitionTo(.suspended) else { return self }
  201. state = .suspended
  202. delegate?.suspendRequest(self)
  203. return self
  204. }
  205. @discardableResult
  206. public func resume() -> Self {
  207. guard state.canTransitionTo(.resumed) else { return self }
  208. state = .resumed
  209. delegate?.resumeRequest(self)
  210. return self
  211. }
  212. // MARK: - Closure API
  213. // Callable from any queue
  214. // TODO: Handle race from internal queue?
  215. @discardableResult
  216. open func authenticate(withUsername username: String, password: String, persistence: URLCredential.Persistence = .forSession) -> Self {
  217. let credential = URLCredential(user: username, password: password, persistence: persistence)
  218. return authenticate(with: credential)
  219. }
  220. @discardableResult
  221. open func authenticate(with credential: URLCredential) -> Self {
  222. self.credential = credential
  223. return self
  224. }
  225. /// Sets a closure to be called periodically during the lifecycle of the `Request` as data is read from the server.
  226. ///
  227. /// - parameter queue: The dispatch queue to execute the closure on.
  228. /// - parameter closure: The code to be executed periodically as data is read from the server.
  229. ///
  230. /// - returns: The request.
  231. @discardableResult
  232. open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
  233. underlyingQueue.async { self.downloadProgressHandler = (closure, queue) }
  234. return self
  235. }
  236. // MARK: Upload Progress
  237. /// Sets a closure to be called periodically during the lifecycle of the `UploadRequest` as data is sent to
  238. /// the server.
  239. ///
  240. /// After the data is sent to the server, the `progress(queue:closure:)` APIs can be used to monitor the progress
  241. /// of data being read from the server.
  242. ///
  243. /// - parameter queue: The dispatch queue to execute the closure on.
  244. /// - parameter closure: The code to be executed periodically as data is sent to the server.
  245. ///
  246. /// - returns: The request.
  247. @discardableResult
  248. open func uploadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
  249. underlyingQueue.async { self.uploadProgressHandler = (closure, queue) }
  250. return self
  251. }
  252. }
  253. extension Request: Equatable {
  254. public static func == (lhs: Request, rhs: Request) -> Bool {
  255. return lhs.id == rhs.id
  256. }
  257. }
  258. extension Request: Hashable {
  259. public var hashValue: Int {
  260. return id.hashValue
  261. }
  262. }
  263. open class DataRequest: Request {
  264. let convertible: URLRequestConvertible
  265. private(set) var data: Data?
  266. init(id: UUID = UUID(),
  267. convertible: URLRequestConvertible,
  268. underlyingQueue: DispatchQueue,
  269. serializationQueue: DispatchQueue? = nil,
  270. eventMonitor: RequestEventMonitor?,
  271. delegate: RequestDelegate) {
  272. self.convertible = convertible
  273. super.init(id: id,
  274. underlyingQueue: underlyingQueue,
  275. serializationQueue: serializationQueue,
  276. eventMonitor: eventMonitor,
  277. delegate: delegate)
  278. }
  279. override func reset() {
  280. super.reset()
  281. data = nil
  282. }
  283. func didRecieve(data: Data) {
  284. if self.data == nil {
  285. self.data = data
  286. } else {
  287. self.data?.append(data)
  288. }
  289. updateDownloadProgress()
  290. }
  291. func updateDownloadProgress() {
  292. let totalBytesRecieved = Int64(self.data?.count ?? 0)
  293. let totalBytesExpected = task?.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
  294. downloadProgress.totalUnitCount = totalBytesExpected
  295. downloadProgress.completedUnitCount = totalBytesRecieved
  296. downloadProgressHandler?.queue.async { self.downloadProgressHandler?.handler(self.downloadProgress) }
  297. }
  298. /// Validates the request, using the specified closure.
  299. ///
  300. /// If validation fails, subsequent calls to response handlers will have an associated error.
  301. ///
  302. /// - parameter validation: A closure to validate the request.
  303. ///
  304. /// - returns: The request.
  305. @discardableResult
  306. public func validate(_ validation: @escaping Validation) -> Self {
  307. underlyingQueue.async {
  308. let validationExecution: () -> Void = { [unowned self] in
  309. if
  310. let response = self.response,
  311. self.error == nil,
  312. case let .failure(error) = validation(self.request, response, self.data)
  313. {
  314. self.error = error
  315. }
  316. }
  317. self.validators.append(validationExecution)
  318. }
  319. return self
  320. }
  321. }
  322. open class DownloadRequest: Request {
  323. /// A collection of options to be executed prior to moving a downloaded file from the temporary URL to the
  324. /// destination URL.
  325. public struct Options: OptionSet {
  326. /// A `DownloadOptions` flag that creates intermediate directories for the destination URL if specified.
  327. public static let createIntermediateDirectories = Options(rawValue: 1 << 0)
  328. /// A `DownloadOptions` flag that removes a previous file from the destination URL if specified.
  329. public static let removePreviousFile = Options(rawValue: 1 << 1)
  330. /// Returns the raw bitmask value of the option and satisfies the `RawRepresentable` protocol.
  331. public let rawValue: Int
  332. /// Creates a `DownloadRequest.Options` instance with the specified raw value.
  333. ///
  334. /// - parameter rawValue: The raw bitmask value for the option.
  335. ///
  336. /// - returns: A new `DownloadRequest.Options` instance.
  337. public init(rawValue: Int) {
  338. self.rawValue = rawValue
  339. }
  340. }
  341. /// A closure executed once a download request has successfully completed in order to determine where to move the
  342. /// temporary file written to during the download process. The closure takes two arguments: the temporary file URL
  343. /// and the URL response, and returns a two arguments: the file URL where the temporary file should be moved and
  344. /// the options defining how the file should be moved.
  345. public typealias Destination = (_ temporaryURL: URL,
  346. _ response: HTTPURLResponse) -> (destinationURL: URL, options: Options)
  347. // MARK: Destination
  348. /// Creates a download file destination closure which uses the default file manager to move the temporary file to a
  349. /// file URL in the first available directory with the specified search path directory and search path domain mask.
  350. ///
  351. /// - parameter directory: The search path directory. `.documentDirectory` by default.
  352. /// - parameter domain: The search path domain mask. `.userDomainMask` by default.
  353. ///
  354. /// - returns: A download file destination closure.
  355. open class func suggestedDownloadDestination(for directory: FileManager.SearchPathDirectory = .documentDirectory,
  356. in domain: FileManager.SearchPathDomainMask = .userDomainMask,
  357. options: Options = []) -> Destination {
  358. return { (temporaryURL, response) in
  359. let directoryURLs = FileManager.default.urls(for: directory, in: domain)
  360. let url = directoryURLs.first?.appendingPathComponent(response.suggestedFilename!) ?? temporaryURL
  361. return (url, options)
  362. }
  363. }
  364. public enum Downloadable {
  365. case request(URLRequestConvertible)
  366. case resumeData(Data)
  367. }
  368. // MARK: Initial State
  369. let downloadable: Downloadable
  370. private let destination: Destination?
  371. // MARK: Updated State
  372. open internal(set) var resumeData: Data?
  373. open internal(set) var temporaryURL: URL?
  374. open internal(set) var destinationURL: URL?
  375. open var fileURL: URL? {
  376. return destinationURL ?? temporaryURL
  377. }
  378. // MARK: Init
  379. init(id: UUID = UUID(),
  380. downloadable: Downloadable,
  381. underlyingQueue: DispatchQueue,
  382. serializationQueue: DispatchQueue? = nil,
  383. eventMonitor: RequestEventMonitor?,
  384. delegate: RequestDelegate,
  385. destination: Destination? = nil) {
  386. self.downloadable = downloadable
  387. self.destination = destination
  388. super.init(id: id,
  389. underlyingQueue: underlyingQueue,
  390. serializationQueue: serializationQueue,
  391. eventMonitor: eventMonitor,
  392. delegate: delegate)
  393. }
  394. override func reset() {
  395. super.reset()
  396. temporaryURL = nil
  397. destinationURL = nil
  398. resumeData = nil
  399. }
  400. func didComplete(task: URLSessionTask, with url: URL) {
  401. temporaryURL = url
  402. guard let destination = destination, let response = response else { return }
  403. let (destinationURL, options) = destination(url, response)
  404. self.destinationURL = destinationURL
  405. // TODO: Inject FileManager?
  406. do {
  407. if options.contains(.removePreviousFile), FileManager.default.fileExists(atPath: destinationURL.path) {
  408. try FileManager.default.removeItem(at: destinationURL)
  409. }
  410. if options.contains(.createIntermediateDirectories) {
  411. let directory = destinationURL.deletingLastPathComponent()
  412. try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
  413. }
  414. try FileManager.default.moveItem(at: url, to: destinationURL)
  415. } catch {
  416. self.error = error
  417. }
  418. }
  419. func updateDownloadProgress(bytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
  420. downloadProgress.totalUnitCount = totalBytesExpectedToWrite
  421. downloadProgress.completedUnitCount += bytesWritten
  422. downloadProgressHandler?.queue.async { self.downloadProgressHandler?.handler(self.downloadProgress) }
  423. }
  424. override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask {
  425. return session.downloadTask(with: request)
  426. }
  427. open func task(forResumeData data: Data, using session: URLSession) -> URLSessionTask {
  428. return session.downloadTask(withResumeData: data)
  429. }
  430. @discardableResult
  431. public override func cancel() -> Self {
  432. // TODO: EventMonitor?
  433. guard state.canTransitionTo(.cancelled) else { return self }
  434. state = .cancelled
  435. delegate?.cancelDownloadRequest(self) { self.resumeData = $0 }
  436. return self
  437. }
  438. /// Validates the request, using the specified closure.
  439. ///
  440. /// If validation fails, subsequent calls to response handlers will have an associated error.
  441. ///
  442. /// - parameter validation: A closure to validate the request.
  443. ///
  444. /// - returns: The request.
  445. @discardableResult
  446. public func validate(_ validation: @escaping Validation) -> Self {
  447. underlyingQueue.async {
  448. let validationExecution: () -> Void = { [unowned self] in
  449. let request = self.request
  450. let temporaryURL = self.temporaryURL
  451. let destinationURL = self.temporaryURL
  452. if
  453. let response = self.response,
  454. self.error == nil,
  455. case let .failure(error) = validation(request, response, temporaryURL, destinationURL)
  456. {
  457. self.error = error
  458. }
  459. }
  460. self.validators.append(validationExecution)
  461. }
  462. return self
  463. }
  464. }
  465. open class UploadRequest: DataRequest {
  466. public enum Uploadable {
  467. case data(Data)
  468. case file(URL, shouldRemove: Bool)
  469. case stream(InputStream)
  470. }
  471. // MARK: - Initial State
  472. let upload: UploadableConvertible
  473. // MARK: - Updated State
  474. public var uploadable: Uploadable?
  475. init(id: UUID = UUID(),
  476. convertible: UploadConvertible,
  477. underlyingQueue: DispatchQueue,
  478. serializationQueue: DispatchQueue? = nil,
  479. eventMonitor: RequestEventMonitor?,
  480. delegate: RequestDelegate) {
  481. self.upload = convertible
  482. super.init(id: id,
  483. convertible: convertible,
  484. underlyingQueue: underlyingQueue,
  485. serializationQueue: serializationQueue,
  486. eventMonitor: eventMonitor,
  487. delegate: delegate)
  488. // Automatically remove temporary upload files (e.g. multipart form data)
  489. internalQueue.addOperation {
  490. guard
  491. let uploadable = self.uploadable,
  492. case let .file(url, shouldRemove) = uploadable,
  493. shouldRemove else { return }
  494. // TODO: Abstract file manager
  495. try? FileManager.default.removeItem(at: url)
  496. }
  497. }
  498. func didCreateUploadable(_ uploadable: Uploadable) {
  499. // TODO: Event Monitor here.
  500. self.uploadable = uploadable
  501. }
  502. func didFailToCreateUploadable(with error: Error) {
  503. // TODO: Event monitor
  504. self.error = error
  505. retryOrFinish(error: error)
  506. }
  507. override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask {
  508. guard let uploadable = uploadable else {
  509. fatalError("Attempting to create a URLSessionUploadTask when Uploadable value doesn't exist.")
  510. }
  511. switch uploadable {
  512. case let .data(data): return session.uploadTask(with: request, from: data)
  513. case let .file(url, _): return session.uploadTask(with: request, fromFile: url)
  514. case .stream: return session.uploadTask(withStreamedRequest: request)
  515. }
  516. }
  517. func inputStream() -> InputStream {
  518. guard let uploadable = uploadable else {
  519. fatalError("Attempting to access the input stream but the uploadable doesn't exist.")
  520. }
  521. guard case let .stream(stream) = uploadable else {
  522. fatalError("Attempted to access the stream of an UploadRequest that wasn't created with one.")
  523. }
  524. return stream
  525. }
  526. }
  527. protocol UploadableConvertible {
  528. func createUploadable() throws -> UploadRequest.Uploadable
  529. }
  530. extension UploadRequest.Uploadable: UploadableConvertible {
  531. func createUploadable() throws -> UploadRequest.Uploadable {
  532. return self
  533. }
  534. }
  535. protocol UploadConvertible: UploadableConvertible & URLRequestConvertible { }