SessionManager.swift 29 KB

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