SessionManager.swift 33 KB

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