SessionManager.swift 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  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." + NSUUID().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 adaptedRequest = originalRequest.adapt(using: adapter)
  202. var task: URLSessionDataTask!
  203. queue.sync { task = self.session.dataTask(with: adaptedRequest) }
  204. let originalTask = DataRequest.Requestable.request(originalRequest)
  205. let request = DataRequest(session: session, task: task, originalTask: originalTask)
  206. delegate[request.delegate.task] = request
  207. if startRequestsImmediately { request.resume() }
  208. return request
  209. }
  210. // MARK: - Download Request
  211. // MARK: URL Request
  212. /// Creates a `DownloadRequest` to retrieve the contents of a URL based on the specified `urlString`, `method`,
  213. /// `parameters`, `encoding`, `headers` and save them to the `destination`.
  214. ///
  215. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  216. ///
  217. /// - parameter urlString: The URL string.
  218. /// - parameter destination: The closure used to determine the destination of the downloaded file.
  219. /// - parameter method: The HTTP method.
  220. /// - parameter parameters: The parameters. `nil` by default.
  221. /// - parameter encoding: The parameter encoding. `.url` by default.
  222. /// - parameter headers: The HTTP headers. `nil` by default.
  223. ///
  224. /// - returns: The created `DownloadRequest`.
  225. @discardableResult
  226. open func download(
  227. _ urlString: URLStringConvertible,
  228. to destination: DownloadRequest.DownloadFileDestination,
  229. withMethod method: HTTPMethod,
  230. parameters: [String: Any]? = nil,
  231. encoding: ParameterEncoding = .url,
  232. headers: [String: String]? = nil)
  233. -> DownloadRequest
  234. {
  235. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  236. let encodedURLRequest = encoding.encode(urlRequest, parameters: parameters).0
  237. return download(encodedURLRequest, to: destination)
  238. }
  239. /// Creates a `DownloadRequest` to retrieve the contents of a URL based on the specified `urlRequest` and save
  240. /// them to the `destination`.
  241. ///
  242. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  243. ///
  244. /// - parameter urlRequest: The URL request
  245. /// - parameter destination: The closure used to determine the destination of the downloaded file.
  246. ///
  247. /// - returns: The created `DownloadRequest`.
  248. @discardableResult
  249. open func download(
  250. _ urlRequest: URLRequestConvertible,
  251. to destination: DownloadRequest.DownloadFileDestination)
  252. -> DownloadRequest
  253. {
  254. return download(.request(urlRequest.urlRequest), to: destination)
  255. }
  256. // MARK: Resume Data
  257. /// Creates a `DownloadRequest` from the `resumeData` produced from a previous request cancellation to retrieve
  258. /// the contents of the original request and save them to the `destination`.
  259. ///
  260. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  261. ///
  262. /// - parameter resumeData: The resume data. This is an opaque data blob produced by `URLSessionDownloadTask`
  263. /// when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for
  264. /// additional information.
  265. /// - parameter destination: The closure used to determine the destination of the downloaded file.
  266. ///
  267. /// - returns: The created `DownloadRequest`.
  268. @discardableResult
  269. open func download(
  270. resourceWithin resumeData: Data,
  271. to destination: DownloadRequest.DownloadFileDestination)
  272. -> DownloadRequest
  273. {
  274. return download(.resumeData(resumeData), to: destination)
  275. }
  276. // MARK: Private - Download Implementation
  277. private func download(
  278. _ downloadable: DownloadRequest.Downloadable,
  279. to destination: DownloadRequest.DownloadFileDestination)
  280. -> DownloadRequest
  281. {
  282. var task: URLSessionDownloadTask!
  283. switch downloadable {
  284. case let .request(urlRequest):
  285. let urlRequest = urlRequest.adapt(using: adapter)
  286. queue.sync { task = self.session.downloadTask(with: urlRequest) }
  287. case let .resumeData(resumeData):
  288. queue.sync { task = self.session.downloadTask(withResumeData: resumeData) }
  289. }
  290. let request = DownloadRequest(session: session, task: task, originalTask: downloadable)
  291. request.downloadDelegate.downloadTaskDidFinishDownloadingToURL = { session, task, URL in
  292. return destination(URL, task.response as! HTTPURLResponse)
  293. }
  294. delegate[request.delegate.task] = request
  295. if startRequestsImmediately { request.resume() }
  296. return request
  297. }
  298. // MARK: - Upload Request
  299. // MARK: File
  300. /// Creates an `UploadRequest` from the specified `method`, `urlString` and `headers` for uploading the `file`.
  301. ///
  302. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  303. ///
  304. /// - parameter file: The file to upload.
  305. /// - parameter method: The HTTP method.
  306. /// - parameter urlString: The URL string.
  307. /// - parameter headers: The HTTP headers. `nil` by default.
  308. ///
  309. /// - returns: The created `UploadRequest`.
  310. @discardableResult
  311. open func upload(
  312. _ fileURL: URL,
  313. to urlString: URLStringConvertible,
  314. withMethod method: HTTPMethod,
  315. headers: [String: String]? = nil)
  316. -> UploadRequest
  317. {
  318. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  319. return upload(fileURL, with: urlRequest)
  320. }
  321. /// Creates a `UploadRequest` from the specified `urlRequest` for uploading the `file`.
  322. ///
  323. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  324. ///
  325. /// - parameter file: The file to upload.
  326. /// - parameter urlRequest: The URL request.
  327. ///
  328. /// - returns: The created `UploadRequest`.
  329. @discardableResult
  330. open func upload(_ fileURL: URL, with urlRequest: URLRequestConvertible) -> UploadRequest {
  331. return upload(.file(fileURL, urlRequest.urlRequest))
  332. }
  333. // MARK: Data
  334. /// Creates an `UploadRequest` from the specified `method`, `urlString` and `headers` for uploading the `data`.
  335. ///
  336. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  337. ///
  338. /// - parameter data: The data to upload.
  339. /// - parameter urlString: The URL string.
  340. /// - parameter method: The HTTP method.
  341. /// - parameter headers: The HTTP headers. `nil` by default.
  342. ///
  343. /// - returns: The created `UploadRequest`.
  344. @discardableResult
  345. open func upload(
  346. _ data: Data,
  347. to urlString: URLStringConvertible,
  348. withMethod method: HTTPMethod,
  349. headers: [String: String]? = nil)
  350. -> UploadRequest
  351. {
  352. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  353. return upload(data, with: urlRequest)
  354. }
  355. /// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `data`.
  356. ///
  357. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  358. ///
  359. /// - parameter data: The data to upload.
  360. /// - parameter urlRequest: The URL request.
  361. ///
  362. /// - returns: The created `UploadRequest`.
  363. @discardableResult
  364. open func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> UploadRequest {
  365. return upload(.data(data, urlRequest.urlRequest))
  366. }
  367. // MARK: InputStream
  368. /// Creates an `UploadRequest` from the specified `method`, `urlString` and `headers` for uploading the `stream`.
  369. ///
  370. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  371. ///
  372. /// - parameter stream: The stream to upload.
  373. /// - parameter urlString: The URL string.
  374. /// - parameter method: The HTTP method.
  375. /// - parameter headers: The HTTP headers. `nil` by default.
  376. ///
  377. /// - returns: The created `UploadRequest`.
  378. @discardableResult
  379. open func upload(
  380. _ stream: InputStream,
  381. to urlString: URLStringConvertible,
  382. withMethod method: HTTPMethod,
  383. headers: [String: String]? = nil)
  384. -> UploadRequest
  385. {
  386. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  387. return upload(stream, with: urlRequest)
  388. }
  389. /// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `stream`.
  390. ///
  391. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  392. ///
  393. /// - parameter stream: The stream to upload.
  394. /// - parameter urlRequest: The URL request.
  395. ///
  396. /// - returns: The created `UploadRequest`.
  397. @discardableResult
  398. open func upload(_ stream: InputStream, with urlRequest: URLRequestConvertible) -> UploadRequest {
  399. return upload(.stream(stream, urlRequest.urlRequest))
  400. }
  401. // MARK: MultipartFormData
  402. /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
  403. /// `UploadRequest` using the `method`, `urlString` and `headers`.
  404. ///
  405. /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  406. /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  407. /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  408. /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  409. /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  410. /// used for larger payloads such as video content.
  411. ///
  412. /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  413. /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  414. /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  415. /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  416. /// technique was used.
  417. ///
  418. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  419. ///
  420. /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
  421. /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
  422. /// `multipartFormDataEncodingMemoryThreshold` by default.
  423. /// - parameter urlString: The URL string.
  424. /// - parameter method: The HTTP method.
  425. /// - parameter headers: The HTTP headers. `nil` by default.
  426. /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
  427. open func upload(
  428. multipartFormData: @escaping (MultipartFormData) -> Void,
  429. usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
  430. to urlString: URLStringConvertible,
  431. withMethod method: HTTPMethod,
  432. headers: [String: String]? = nil,
  433. encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
  434. {
  435. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  436. return upload(
  437. multipartFormData: multipartFormData,
  438. usingThreshold: encodingMemoryThreshold,
  439. with: urlRequest,
  440. encodingCompletion: encodingCompletion
  441. )
  442. }
  443. /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
  444. /// `UploadRequest` using the `urlRequest`.
  445. ///
  446. /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  447. /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  448. /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  449. /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  450. /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  451. /// used for larger payloads such as video content.
  452. ///
  453. /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  454. /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  455. /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  456. /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  457. /// technique was used.
  458. ///
  459. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  460. ///
  461. /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
  462. /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
  463. /// `multipartFormDataEncodingMemoryThreshold` by default.
  464. /// - parameter urlRequest: The URL request.
  465. /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
  466. open func upload(
  467. multipartFormData: @escaping (MultipartFormData) -> Void,
  468. usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
  469. with urlRequest: URLRequestConvertible,
  470. encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
  471. {
  472. DispatchQueue.global(qos: .utility).async {
  473. let formData = MultipartFormData()
  474. multipartFormData(formData)
  475. var urlRequestWithContentType = urlRequest.urlRequest
  476. urlRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
  477. let isBackgroundSession = self.session.configuration.identifier != nil
  478. if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {
  479. do {
  480. let data = try formData.encode()
  481. let encodingResult = MultipartFormDataEncodingResult.success(
  482. request: self.upload(data, with: urlRequestWithContentType),
  483. streamingFromDisk: false,
  484. streamFileURL: nil
  485. )
  486. DispatchQueue.main.async { encodingCompletion?(encodingResult) }
  487. } catch {
  488. DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
  489. }
  490. } else {
  491. let fileManager = FileManager.default
  492. let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())
  493. let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data")
  494. let fileName = UUID().uuidString
  495. let fileURL = directoryURL.appendingPathComponent(fileName)
  496. do {
  497. try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
  498. try formData.writeEncodedData(to: fileURL)
  499. DispatchQueue.main.async {
  500. let encodingResult = MultipartFormDataEncodingResult.success(
  501. request: self.upload(fileURL, with: urlRequestWithContentType),
  502. streamingFromDisk: true,
  503. streamFileURL: fileURL
  504. )
  505. encodingCompletion?(encodingResult)
  506. }
  507. } catch {
  508. DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
  509. }
  510. }
  511. }
  512. }
  513. // MARK: Private - Upload Implementation
  514. private func upload(_ uploadable: UploadRequest.Uploadable) -> UploadRequest {
  515. var task: URLSessionUploadTask!
  516. var HTTPBodyStream: InputStream?
  517. switch uploadable {
  518. case let .data(data, urlRequest):
  519. let urlRequest = urlRequest.adapt(using: adapter)
  520. queue.sync { task = self.session.uploadTask(with: urlRequest, from: data) }
  521. case let .file(fileURL, urlRequest):
  522. let urlRequest = urlRequest.adapt(using: adapter)
  523. queue.sync { task = self.session.uploadTask(with: urlRequest, fromFile: fileURL) }
  524. case let .stream(stream, urlRequest):
  525. let urlRequest = urlRequest.adapt(using: adapter)
  526. queue.sync { task = self.session.uploadTask(withStreamedRequest: urlRequest) }
  527. HTTPBodyStream = stream
  528. }
  529. let request = UploadRequest(session: session, task: task, originalTask: uploadable)
  530. if HTTPBodyStream != nil {
  531. request.delegate.taskNeedNewBodyStream = { _, _ in HTTPBodyStream }
  532. }
  533. delegate[request.delegate.task] = request
  534. if startRequestsImmediately { request.resume() }
  535. return request
  536. }
  537. #if !os(watchOS)
  538. // MARK: - Stream Request
  539. // MARK: Hostname and Port
  540. /// Creates a `StreamRequest` for bidirectional streaming using the `hostname` and `port`.
  541. ///
  542. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  543. ///
  544. /// - parameter hostName: The hostname of the server to connect to.
  545. /// - parameter port: The port of the server to connect to.
  546. ///
  547. /// - returns: The created `StreamRequest`.
  548. @discardableResult
  549. open func stream(withHostName hostName: String, port: Int) -> StreamRequest {
  550. return stream(.stream(hostName, port))
  551. }
  552. // MARK: NetService
  553. /// Creates a `StreamRequest` for bidirectional streaming using the `netService`.
  554. ///
  555. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  556. ///
  557. /// - parameter netService: The net service used to identify the endpoint.
  558. ///
  559. /// - returns: The created `StreamRequest`.
  560. @discardableResult
  561. open func stream(with netService: NetService) -> StreamRequest {
  562. return stream(.netService(netService))
  563. }
  564. // MARK: Private - Stream Implementation
  565. private func stream(_ streamable: StreamRequest.Streamable) -> StreamRequest {
  566. var task: URLSessionStreamTask!
  567. switch streamable {
  568. case let .stream(hostName, port):
  569. queue.sync { task = self.session.streamTask(withHostName: hostName, port: port) }
  570. case .netService(let netService):
  571. queue.sync { task = self.session.streamTask(with: netService) }
  572. }
  573. let request = StreamRequest(session: session, task: task, originalTask: streamable)
  574. delegate[request.delegate.task] = request
  575. if startRequestsImmediately { request.resume() }
  576. return request
  577. }
  578. #endif
  579. // MARK: - Internal - Retry Request
  580. func retry(_ request: Request) -> Bool {
  581. guard let originalTask = request.originalTask else { return false }
  582. let task = originalTask.task(session: session, adapter: adapter, queue: queue)
  583. request.delegate.task = task // resets all task delegate data
  584. request.startTime = CFAbsoluteTimeGetCurrent()
  585. request.endTime = nil
  586. task.resume()
  587. return true
  588. }
  589. }