You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

899 lines
37 KiB

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