SessionManager.swift 37 KB

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