SessionManager.swift 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. //
  2. // SessionManager.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. /// Responsible for creating and managing `Request` objects, as well as their underlying `NSURLSession`.
  26. open class SessionManager {
  27. // MARK: - Helper Types
  28. /// Defines whether the `MultipartFormData` encoding was successful and contains result of the encoding as
  29. /// associated values.
  30. ///
  31. /// - Success: Represents a successful `MultipartFormData` encoding and contains the new `Request` along with
  32. /// streaming information.
  33. /// - Failure: Used to represent a failure in the `MultipartFormData` encoding and also contains the encoding
  34. /// error.
  35. public enum MultipartFormDataEncodingResult {
  36. case success(request: Request, streamingFromDisk: Bool, streamFileURL: URL?)
  37. case failure(Error)
  38. }
  39. private enum Downloadable {
  40. case request(URLRequest)
  41. case resumeData(Data)
  42. var task: Request.TaskConvertible {
  43. switch self {
  44. case let .request(urlRequest):
  45. return .download(urlRequest)
  46. case let .resumeData(resumeData):
  47. return .downloadResumeData(resumeData)
  48. }
  49. }
  50. }
  51. private enum Uploadable {
  52. case data(Data, URLRequest)
  53. case file(URL, URLRequest)
  54. case stream(InputStream, URLRequest)
  55. var task: Request.TaskConvertible {
  56. switch self {
  57. case let .data(data, urlRequest):
  58. return .uploadData(data, urlRequest)
  59. case let .file(url, urlRequest):
  60. return .uploadFile(url, urlRequest)
  61. case let .stream(stream, urlRequest):
  62. return .uploadStream(stream, urlRequest)
  63. }
  64. }
  65. }
  66. #if !os(watchOS)
  67. private enum Streamable {
  68. case stream(String, Int)
  69. case netService(NetService)
  70. }
  71. #endif
  72. // MARK: - Properties
  73. /// A default instance of `SessionManager`, used by top-level Alamofire request methods, and suitable for use
  74. /// directly for any ad hoc requests.
  75. open static let `default`: SessionManager = {
  76. let configuration = URLSessionConfiguration.default
  77. configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
  78. return SessionManager(configuration: configuration)
  79. }()
  80. /// Creates default values for the "Accept-Encoding", "Accept-Language" and "User-Agent" headers.
  81. open static let defaultHTTPHeaders: [String: String] = {
  82. // Accept-Encoding HTTP Header; see https://tools.ietf.org/html/rfc7230#section-4.2.3
  83. let acceptEncoding: String = "gzip;q=1.0, compress;q=0.5"
  84. // Accept-Language HTTP Header; see https://tools.ietf.org/html/rfc7231#section-5.3.5
  85. let acceptLanguage = Locale.preferredLanguages.prefix(6).enumerated().map { index, languageCode in
  86. let quality = 1.0 - (Double(index) * 0.1)
  87. return "\(languageCode);q=\(quality)"
  88. }.joined(separator: ", ")
  89. // User-Agent Header; see https://tools.ietf.org/html/rfc7231#section-5.5.3
  90. let userAgent: String = {
  91. if let info = Bundle.main.infoDictionary {
  92. let executable = info[kCFBundleExecutableKey as String] as? String ?? "Unknown"
  93. let bundle = info[kCFBundleIdentifierKey as String] as? String ?? "Unknown"
  94. let appVersion = info["CFBundleShortVersionString"] as? String ?? "Unknown"
  95. let appBuild = info[kCFBundleVersionKey as String] as? String ?? "Unknown"
  96. let osNameVersion: String = {
  97. let version = ProcessInfo.processInfo.operatingSystemVersion
  98. let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
  99. let osName: String = {
  100. #if os(iOS)
  101. return "iOS"
  102. #elseif os(watchOS)
  103. return "watchOS"
  104. #elseif os(tvOS)
  105. return "tvOS"
  106. #elseif os(OSX)
  107. return "OS X"
  108. #elseif os(Linux)
  109. return "Linux"
  110. #else
  111. return "Unknown"
  112. #endif
  113. }()
  114. return "\(osName) \(versionString)"
  115. }()
  116. return "\(executable)/\(bundle) (\(appVersion)/\(appBuild)); \(osNameVersion))"
  117. }
  118. return "Alamofire"
  119. }()
  120. return [
  121. "Accept-Encoding": acceptEncoding,
  122. "Accept-Language": acceptLanguage,
  123. "User-Agent": userAgent
  124. ]
  125. }()
  126. /// Default memory threshold used when encoding `MultipartFormData` in bytes.
  127. open static let multipartFormDataEncodingMemoryThreshold: UInt64 = 10_000_000
  128. /// The underlying session.
  129. open let session: URLSession
  130. /// The session delegate handling all the task and session delegate callbacks.
  131. open let delegate: SessionDelegate
  132. /// Whether to start requests immediately after being constructed. `true` by default.
  133. open var startRequestsImmediately: Bool = true
  134. /// The request adapter called each time a new request is created.
  135. open var adapter: RequestAdapter?
  136. /// The request retrier called each time a request encounters an error to determine whether to retry the request.
  137. open var retrier: RequestRetrier? {
  138. get { return delegate.retrier }
  139. set { delegate.retrier = newValue }
  140. }
  141. /// The background completion handler closure provided by the UIApplicationDelegate
  142. /// `application:handleEventsForBackgroundURLSession:completionHandler:` method. By setting the background
  143. /// completion handler, the SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` closure implementation
  144. /// will automatically call the handler.
  145. ///
  146. /// If you need to handle your own events before the handler is called, then you need to override the
  147. /// SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` and manually call the handler when finished.
  148. ///
  149. /// `nil` by default.
  150. open var backgroundCompletionHandler: (() -> Void)?
  151. let queue = DispatchQueue(label: "org.alamofire.session-manager." + NSUUID().uuidString)
  152. // MARK: - Lifecycle
  153. /// Creates an instance with the specified `configuration`, `delegate` and `serverTrustPolicyManager`.
  154. ///
  155. /// - parameter configuration: The configuration used to construct the managed session.
  156. /// `URLSessionConfiguration.default` by default.
  157. /// - parameter delegate: The delegate used when initializing the session. `SessionDelegate()` by
  158. /// default.
  159. /// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
  160. /// challenges. `nil` by default.
  161. ///
  162. /// - returns: The new `SessionManager` instance.
  163. public init(
  164. configuration: URLSessionConfiguration = URLSessionConfiguration.default,
  165. delegate: SessionDelegate = SessionDelegate(),
  166. serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
  167. {
  168. self.delegate = delegate
  169. self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
  170. commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
  171. }
  172. /// Creates an instance with the specified `session`, `delegate` and `serverTrustPolicyManager`.
  173. ///
  174. /// - parameter session: The URL session.
  175. /// - parameter delegate: The delegate of the URL session. Must equal the URL session's delegate.
  176. /// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
  177. /// challenges. `nil` by default.
  178. ///
  179. /// - returns: The new `SessionManager` instance if the URL session's delegate matches; `nil` otherwise.
  180. public init?(
  181. session: URLSession,
  182. delegate: SessionDelegate,
  183. serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
  184. {
  185. guard delegate === session.delegate else { return nil }
  186. self.delegate = delegate
  187. self.session = session
  188. commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
  189. }
  190. private func commonInit(serverTrustPolicyManager: ServerTrustPolicyManager?) {
  191. session.serverTrustPolicyManager = serverTrustPolicyManager
  192. delegate.sessionManager = self
  193. delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
  194. guard let strongSelf = self else { return }
  195. DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
  196. }
  197. }
  198. deinit {
  199. session.invalidateAndCancel()
  200. }
  201. // MARK: - Data Request
  202. /// Creates a data `Request` to retrieve the contents of a URL based on the specified `urlString`, `method`,
  203. /// `parameters`, `encoding` and `headers`.
  204. ///
  205. /// - parameter urlString: The URL string.
  206. /// - parameter method: The HTTP method.
  207. /// - parameter parameters: The parameters. `nil` by default.
  208. /// - parameter encoding: The parameter encoding. `.url` by default.
  209. /// - parameter headers: The HTTP headers. `nil` by default.
  210. ///
  211. /// - returns: The created data `Request`.
  212. @discardableResult
  213. open func request(
  214. _ urlString: URLStringConvertible,
  215. withMethod method: HTTPMethod,
  216. parameters: [String: Any]? = nil,
  217. encoding: ParameterEncoding = .url,
  218. headers: [String: String]? = nil)
  219. -> Request
  220. {
  221. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  222. let encodedURLRequest = encoding.encode(urlRequest, parameters: parameters).0
  223. return request(encodedURLRequest)
  224. }
  225. /// Creates a data `Request` to retrieve the contents of a URL based on the specified `urlRequest`.
  226. ///
  227. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  228. ///
  229. /// - parameter urlRequest: The URL request
  230. ///
  231. /// - returns: The created data `Request`.
  232. open func request(_ urlRequest: URLRequestConvertible) -> Request {
  233. let originalRequest = urlRequest.urlRequest
  234. let adaptedRequest = adapt(originalRequest)
  235. var dataTask: URLSessionDataTask!
  236. queue.sync { dataTask = self.session.dataTask(with: adaptedRequest) }
  237. let request = Request(session: session, task: dataTask, originalTask: .data(originalRequest))
  238. delegate[request.delegate.task] = request
  239. if startRequestsImmediately {
  240. request.resume()
  241. }
  242. return request
  243. }
  244. // MARK: - Download Request
  245. // MARK: URL Request
  246. /// Creates a download `Request` to retrieve the contents of a URL based on the specified `urlString`, `method`,
  247. /// `parameters`, `encoding`, `headers` and save them to the `destination`.
  248. ///
  249. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  250. ///
  251. /// - parameter urlString: The URL string.
  252. /// - parameter destination: The closure used to determine the destination of the downloaded file.
  253. /// - parameter method: The HTTP method.
  254. /// - parameter parameters: The parameters. `nil` by default.
  255. /// - parameter encoding: The parameter encoding. `.url` by default.
  256. /// - parameter headers: The HTTP headers. `nil` by default.
  257. ///
  258. /// - returns: The created download `Request`.
  259. @discardableResult
  260. open func download(
  261. _ urlString: URLStringConvertible,
  262. to destination: Request.DownloadFileDestination,
  263. withMethod method: HTTPMethod,
  264. parameters: [String: Any]? = nil,
  265. encoding: ParameterEncoding = .url,
  266. headers: [String: String]? = nil)
  267. -> Request
  268. {
  269. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  270. let encodedURLRequest = encoding.encode(urlRequest, parameters: parameters).0
  271. return download(encodedURLRequest, to: destination)
  272. }
  273. /// Creates a download `Request` to retrieve the contents of a URL based on the specified `urlRequest` and save
  274. /// them to the `destination`.
  275. ///
  276. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  277. ///
  278. /// - parameter urlRequest: The URL request
  279. /// - parameter destination: The closure used to determine the destination of the downloaded file.
  280. ///
  281. /// - returns: The created download `Request`.
  282. @discardableResult
  283. open func download(
  284. _ urlRequest: URLRequestConvertible,
  285. to destination: Request.DownloadFileDestination)
  286. -> Request
  287. {
  288. return download(.request(urlRequest.urlRequest), to: destination)
  289. }
  290. // MARK: Resume Data
  291. /// Creates a download `Request` from the `resumeData` produced from a previous request cancellation to retrieve
  292. /// the contents of the original request and save them to the `destination`.
  293. ///
  294. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  295. ///
  296. /// - parameter resumeData: The resume data. This is an opaque data blob produced by `URLSessionDownloadTask`
  297. /// when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for
  298. /// additional information.
  299. /// - parameter destination: The closure used to determine the destination of the downloaded file.
  300. ///
  301. /// - returns: The created download `Request`.
  302. @discardableResult
  303. open func download(resourceWithin resumeData: Data, to destination: Request.DownloadFileDestination) -> Request {
  304. return download(.resumeData(resumeData), to: destination)
  305. }
  306. // MARK: Private - Download Implementation
  307. private func download(_ downloadable: Downloadable, to destination: Request.DownloadFileDestination) -> Request {
  308. var downloadTask: URLSessionDownloadTask!
  309. switch downloadable {
  310. case let .request(urlRequest):
  311. let urlRequest = adapt(urlRequest)
  312. queue.sync { downloadTask = self.session.downloadTask(with: urlRequest) }
  313. case let .resumeData(resumeData):
  314. queue.sync { downloadTask = self.session.downloadTask(withResumeData: resumeData) }
  315. }
  316. let request = Request(session: session, task: downloadTask, originalTask: downloadable.task)
  317. if let downloadDelegate = request.delegate as? DownloadTaskDelegate {
  318. downloadDelegate.downloadTaskDidFinishDownloadingToURL = { session, downloadTask, URL in
  319. return destination(URL, downloadTask.response as! HTTPURLResponse)
  320. }
  321. }
  322. delegate[request.delegate.task] = request
  323. if startRequestsImmediately {
  324. request.resume()
  325. }
  326. return request
  327. }
  328. // MARK: - Upload Request
  329. // MARK: File
  330. /// Creates an upload `Request` from the specified `method`, `urlString` and `headers` for uploading the `file`.
  331. ///
  332. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  333. ///
  334. /// - parameter file: The file to upload.
  335. /// - parameter method: The HTTP method.
  336. /// - parameter urlString: The URL string.
  337. /// - parameter headers: The HTTP headers. `nil` by default.
  338. ///
  339. /// - returns: The created upload `Request`.
  340. @discardableResult
  341. open func upload(
  342. _ fileURL: URL,
  343. to urlString: URLStringConvertible,
  344. withMethod method: HTTPMethod,
  345. headers: [String: String]? = nil)
  346. -> Request
  347. {
  348. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  349. return upload(fileURL, with: urlRequest)
  350. }
  351. /// Creates a upload `Request` from the specified `urlRequest` for uploading the `file`.
  352. ///
  353. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  354. ///
  355. /// - parameter file: The file to upload.
  356. /// - parameter urlRequest: The URL request.
  357. ///
  358. /// - returns: The created upload `Request`.
  359. @discardableResult
  360. open func upload(_ fileURL: URL, with urlRequest: URLRequestConvertible) -> Request {
  361. return upload(.file(fileURL, urlRequest.urlRequest))
  362. }
  363. // MARK: Data
  364. /// Creates an upload `Request` from the specified `method`, `urlString` and `headers` for uploading the `data`.
  365. ///
  366. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  367. ///
  368. /// - parameter data: The data to upload.
  369. /// - parameter urlString: The URL string.
  370. /// - parameter method: The HTTP method.
  371. /// - parameter headers: The HTTP headers. `nil` by default.
  372. ///
  373. /// - returns: The created upload `Request`.
  374. @discardableResult
  375. open func upload(
  376. _ data: Data,
  377. to urlString: URLStringConvertible,
  378. withMethod method: HTTPMethod,
  379. headers: [String: String]? = nil)
  380. -> Request
  381. {
  382. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  383. return upload(data, with: urlRequest)
  384. }
  385. /// Creates an upload `Request` from the specified `urlRequest` for uploading the `data`.
  386. ///
  387. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  388. ///
  389. /// - parameter data: The data to upload.
  390. /// - parameter urlRequest: The URL request.
  391. ///
  392. /// - returns: The created upload `Request`.
  393. @discardableResult
  394. open func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> Request {
  395. return upload(.data(data, urlRequest.urlRequest))
  396. }
  397. // MARK: InputStream
  398. /// Creates an upload `Request` from the specified `method`, `urlString` and `headers` for uploading the `stream`.
  399. ///
  400. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  401. ///
  402. /// - parameter stream: The stream to upload.
  403. /// - parameter urlString: The URL string.
  404. /// - parameter method: The HTTP method.
  405. /// - parameter headers: The HTTP headers. `nil` by default.
  406. ///
  407. /// - returns: The created upload `Request`.
  408. @discardableResult
  409. open func upload(
  410. _ stream: InputStream,
  411. to urlString: URLStringConvertible,
  412. withMethod method: HTTPMethod,
  413. headers: [String: String]? = nil)
  414. -> Request
  415. {
  416. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  417. return upload(stream, with: urlRequest)
  418. }
  419. /// Creates an upload `Request` from the specified `urlRequest` for uploading the `stream`.
  420. ///
  421. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  422. ///
  423. /// - parameter stream: The stream to upload.
  424. /// - parameter urlRequest: The URL request.
  425. ///
  426. /// - returns: The created upload `Request`.
  427. @discardableResult
  428. open func upload(_ stream: InputStream, with urlRequest: URLRequestConvertible) -> Request {
  429. return upload(.stream(stream, urlRequest.urlRequest))
  430. }
  431. // MARK: MultipartFormData
  432. /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
  433. /// upload `Request` using the `method`, `urlString` and `headers`.
  434. ///
  435. /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  436. /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  437. /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  438. /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  439. /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  440. /// used for larger payloads such as video content.
  441. ///
  442. /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  443. /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  444. /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  445. /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  446. /// technique was used.
  447. ///
  448. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  449. ///
  450. /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
  451. /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
  452. /// `multipartFormDataEncodingMemoryThreshold` by default.
  453. /// - parameter urlString: The URL string.
  454. /// - parameter method: The HTTP method.
  455. /// - parameter headers: The HTTP headers. `nil` by default.
  456. /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
  457. open func upload(
  458. multipartFormData: @escaping (MultipartFormData) -> Void,
  459. usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
  460. to urlString: URLStringConvertible,
  461. withMethod method: HTTPMethod,
  462. headers: [String: String]? = nil,
  463. encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
  464. {
  465. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  466. return upload(
  467. multipartFormData: multipartFormData,
  468. usingThreshold: encodingMemoryThreshold,
  469. with: urlRequest,
  470. encodingCompletion: encodingCompletion
  471. )
  472. }
  473. /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
  474. /// upload `Request` using the `urlRequest`.
  475. ///
  476. /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  477. /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  478. /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  479. /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  480. /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  481. /// used for larger payloads such as video content.
  482. ///
  483. /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  484. /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  485. /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  486. /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  487. /// technique was used.
  488. ///
  489. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  490. ///
  491. /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
  492. /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
  493. /// `multipartFormDataEncodingMemoryThreshold` by default.
  494. /// - parameter urlRequest: The URL request.
  495. /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
  496. open func upload(
  497. multipartFormData: @escaping (MultipartFormData) -> Void,
  498. usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
  499. with urlRequest: URLRequestConvertible,
  500. encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
  501. {
  502. DispatchQueue.global(qos: .utility).async {
  503. let formData = MultipartFormData()
  504. multipartFormData(formData)
  505. var urlRequestWithContentType = urlRequest.urlRequest
  506. urlRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
  507. let isBackgroundSession = self.session.configuration.identifier != nil
  508. if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {
  509. do {
  510. let data = try formData.encode()
  511. let encodingResult = MultipartFormDataEncodingResult.success(
  512. request: self.upload(data, with: urlRequestWithContentType),
  513. streamingFromDisk: false,
  514. streamFileURL: nil
  515. )
  516. DispatchQueue.main.async { encodingCompletion?(encodingResult) }
  517. } catch {
  518. DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
  519. }
  520. } else {
  521. let fileManager = FileManager.default
  522. let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())
  523. let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data")
  524. let fileName = UUID().uuidString
  525. let fileURL = directoryURL.appendingPathComponent(fileName)
  526. do {
  527. try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
  528. try formData.writeEncodedData(to: fileURL)
  529. DispatchQueue.main.async {
  530. let encodingResult = MultipartFormDataEncodingResult.success(
  531. request: self.upload(fileURL, with: urlRequestWithContentType),
  532. streamingFromDisk: true,
  533. streamFileURL: fileURL
  534. )
  535. encodingCompletion?(encodingResult)
  536. }
  537. } catch {
  538. DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
  539. }
  540. }
  541. }
  542. }
  543. // MARK: Private - Upload Implementation
  544. private func upload(_ uploadable: Uploadable) -> Request {
  545. var uploadTask: URLSessionUploadTask!
  546. var HTTPBodyStream: InputStream?
  547. switch uploadable {
  548. case let .data(data, urlRequest):
  549. let urlRequest = adapt(urlRequest)
  550. queue.sync { uploadTask = self.session.uploadTask(with: urlRequest, from: data) }
  551. case let .file(fileURL, urlRequest):
  552. let urlRequest = adapt(urlRequest)
  553. queue.sync { uploadTask = self.session.uploadTask(with: urlRequest, fromFile: fileURL) }
  554. case let .stream(stream, urlRequest):
  555. let urlRequest = adapt(urlRequest)
  556. queue.sync { uploadTask = self.session.uploadTask(withStreamedRequest: urlRequest) }
  557. HTTPBodyStream = stream
  558. }
  559. let request = Request(session: session, task: uploadTask, originalTask: uploadable.task)
  560. if HTTPBodyStream != nil {
  561. request.delegate.taskNeedNewBodyStream = { _, _ in HTTPBodyStream }
  562. }
  563. delegate[request.delegate.task] = request
  564. if startRequestsImmediately {
  565. request.resume()
  566. }
  567. return request
  568. }
  569. #if !os(watchOS)
  570. // MARK: - Stream Request
  571. // MARK: Hostname and Port
  572. /// Creates a stream `Request` for bidirectional streaming using the `hostname` and `port`.
  573. ///
  574. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  575. ///
  576. /// - parameter hostName: The hostname of the server to connect to.
  577. /// - parameter port: The port of the server to connect to.
  578. ///
  579. /// - returns: The created stream `Request`.
  580. @discardableResult
  581. open func stream(withHostName hostName: String, port: Int) -> Request {
  582. return stream(.stream(hostName, port))
  583. }
  584. // MARK: NetService
  585. /// Creates a stream `Request` for bidirectional streaming using the `netService`.
  586. ///
  587. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  588. ///
  589. /// - parameter netService: The net service used to identify the endpoint.
  590. ///
  591. /// - returns: The created stream `Request`.
  592. @discardableResult
  593. open func stream(with netService: NetService) -> Request {
  594. return stream(.netService(netService))
  595. }
  596. // MARK: Private - Stream Implementation
  597. private func stream(_ streamable: Streamable) -> Request {
  598. var streamTask: URLSessionStreamTask!
  599. switch streamable {
  600. case .stream(let hostName, let port):
  601. queue.sync { streamTask = self.session.streamTask(withHostName: hostName, port: port) }
  602. case .netService(let netService):
  603. queue.sync { streamTask = self.session.streamTask(with: netService) }
  604. }
  605. let request = Request(session: session, task: streamTask, originalTask: nil)
  606. delegate[request.delegate.task] = request
  607. if startRequestsImmediately {
  608. request.resume()
  609. }
  610. return request
  611. }
  612. #endif
  613. // MARK: - Internal - Adapt Request
  614. func adapt(_ urlRequest: URLRequest) -> URLRequest {
  615. guard let adapter = adapter else { return urlRequest }
  616. return adapter.adapt(urlRequest)
  617. }
  618. // MARK: - Internal - Retry Request
  619. func retry(_ request: Request) -> Bool {
  620. guard let originalTask = request.originalTask else { return false }
  621. var task: URLSessionTask!
  622. queue.sync {
  623. switch originalTask {
  624. case let .data(urlRequest):
  625. let urlRequest = adapt(urlRequest)
  626. task = self.session.dataTask(with: urlRequest)
  627. case let .download(urlRequest):
  628. let urlRequest = adapt(urlRequest)
  629. task = self.session.downloadTask(with: urlRequest)
  630. case let .downloadResumeData(resumeData):
  631. task = self.session.downloadTask(withResumeData: resumeData)
  632. case let .uploadData(data, urlRequest):
  633. let urlRequest = adapt(urlRequest)
  634. task = self.session.uploadTask(with: urlRequest, from: data)
  635. case let .uploadFile(url, urlRequest):
  636. let urlRequest = adapt(urlRequest)
  637. task = self.session.uploadTask(with: urlRequest, fromFile: url)
  638. case let .uploadStream(_, urlRequest):
  639. let urlRequest = adapt(urlRequest)
  640. task = self.session.uploadTask(withStreamedRequest: urlRequest)
  641. }
  642. }
  643. request.delegate.task = task // resets all task delegate data
  644. request.startTime = CFAbsoluteTimeGetCurrent()
  645. request.endTime = nil
  646. task.resume()
  647. return true
  648. }
  649. }