Request.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  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 suspendRequest(_ request: Request)
  29. func resumeRequest(_ request: Request)
  30. }
  31. open class Request {
  32. // TODO: Make publicly readable properties protected?
  33. public enum State {
  34. case initialized, resumed, suspended, cancelled
  35. func canTransitionTo(_ state: State) -> Bool {
  36. switch (self, state) {
  37. case (.initialized, _): return true
  38. case (_, .initialized): return false
  39. case (.resumed, .cancelled), (.suspended, .cancelled),
  40. (.resumed, .suspended), (.suspended, .resumed): return true
  41. case (.suspended, .suspended), (.resumed, .resumed): return false
  42. case (.cancelled, _): return false
  43. }
  44. }
  45. }
  46. // MARK: - Initial State
  47. let id: UUID
  48. let convertible: URLRequestConvertible
  49. let underlyingQueue: DispatchQueue
  50. let serializationQueue: DispatchQueue
  51. let eventMonitor: RequestEventMonitor?
  52. weak var delegate: RequestDelegate?
  53. // TODO: Do we still want to expose the queue(s?) as public API?
  54. open let internalQueue: OperationQueue
  55. // MARK: - Updated State
  56. private var protectedState: Protector<State> = Protector(.initialized)
  57. public private(set) var state: State {
  58. get { return protectedState.directValue }
  59. set { protectedState.directValue = newValue }
  60. }
  61. public var isCancelled: Bool { return state == .cancelled }
  62. public var isResumed: Bool { return state == .resumed }
  63. public var isSuspended: Bool { return state == .suspended }
  64. public var isInitialized: Bool { return state == .initialized }
  65. public var retryCount: Int { return protectedTasks.read { max($0.count - 1, 0) } }
  66. private(set) var initialRequest: URLRequest?
  67. open var request: URLRequest? {
  68. return finalTask?.currentRequest
  69. }
  70. open var response: HTTPURLResponse? {
  71. return finalTask?.response as? HTTPURLResponse
  72. }
  73. private(set) var metrics: URLSessionTaskMetrics?
  74. // TODO: How to expose task progress on iOS 11?
  75. private var protectedTasks = Protector<[URLSessionTask]>([])
  76. public var tasks: [URLSessionTask] {
  77. get { return protectedTasks.directValue }
  78. }
  79. public var initialTask: URLSessionTask? { return tasks.first }
  80. public var finalTask: URLSessionTask? { return tasks.last }
  81. private var protectedTask = Protector<URLSessionTask?>(nil)
  82. private(set) public var task: URLSessionTask? {
  83. get { return protectedTask.directValue }
  84. set {
  85. // TODO: Prevent task from being set to nil?
  86. protectedTask.directValue = newValue
  87. guard let task = newValue else { return }
  88. protectedTasks.append(task)
  89. }
  90. }
  91. fileprivate(set) public var error: Error?
  92. private(set) var credential: URLCredential?
  93. fileprivate(set) var validators: [() -> Void] = []
  94. init(id: UUID = UUID(),
  95. convertible: URLRequestConvertible,
  96. underlyingQueue: DispatchQueue,
  97. serializationQueue: DispatchQueue? = nil,
  98. eventMonitor: RequestEventMonitor?,
  99. delegate: RequestDelegate) {
  100. self.id = id
  101. self.convertible = convertible
  102. self.underlyingQueue = underlyingQueue
  103. self.serializationQueue = serializationQueue ?? underlyingQueue
  104. internalQueue = OperationQueue(maxConcurrentOperationCount: 1,
  105. underlyingQueue: underlyingQueue,
  106. name: "org.alamofire.request-\(id)",
  107. startSuspended: true)
  108. self.eventMonitor = eventMonitor
  109. self.delegate = delegate
  110. }
  111. // MARK: - Internal API
  112. // Called from internal queue.
  113. func didCreateURLRequest(_ request: URLRequest) {
  114. initialRequest = request
  115. eventMonitor?.request(self, didCreateURLRequest: request)
  116. }
  117. func didFailToCreateURLRequest(with error: Error) {
  118. self.error = error
  119. eventMonitor?.request(self, didFailToCreateURLRequestWithError: error)
  120. retryOrFinish(error: error)
  121. }
  122. func didAdaptInitialRequest(_ initialRequest: URLRequest, to adaptedRequest: URLRequest) {
  123. self.initialRequest = adaptedRequest
  124. // Set initialRequest or something else?
  125. eventMonitor?.request(self, didAdaptInitialRequest: initialRequest, to: adaptedRequest)
  126. }
  127. func didFailToAdaptURLRequest(_ request: URLRequest, withError error: Error) {
  128. self.error = error
  129. eventMonitor?.request(self, didFailToAdaptURLRequest: request, withError: error)
  130. retryOrFinish(error: error)
  131. }
  132. func didCreateTask(_ task: URLSessionTask) {
  133. self.task = task
  134. // TODO: Reset behavior?
  135. self.error = nil
  136. eventMonitor?.request(self, didCreateTask: task)
  137. }
  138. func didResume() {
  139. eventMonitor?.requestDidResume(self)
  140. }
  141. func didSuspend() {
  142. eventMonitor?.requestDidSuspend(self)
  143. }
  144. func didCancel() {
  145. error = AFError.explicitlyCancelled
  146. eventMonitor?.requestDidCancel(self)
  147. }
  148. func didGatherMetrics(_ metrics: URLSessionTaskMetrics) {
  149. self.metrics = metrics
  150. eventMonitor?.request(self, didGatherMetrics: metrics)
  151. }
  152. // Should only be triggered by internal AF code, never URLSession
  153. func didFailTask(_ task: URLSessionTask, earlyWithError error: Error) {
  154. self.error = error
  155. // Task will still complete, so didCompleteTask(_:with:) will handle retry.
  156. eventMonitor?.request(self, didFailTask: task, earlyWithError: error)
  157. }
  158. // Completion point for all tasks.
  159. func didCompleteTask(_ task: URLSessionTask, with error: Error?) {
  160. self.error = self.error ?? error
  161. validators.forEach { $0() }
  162. eventMonitor?.request(self, didCompleteTask: task, with: error)
  163. retryOrFinish(error: self.error)
  164. }
  165. func retryOrFinish(error: Error?) {
  166. if let error = error, delegate?.isRetryingRequest(self, ifNecessaryWithError: error) == true {
  167. return
  168. } else {
  169. finish()
  170. }
  171. }
  172. func finish() {
  173. // Start response handlers
  174. internalQueue.isSuspended = false
  175. eventMonitor?.requestDidFinish(self)
  176. }
  177. // MARK: Task Creation
  178. // Subclasses wanting something other than URLSessionDataTask should override.
  179. func task(for request: URLRequest, using session: URLSession) -> URLSessionTask {
  180. return session.dataTask(with: request)
  181. }
  182. // MARK: - Public API
  183. // Callable from any queue.
  184. @discardableResult
  185. public func cancel() -> Self {
  186. guard state.canTransitionTo(.cancelled) else { return self }
  187. state = .cancelled
  188. delegate?.cancelRequest(self)
  189. return self
  190. }
  191. @discardableResult
  192. public func suspend() -> Self {
  193. guard state.canTransitionTo(.suspended) else { return self }
  194. state = .suspended
  195. delegate?.suspendRequest(self)
  196. return self
  197. }
  198. @discardableResult
  199. public func resume() -> Self {
  200. guard state.canTransitionTo(.resumed) else { return self }
  201. state = .resumed
  202. delegate?.resumeRequest(self)
  203. return self
  204. }
  205. // MARK: - Closure API
  206. // Callable from any queue
  207. // TODO: Handle race from internal queue?
  208. @discardableResult
  209. open func authenticate(withUsername username: String, password: String, persistence: URLCredential.Persistence = .forSession) -> Self {
  210. let credential = URLCredential(user: username, password: password, persistence: persistence)
  211. return authenticate(with: credential)
  212. }
  213. @discardableResult
  214. open func authenticate(with credential: URLCredential) -> Self {
  215. self.credential = credential
  216. return self
  217. }
  218. }
  219. extension Request: Equatable {
  220. public static func == (lhs: Request, rhs: Request) -> Bool {
  221. return lhs.id == rhs.id
  222. }
  223. }
  224. extension Request: Hashable {
  225. public var hashValue: Int {
  226. return id.hashValue
  227. }
  228. }
  229. open class DataRequest: Request {
  230. private(set) var data: Data?
  231. func didRecieve(data: Data) {
  232. if self.data == nil {
  233. self.data = data
  234. } else {
  235. self.data?.append(data)
  236. }
  237. }
  238. /// Validates the request, using the specified closure.
  239. ///
  240. /// If validation fails, subsequent calls to response handlers will have an associated error.
  241. ///
  242. /// - parameter validation: A closure to validate the request.
  243. ///
  244. /// - returns: The request.
  245. @discardableResult
  246. public func validate(_ validation: @escaping Validation) -> Self {
  247. underlyingQueue.async {
  248. let validationExecution: () -> Void = { [unowned self] in
  249. if
  250. let response = self.response,
  251. self.error == nil,
  252. case let .failure(error) = validation(self.request, response, self.data)
  253. {
  254. self.error = error
  255. }
  256. }
  257. self.validators.append(validationExecution)
  258. }
  259. return self
  260. }
  261. }
  262. open class DownloadRequest: Request {
  263. /// A `DownloadOptions` flag that creates intermediate directories for the destination URL if specified.
  264. public static let createIntermediateDirectories = Options(rawValue: 1 << 0)
  265. /// A `DownloadOptions` flag that removes a previous file from the destination URL if specified.
  266. public static let removePreviousFile = Options(rawValue: 1 << 1)
  267. /// A collection of options to be executed prior to moving a downloaded file from the temporary URL to the
  268. /// destination URL.
  269. public struct Options: OptionSet {
  270. /// Returns the raw bitmask value of the option and satisfies the `RawRepresentable` protocol.
  271. public let rawValue: Int
  272. /// Creates a `DownloadRequest.Options` instance with the specified raw value.
  273. ///
  274. /// - parameter rawValue: The raw bitmask value for the option.
  275. ///
  276. /// - returns: A new `DownloadRequest.Options` instance.
  277. public init(rawValue: Int) {
  278. self.rawValue = rawValue
  279. }
  280. }
  281. /// A closure executed once a download request has successfully completed in order to determine where to move the
  282. /// temporary file written to during the download process. The closure takes two arguments: the temporary file URL
  283. /// and the URL response, and returns a two arguments: the file URL where the temporary file should be moved and
  284. /// the options defining how the file should be moved.
  285. public typealias Destination = (_ temporaryURL: URL,
  286. _ response: HTTPURLResponse) -> (destinationURL: URL, options: Options)
  287. // MARK: Destination
  288. /// Creates a download file destination closure which uses the default file manager to move the temporary file to a
  289. /// file URL in the first available directory with the specified search path directory and search path domain mask.
  290. ///
  291. /// - parameter directory: The search path directory. `.documentDirectory` by default.
  292. /// - parameter domain: The search path domain mask. `.userDomainMask` by default.
  293. ///
  294. /// - returns: A download file destination closure.
  295. open class func suggestedDownloadDestination(for directory: FileManager.SearchPathDirectory = .documentDirectory,
  296. in domain: FileManager.SearchPathDomainMask = .userDomainMask) -> Destination {
  297. return { (temporaryURL, response) in
  298. let directoryURLs = FileManager.default.urls(for: directory, in: domain)
  299. let url = directoryURLs.first?.appendingPathComponent(response.suggestedFilename!) ?? temporaryURL
  300. return (url, [])
  301. }
  302. }
  303. // MARK: Initial State
  304. private let destination: Destination
  305. // MARK: Updated State
  306. private(set) var temporaryURL: URL?
  307. // MARK: Init
  308. init(id: UUID = UUID(),
  309. convertible: URLRequestConvertible,
  310. underlyingQueue: DispatchQueue,
  311. serializationQueue: DispatchQueue? = nil,
  312. eventMonitor: RequestEventMonitor?,
  313. delegate: RequestDelegate,
  314. destination: @escaping Destination) {
  315. self.destination = destination
  316. super.init(id: id,
  317. convertible: convertible,
  318. underlyingQueue: underlyingQueue,
  319. serializationQueue: serializationQueue,
  320. eventMonitor: eventMonitor,
  321. delegate: delegate)
  322. }
  323. func didComplete(task: URLSessionTask, with url: URL) {
  324. temporaryURL = url
  325. }
  326. override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask {
  327. // TODO: Need resume data.
  328. return session.downloadTask(with: request)
  329. }
  330. /// Validates the request, using the specified closure.
  331. ///
  332. /// If validation fails, subsequent calls to response handlers will have an associated error.
  333. ///
  334. /// - parameter validation: A closure to validate the request.
  335. ///
  336. /// - returns: The request.
  337. @discardableResult
  338. public func validate(_ validation: @escaping Validation) -> Self {
  339. underlyingQueue.async {
  340. let validationExecution: () -> Void = { [unowned self] in
  341. let request = self.request
  342. let temporaryURL = self.temporaryURL
  343. let destinationURL = self.temporaryURL
  344. if
  345. let response = self.response,
  346. self.error == nil,
  347. case let .failure(error) = validation(request, response, temporaryURL, destinationURL)
  348. {
  349. self.error = error
  350. }
  351. }
  352. self.validators.append(validationExecution)
  353. }
  354. return self
  355. }
  356. }
  357. open class UploadRequest: DataRequest {
  358. enum Uploadable {
  359. case data(Data)
  360. case file(URL)
  361. case stream(InputStream)
  362. }
  363. let uploadable: Uploadable
  364. init(id: UUID = UUID(),
  365. convertible: URLRequestConvertible,
  366. underlyingQueue: DispatchQueue,
  367. serializationQueue: DispatchQueue? = nil,
  368. eventMonitor: RequestEventMonitor?,
  369. delegate: RequestDelegate,
  370. uploadable: Uploadable) {
  371. self.uploadable = uploadable
  372. super.init(id: id,
  373. convertible: convertible,
  374. underlyingQueue: underlyingQueue,
  375. serializationQueue: serializationQueue,
  376. eventMonitor: eventMonitor,
  377. delegate: delegate)
  378. }
  379. override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask {
  380. switch uploadable {
  381. case let .data(data): return session.uploadTask(with: request, from: data)
  382. case let .file(url): return session.uploadTask(with: request, fromFile: url)
  383. case .stream: return session.uploadTask(withStreamedRequest: request)
  384. }
  385. }
  386. func inputStream() -> InputStream {
  387. switch uploadable {
  388. case .stream(let stream): return stream
  389. default: fatalError("Attempted to access the stream of an UploadRequest that wasn't created with one.")
  390. }
  391. }
  392. }