Upload.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. //
  2. // Upload.swift
  3. //
  4. // Copyright (c) 2014-2016 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. extension Manager {
  26. private enum Uploadable {
  27. case Data(NSURLRequest, NSData)
  28. case File(NSURLRequest, NSURL)
  29. case Stream(NSURLRequest, NSInputStream)
  30. }
  31. private func upload(uploadable: Uploadable) -> Request {
  32. var uploadTask: NSURLSessionUploadTask!
  33. var HTTPBodyStream: NSInputStream?
  34. switch uploadable {
  35. case .Data(let request, let data):
  36. dispatch_sync(queue) {
  37. uploadTask = self.session.uploadTaskWithRequest(request, fromData: data)
  38. }
  39. case .File(let request, let fileURL):
  40. dispatch_sync(queue) {
  41. uploadTask = self.session.uploadTaskWithRequest(request, fromFile: fileURL)
  42. }
  43. case .Stream(let request, let stream):
  44. dispatch_sync(queue) {
  45. uploadTask = self.session.uploadTaskWithStreamedRequest(request)
  46. }
  47. HTTPBodyStream = stream
  48. }
  49. let request = Request(session: session, task: uploadTask)
  50. if HTTPBodyStream != nil {
  51. request.delegate.taskNeedNewBodyStream = { _, _ in
  52. return HTTPBodyStream
  53. }
  54. }
  55. delegate[request.delegate.task] = request.delegate
  56. if startRequestsImmediately {
  57. request.resume()
  58. }
  59. return request
  60. }
  61. // MARK: File
  62. /**
  63. Creates a request for uploading a file to the specified URL request.
  64. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  65. - parameter URLRequest: The URL request
  66. - parameter file: The file to upload
  67. - returns: The created upload request.
  68. */
  69. public func upload(URLRequest: URLRequestConvertible, file: NSURL) -> Request {
  70. return upload(.File(URLRequest.URLRequest, file))
  71. }
  72. /**
  73. Creates a request for uploading a file to the specified URL request.
  74. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  75. - parameter method: The HTTP method.
  76. - parameter URLString: The URL string.
  77. - parameter headers: The HTTP headers. `nil` by default.
  78. - parameter file: The file to upload
  79. - returns: The created upload request.
  80. */
  81. public func upload(
  82. method: Method,
  83. _ URLString: URLStringConvertible,
  84. headers: [String: String]? = nil,
  85. file: NSURL)
  86. -> Request
  87. {
  88. let mutableURLRequest = URLRequest(method, URLString, headers: headers)
  89. return upload(mutableURLRequest, file: file)
  90. }
  91. // MARK: Data
  92. /**
  93. Creates a request for uploading data to the specified URL request.
  94. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  95. - parameter URLRequest: The URL request.
  96. - parameter data: The data to upload.
  97. - returns: The created upload request.
  98. */
  99. public func upload(URLRequest: URLRequestConvertible, data: NSData) -> Request {
  100. return upload(.Data(URLRequest.URLRequest, data))
  101. }
  102. /**
  103. Creates a request for uploading data to the specified URL request.
  104. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  105. - parameter method: The HTTP method.
  106. - parameter URLString: The URL string.
  107. - parameter headers: The HTTP headers. `nil` by default.
  108. - parameter data: The data to upload
  109. - returns: The created upload request.
  110. */
  111. public func upload(
  112. method: Method,
  113. _ URLString: URLStringConvertible,
  114. headers: [String: String]? = nil,
  115. data: NSData)
  116. -> Request
  117. {
  118. let mutableURLRequest = URLRequest(method, URLString, headers: headers)
  119. return upload(mutableURLRequest, data: data)
  120. }
  121. // MARK: Stream
  122. /**
  123. Creates a request for uploading a stream to the specified URL request.
  124. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  125. - parameter URLRequest: The URL request.
  126. - parameter stream: The stream to upload.
  127. - returns: The created upload request.
  128. */
  129. public func upload(URLRequest: URLRequestConvertible, stream: NSInputStream) -> Request {
  130. return upload(.Stream(URLRequest.URLRequest, stream))
  131. }
  132. /**
  133. Creates a request for uploading a stream to the specified URL request.
  134. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  135. - parameter method: The HTTP method.
  136. - parameter URLString: The URL string.
  137. - parameter headers: The HTTP headers. `nil` by default.
  138. - parameter stream: The stream to upload.
  139. - returns: The created upload request.
  140. */
  141. public func upload(
  142. method: Method,
  143. _ URLString: URLStringConvertible,
  144. headers: [String: String]? = nil,
  145. stream: NSInputStream)
  146. -> Request
  147. {
  148. let mutableURLRequest = URLRequest(method, URLString, headers: headers)
  149. return upload(mutableURLRequest, stream: stream)
  150. }
  151. // MARK: MultipartFormData
  152. /// Default memory threshold used when encoding `MultipartFormData`.
  153. public static let MultipartFormDataEncodingMemoryThreshold: UInt64 = 10 * 1024 * 1024
  154. /**
  155. Defines whether the `MultipartFormData` encoding was successful and contains result of the encoding as
  156. associated values.
  157. - Success: Represents a successful `MultipartFormData` encoding and contains the new `Request` along with
  158. streaming information.
  159. - Failure: Used to represent a failure in the `MultipartFormData` encoding and also contains the encoding
  160. error.
  161. */
  162. public enum MultipartFormDataEncodingResult {
  163. case Success(request: Request, streamingFromDisk: Bool, streamFileURL: NSURL?)
  164. case Failure(ErrorType)
  165. }
  166. /**
  167. Encodes the `MultipartFormData` and creates a request to upload the result to the specified URL request.
  168. It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  169. payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  170. efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  171. be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  172. footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  173. used for larger payloads such as video content.
  174. The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  175. or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  176. encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  177. during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  178. technique was used.
  179. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  180. - parameter method: The HTTP method.
  181. - parameter URLString: The URL string.
  182. - parameter headers: The HTTP headers. `nil` by default.
  183. - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
  184. - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
  185. `MultipartFormDataEncodingMemoryThreshold` by default.
  186. - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
  187. */
  188. public func upload(
  189. method: Method,
  190. _ URLString: URLStringConvertible,
  191. headers: [String: String]? = nil,
  192. multipartFormData: MultipartFormData -> Void,
  193. encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold,
  194. encodingCompletion: (MultipartFormDataEncodingResult -> Void)?)
  195. {
  196. let mutableURLRequest = URLRequest(method, URLString, headers: headers)
  197. return upload(
  198. mutableURLRequest,
  199. multipartFormData: multipartFormData,
  200. encodingMemoryThreshold: encodingMemoryThreshold,
  201. encodingCompletion: encodingCompletion
  202. )
  203. }
  204. /**
  205. Encodes the `MultipartFormData` and creates a request to upload the result to the specified URL request.
  206. It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  207. payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  208. efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  209. be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  210. footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  211. used for larger payloads such as video content.
  212. The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  213. or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  214. encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  215. during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  216. technique was used.
  217. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  218. - parameter URLRequest: The URL request.
  219. - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
  220. - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
  221. `MultipartFormDataEncodingMemoryThreshold` by default.
  222. - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
  223. */
  224. public func upload(
  225. URLRequest: URLRequestConvertible,
  226. multipartFormData: MultipartFormData -> Void,
  227. encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold,
  228. encodingCompletion: (MultipartFormDataEncodingResult -> Void)?)
  229. {
  230. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
  231. let formData = MultipartFormData()
  232. multipartFormData(formData)
  233. let URLRequestWithContentType = URLRequest.URLRequest
  234. URLRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
  235. let isBackgroundSession = self.session.configuration.identifier != nil
  236. if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {
  237. do {
  238. let data = try formData.encode()
  239. let encodingResult = MultipartFormDataEncodingResult.Success(
  240. request: self.upload(URLRequestWithContentType, data: data),
  241. streamingFromDisk: false,
  242. streamFileURL: nil
  243. )
  244. dispatch_async(dispatch_get_main_queue()) {
  245. encodingCompletion?(encodingResult)
  246. }
  247. } catch {
  248. dispatch_async(dispatch_get_main_queue()) {
  249. encodingCompletion?(.Failure(error as NSError))
  250. }
  251. }
  252. } else {
  253. let fileManager = NSFileManager.defaultManager()
  254. let tempDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory())
  255. let directoryURL = tempDirectoryURL.URLByAppendingPathComponent("com.alamofire.manager/multipart.form.data")
  256. let fileName = NSUUID().UUIDString
  257. let fileURL = directoryURL.URLByAppendingPathComponent(fileName)
  258. do {
  259. try fileManager.createDirectoryAtURL(directoryURL, withIntermediateDirectories: true, attributes: nil)
  260. try formData.writeEncodedDataToDisk(fileURL)
  261. dispatch_async(dispatch_get_main_queue()) {
  262. let encodingResult = MultipartFormDataEncodingResult.Success(
  263. request: self.upload(URLRequestWithContentType, file: fileURL),
  264. streamingFromDisk: true,
  265. streamFileURL: fileURL
  266. )
  267. encodingCompletion?(encodingResult)
  268. }
  269. } catch {
  270. dispatch_async(dispatch_get_main_queue()) {
  271. encodingCompletion?(.Failure(error as NSError))
  272. }
  273. }
  274. }
  275. }
  276. }
  277. }
  278. // MARK: -
  279. extension Request {
  280. // MARK: - UploadTaskDelegate
  281. class UploadTaskDelegate: DataTaskDelegate {
  282. var uploadTask: NSURLSessionUploadTask? { return task as? NSURLSessionUploadTask }
  283. var uploadProgress: ((Int64, Int64, Int64) -> Void)!
  284. // MARK: - NSURLSessionTaskDelegate
  285. // MARK: Override Closures
  286. var taskDidSendBodyData: ((NSURLSession, NSURLSessionTask, Int64, Int64, Int64) -> Void)?
  287. // MARK: Delegate Methods
  288. func URLSession(
  289. session: NSURLSession,
  290. task: NSURLSessionTask,
  291. didSendBodyData bytesSent: Int64,
  292. totalBytesSent: Int64,
  293. totalBytesExpectedToSend: Int64)
  294. {
  295. if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
  296. if let taskDidSendBodyData = taskDidSendBodyData {
  297. taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
  298. } else {
  299. progress.totalUnitCount = totalBytesExpectedToSend
  300. progress.completedUnitCount = totalBytesSent
  301. uploadProgress?(bytesSent, totalBytesSent, totalBytesExpectedToSend)
  302. }
  303. }
  304. }
  305. }