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