SessionManager.swift 29 KB

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