Upload.swift 16 KB

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