Upload.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. // Alamofire.swift
  2. //
  3. // Copyright (c) 2014–2015 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, var 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. :param: URLRequest The URL request
  64. :param: 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. :param: method The HTTP method.
  74. :param: URLString The URL string.
  75. :param: file The file to upload
  76. :returns: The created upload request.
  77. */
  78. public func upload(method: Method, _ URLString: URLStringConvertible, file: NSURL) -> Request {
  79. return upload(URLRequest(method, URLString), file: file)
  80. }
  81. // MARK: Data
  82. /**
  83. Creates a request for uploading data to the specified URL request.
  84. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  85. :param: URLRequest The URL request
  86. :param: data The data to upload
  87. :returns: The created upload request.
  88. */
  89. public func upload(URLRequest: URLRequestConvertible, data: NSData) -> Request {
  90. return upload(.Data(URLRequest.URLRequest, data))
  91. }
  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. :param: method The HTTP method.
  96. :param: URLString The URL string.
  97. :param: data The data to upload
  98. :returns: The created upload request.
  99. */
  100. public func upload(method: Method, _ URLString: URLStringConvertible, data: NSData) -> Request {
  101. return upload(URLRequest(method, URLString), data: data)
  102. }
  103. // MARK: Stream
  104. /**
  105. Creates a request for uploading a stream to the specified URL request.
  106. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  107. :param: URLRequest The URL request
  108. :param: stream The stream to upload
  109. :returns: The created upload request.
  110. */
  111. public func upload(URLRequest: URLRequestConvertible, stream: NSInputStream) -> Request {
  112. return upload(.Stream(URLRequest.URLRequest, stream))
  113. }
  114. /**
  115. Creates a request for uploading a stream to the specified URL request.
  116. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  117. :param: method The HTTP method.
  118. :param: URLString The URL string.
  119. :param: stream The stream to upload.
  120. :returns: The created upload request.
  121. */
  122. public func upload(method: Method, _ URLString: URLStringConvertible, stream: NSInputStream) -> Request {
  123. return upload(URLRequest(method, URLString), stream: stream)
  124. }
  125. // MARK: MultipartFormData
  126. /// Default memory threshold used when encoding `MultipartFormData`.
  127. public static let MultipartFormDataEncodingMemoryThreshold: UInt64 = 10 * 1024 * 1024
  128. /**
  129. Defines whether the `MultipartFormData` encoding was successful and contains result of the encoding as
  130. associated values.
  131. - Success: Represents a successful `MultipartFormData` encoding and contains the new `Request` along with
  132. streaming information.
  133. - Failure: Used to represent a failure in the `MultipartFormData` encoding and also contains the encoding
  134. error.
  135. */
  136. public enum MultipartFormDataEncodingResult {
  137. case Success(request: Request, streamingFromDisk: Bool, streamFileURL: NSURL?)
  138. case Failure(NSError)
  139. }
  140. /**
  141. Encodes the `MultipartFormData` and creates a request to upload the result to the specified URL request.
  142. It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  143. payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  144. efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  145. be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  146. footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  147. used for larger payloads such as video content.
  148. The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  149. or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  150. encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  151. during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  152. technique was used.
  153. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  154. :param: method The HTTP method.
  155. :param: URLString The URL string.
  156. :param: multipartFormData The closure used to append body parts to the `MultipartFormData`.
  157. :param: encodingMemoryThreshold The encoding memory threshold in bytes. `MultipartFormDataEncodingMemoryThreshold`
  158. by default.
  159. :param: encodingCompletion The closure called when the `MultipartFormData` encoding is complete.
  160. */
  161. public func upload(
  162. method: Method,
  163. _ URLString: URLStringConvertible,
  164. multipartFormData: MultipartFormData -> Void,
  165. encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold,
  166. encodingCompletion: (MultipartFormDataEncodingResult -> Void)?)
  167. {
  168. let urlRequest = URLRequest(method, URLString)
  169. return upload(
  170. urlRequest,
  171. multipartFormData: multipartFormData,
  172. encodingMemoryThreshold: encodingMemoryThreshold,
  173. encodingCompletion: encodingCompletion
  174. )
  175. }
  176. /**
  177. Encodes the `MultipartFormData` and creates a request to upload the result to the specified URL request.
  178. It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  179. payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  180. efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  181. be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  182. footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  183. used for larger payloads such as video content.
  184. The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  185. or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  186. encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  187. during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  188. technique was used.
  189. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  190. :param: URLRequest The URL request.
  191. :param: multipartFormData The closure used to append body parts to the `MultipartFormData`.
  192. :param: encodingMemoryThreshold The encoding memory threshold in bytes. `MultipartFormDataEncodingMemoryThreshold`
  193. by default.
  194. :param: encodingCompletion The closure called when the `MultipartFormData` encoding is complete.
  195. */
  196. public func upload(
  197. URLRequest: URLRequestConvertible,
  198. multipartFormData: MultipartFormData -> Void,
  199. encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold,
  200. encodingCompletion: (MultipartFormDataEncodingResult -> Void)?)
  201. {
  202. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
  203. let formData = MultipartFormData()
  204. multipartFormData(formData)
  205. let URLRequestWithContentType = URLRequest.URLRequest.mutableCopy() as! NSMutableURLRequest
  206. URLRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
  207. if formData.contentLength < encodingMemoryThreshold {
  208. let encodingResult = formData.encode()
  209. dispatch_async(dispatch_get_main_queue()) {
  210. switch encodingResult {
  211. case .Success(let data):
  212. let encodingResult = MultipartFormDataEncodingResult.Success(
  213. request: self.upload(URLRequestWithContentType, data: data),
  214. streamingFromDisk: false,
  215. streamFileURL: nil
  216. )
  217. encodingCompletion?(encodingResult)
  218. case .Failure(let error):
  219. encodingCompletion?(.Failure(error))
  220. }
  221. }
  222. } else {
  223. let fileManager = NSFileManager.defaultManager()
  224. let tempDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory())!
  225. let directoryURL = tempDirectoryURL.URLByAppendingPathComponent("com.alamofire.manager/multipart.form.data")
  226. let fileName = NSUUID().UUIDString
  227. let fileURL = directoryURL.URLByAppendingPathComponent(fileName)
  228. var error: NSError?
  229. if fileManager.createDirectoryAtURL(directoryURL, withIntermediateDirectories: true, attributes: nil, error: &error) {
  230. formData.writeEncodedDataToDisk(fileURL) { error in
  231. dispatch_async(dispatch_get_main_queue()) {
  232. if let error = error {
  233. encodingCompletion?(.Failure(error))
  234. } else {
  235. let encodingResult = MultipartFormDataEncodingResult.Success(
  236. request: self.upload(URLRequestWithContentType, file: fileURL),
  237. streamingFromDisk: true,
  238. streamFileURL: fileURL
  239. )
  240. encodingCompletion?(encodingResult)
  241. }
  242. }
  243. }
  244. } else {
  245. dispatch_async(dispatch_get_main_queue()) {
  246. encodingCompletion?(.Failure(error!))
  247. }
  248. }
  249. }
  250. }
  251. }
  252. }
  253. // MARK: -
  254. extension Request {
  255. // MARK: - UploadTaskDelegate
  256. class UploadTaskDelegate: DataTaskDelegate {
  257. var uploadTask: NSURLSessionUploadTask? { return task as? NSURLSessionUploadTask }
  258. var uploadProgress: ((Int64, Int64, Int64) -> Void)!
  259. // MARK: - NSURLSessionTaskDelegate
  260. // MARK: Override Closures
  261. var taskDidSendBodyData: ((NSURLSession, NSURLSessionTask, Int64, Int64, Int64) -> Void)?
  262. // MARK: Delegate Methods
  263. func URLSession(session: NSURLSession, task: NSURLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
  264. if let taskDidSendBodyData = self.taskDidSendBodyData {
  265. taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
  266. } else {
  267. progress.totalUnitCount = totalBytesExpectedToSend
  268. progress.completedUnitCount = totalBytesSent
  269. uploadProgress?(bytesSent, totalBytesSent, totalBytesExpectedToSend)
  270. }
  271. }
  272. }
  273. }