2 // SessionManager.swift
4 // Copyright (c) 2014-2017 Alamofire Software Foundation (http://alamofire.org/)
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:
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
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
27 /// Responsible for creating and managing `Request` objects, as well as their underlying `NSURLSession`.
28 open class SessionManager {
30 // MARK: - Helper Types
32 /// Defines whether the `MultipartFormData` encoding was successful and contains result of the encoding as
33 /// associated values.
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
39 public enum MultipartFormDataEncodingResult {
40 case success(request: UploadRequest, streamingFromDisk: Bool, streamFileURL: URL?)
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
52 return SessionManager(configuration: configuration)
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"
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: ", ")
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"
75 let osNameVersion: String = {
76 let version = ProcessInfo.processInfo.operatingSystemVersion
77 let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
79 let osName: String = {
95 return "\(osName) \(versionString)"
98 let alamofireVersion: String = {
100 let afInfo = Bundle(for: SessionManager.self).infoDictionary,
101 let build = afInfo["CFBundleShortVersionString"]
102 else { return "Unknown" }
104 return "Alamofire/\(build)"
107 return "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)"
114 "Accept-Encoding": acceptEncoding,
115 "Accept-Language": acceptLanguage,
116 "User-Agent": userAgent
120 /// Default memory threshold used when encoding `MultipartFormData` in bytes.
121 open static let multipartFormDataEncodingMemoryThreshold: UInt64 = 10_000_000
123 /// The underlying session.
124 open let session: URLSession
126 /// The session delegate handling all the task and session delegate callbacks.
127 open let delegate: SessionDelegate
129 /// Whether to start requests immediately after being constructed. `true` by default.
130 open var startRequestsImmediately: Bool = true
132 /// The request adapter called each time a new request is created.
133 open var adapter: RequestAdapter?
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 }
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.
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.
149 /// `nil` by default.
150 open var backgroundCompletionHandler: (() -> Void)?
152 let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString)
156 /// Creates an instance with the specified `configuration`, `delegate` and `serverTrustPolicyManager`.
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
162 /// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
163 /// challenges. `nil` by default.
165 /// - returns: The new `SessionManager` instance.
167 configuration: URLSessionConfiguration = URLSessionConfiguration.default,
168 delegate: SessionDelegate = SessionDelegate(),
169 serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
171 self.delegate = delegate
172 self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
174 commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
177 /// Creates an instance with the specified `session`, `delegate` and `serverTrustPolicyManager`.
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.
184 /// - returns: The new `SessionManager` instance if the URL session's delegate matches; `nil` otherwise.
187 delegate: SessionDelegate,
188 serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
190 guard delegate === session.delegate else { return nil }
192 self.delegate = delegate
193 self.session = session
195 commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
198 private func commonInit(serverTrustPolicyManager: ServerTrustPolicyManager?) {
199 session.serverTrustPolicyManager = serverTrustPolicyManager
201 delegate.sessionManager = self
203 delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
204 guard let strongSelf = self else { return }
205 DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
210 session.invalidateAndCancel()
213 // MARK: - Data Request
215 /// Creates a `DataRequest` to retrieve the contents of the specified `url`, `method`, `parameters`, `encoding`
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.
224 /// - returns: The created `DataRequest`.
227 _ url: URLConvertible,
228 method: HTTPMethod = .get,
229 parameters: Parameters? = nil,
230 encoding: ParameterEncoding = URLEncoding.default,
231 headers: HTTPHeaders? = nil)
234 var originalRequest: URLRequest?
237 originalRequest = try URLRequest(url: url, method: method, headers: headers)
238 let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
239 return request(encodedURLRequest)
241 return request(originalRequest, failedWith: error)
245 /// Creates a `DataRequest` to retrieve the contents of a URL based on the specified `urlRequest`.
247 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
249 /// - parameter urlRequest: The URL request.
251 /// - returns: The created `DataRequest`.
253 open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
254 var originalRequest: URLRequest?
257 originalRequest = try urlRequest.asURLRequest()
258 let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
260 let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
261 let request = DataRequest(session: session, requestTask: .data(originalTask, task))
263 delegate[task] = request
265 if startRequestsImmediately { request.resume() }
269 return request(originalRequest, failedWith: error)
273 // MARK: Private - Request Implementation
275 private func request(_ urlRequest: URLRequest?, failedWith error: Error) -> DataRequest {
276 var requestTask: Request.RequestTask = .data(nil, nil)
278 if let urlRequest = urlRequest {
279 let originalTask = DataRequest.Requestable(urlRequest: urlRequest)
280 requestTask = .data(originalTask, nil)
283 let underlyingError = error.underlyingAdaptError ?? error
284 let request = DataRequest(session: session, requestTask: requestTask, error: underlyingError)
286 if let retrier = retrier, error is AdaptError {
287 allowRetrier(retrier, toRetry: request, with: underlyingError)
289 if startRequestsImmediately { request.resume() }
295 // MARK: - Download Request
299 /// Creates a `DownloadRequest` to retrieve the contents the specified `url`, `method`, `parameters`, `encoding`,
300 /// `headers` and save them to the `destination`.
302 /// If `destination` is not specified, the contents will remain in the temporary location determined by the
303 /// underlying URL session.
305 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
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.
314 /// - returns: The created `DownloadRequest`.
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)
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)
330 return download(nil, to: destination, failedWith: error)
334 /// Creates a `DownloadRequest` to retrieve the contents of a URL based on the specified `urlRequest` and save
335 /// them to the `destination`.
337 /// If `destination` is not specified, the contents will remain in the temporary location determined by the
338 /// underlying URL session.
340 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
342 /// - parameter urlRequest: The URL request
343 /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
345 /// - returns: The created `DownloadRequest`.
348 _ urlRequest: URLRequestConvertible,
349 to destination: DownloadRequest.DownloadFileDestination? = nil)
353 let urlRequest = try urlRequest.asURLRequest()
354 return download(.request(urlRequest), to: destination)
356 return download(nil, to: destination, failedWith: error)
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`.
365 /// If `destination` is not specified, the contents will remain in the temporary location determined by the
366 /// underlying URL session.
368 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
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:
375 /// - http://stackoverflow.com/a/39347461/1342462
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.
382 /// - returns: The created `DownloadRequest`.
385 resumingWith resumeData: Data,
386 to destination: DownloadRequest.DownloadFileDestination? = nil)
389 return download(.resumeData(resumeData), to: destination)
392 // MARK: Private - Download Implementation
394 private func download(
395 _ downloadable: DownloadRequest.Downloadable,
396 to destination: DownloadRequest.DownloadFileDestination?)
400 let task = try downloadable.task(session: session, adapter: adapter, queue: queue)
401 let download = DownloadRequest(session: session, requestTask: .download(downloadable, task))
403 download.downloadDelegate.destination = destination
405 delegate[task] = download
407 if startRequestsImmediately { download.resume() }
411 return download(downloadable, to: destination, failedWith: error)
415 private func download(
416 _ downloadable: DownloadRequest.Downloadable?,
417 to destination: DownloadRequest.DownloadFileDestination?,
418 failedWith error: Error)
421 var downloadTask: Request.RequestTask = .download(nil, nil)
423 if let downloadable = downloadable {
424 downloadTask = .download(downloadable, nil)
427 let underlyingError = error.underlyingAdaptError ?? error
429 let download = DownloadRequest(session: session, requestTask: downloadTask, error: underlyingError)
430 download.downloadDelegate.destination = destination
432 if let retrier = retrier, error is AdaptError {
433 allowRetrier(retrier, toRetry: download, with: underlyingError)
435 if startRequestsImmediately { download.resume() }
441 // MARK: - Upload Request
445 /// Creates an `UploadRequest` from the specified `url`, `method` and `headers` for uploading the `file`.
447 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
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.
454 /// - returns: The created `UploadRequest`.
458 to url: URLConvertible,
459 method: HTTPMethod = .post,
460 headers: HTTPHeaders? = nil)
464 let urlRequest = try URLRequest(url: url, method: method, headers: headers)
465 return upload(fileURL, with: urlRequest)
467 return upload(nil, failedWith: error)
471 /// Creates a `UploadRequest` from the specified `urlRequest` for uploading the `file`.
473 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
475 /// - parameter file: The file to upload.
476 /// - parameter urlRequest: The URL request.
478 /// - returns: The created `UploadRequest`.
480 open func upload(_ fileURL: URL, with urlRequest: URLRequestConvertible) -> UploadRequest {
482 let urlRequest = try urlRequest.asURLRequest()
483 return upload(.file(fileURL, urlRequest))
485 return upload(nil, failedWith: error)
491 /// Creates an `UploadRequest` from the specified `url`, `method` and `headers` for uploading the `data`.
493 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
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.
500 /// - returns: The created `UploadRequest`.
504 to url: URLConvertible,
505 method: HTTPMethod = .post,
506 headers: HTTPHeaders? = nil)
510 let urlRequest = try URLRequest(url: url, method: method, headers: headers)
511 return upload(data, with: urlRequest)
513 return upload(nil, failedWith: error)
517 /// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `data`.
519 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
521 /// - parameter data: The data to upload.
522 /// - parameter urlRequest: The URL request.
524 /// - returns: The created `UploadRequest`.
526 open func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> UploadRequest {
528 let urlRequest = try urlRequest.asURLRequest()
529 return upload(.data(data, urlRequest))
531 return upload(nil, failedWith: error)
537 /// Creates an `UploadRequest` from the specified `url`, `method` and `headers` for uploading the `stream`.
539 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
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.
546 /// - returns: The created `UploadRequest`.
549 _ stream: InputStream,
550 to url: URLConvertible,
551 method: HTTPMethod = .post,
552 headers: HTTPHeaders? = nil)
556 let urlRequest = try URLRequest(url: url, method: method, headers: headers)
557 return upload(stream, with: urlRequest)
559 return upload(nil, failedWith: error)
563 /// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `stream`.
565 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
567 /// - parameter stream: The stream to upload.
568 /// - parameter urlRequest: The URL request.
570 /// - returns: The created `UploadRequest`.
572 open func upload(_ stream: InputStream, with urlRequest: URLRequestConvertible) -> UploadRequest {
574 let urlRequest = try urlRequest.asURLRequest()
575 return upload(.stream(stream, urlRequest))
577 return upload(nil, failedWith: error)
581 // MARK: MultipartFormData
583 /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
584 /// `UploadRequest` using the `url`, `method` and `headers`.
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.
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.
599 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
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.
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)?)
617 let urlRequest = try URLRequest(url: url, method: method, headers: headers)
620 multipartFormData: multipartFormData,
621 usingThreshold: encodingMemoryThreshold,
623 encodingCompletion: encodingCompletion
626 DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
630 /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
631 /// `UploadRequest` using the `urlRequest`.
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.
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.
646 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
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.
654 multipartFormData: @escaping (MultipartFormData) -> Void,
655 usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
656 with urlRequest: URLRequestConvertible,
657 encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
659 DispatchQueue.global(qos: .utility).async {
660 let formData = MultipartFormData()
661 multipartFormData(formData)
663 var tempFileURL: URL?
666 var urlRequestWithContentType = try urlRequest.asURLRequest()
667 urlRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
669 let isBackgroundSession = self.session.configuration.identifier != nil
671 if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {
672 let data = try formData.encode()
674 let encodingResult = MultipartFormDataEncodingResult.success(
675 request: self.upload(data, with: urlRequestWithContentType),
676 streamingFromDisk: false,
680 DispatchQueue.main.async { encodingCompletion?(encodingResult) }
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)
688 tempFileURL = fileURL
690 var directoryError: Error?
692 // Create directory inside serial queue to ensure two threads don't do this in parallel
695 try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
697 directoryError = error
701 if let directoryError = directoryError { throw directoryError }
703 try formData.writeEncodedData(to: fileURL)
705 let upload = self.upload(fileURL, with: urlRequestWithContentType)
707 // Cleanup the temp file once the upload is complete
708 upload.delegate.queue.addOperation {
710 try FileManager.default.removeItem(at: fileURL)
716 DispatchQueue.main.async {
717 let encodingResult = MultipartFormDataEncodingResult.success(
719 streamingFromDisk: true,
720 streamFileURL: fileURL
723 encodingCompletion?(encodingResult)
727 // Cleanup the temp file in the event that the multipart form data encoding failed
728 if let tempFileURL = tempFileURL {
730 try FileManager.default.removeItem(at: tempFileURL)
736 DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
741 // MARK: Private - Upload Implementation
743 private func upload(_ uploadable: UploadRequest.Uploadable) -> UploadRequest {
745 let task = try uploadable.task(session: session, adapter: adapter, queue: queue)
746 let upload = UploadRequest(session: session, requestTask: .upload(uploadable, task))
748 if case let .stream(inputStream, _) = uploadable {
749 upload.delegate.taskNeedNewBodyStream = { _, _ in inputStream }
752 delegate[task] = upload
754 if startRequestsImmediately { upload.resume() }
758 return upload(uploadable, failedWith: error)
762 private func upload(_ uploadable: UploadRequest.Uploadable?, failedWith error: Error) -> UploadRequest {
763 var uploadTask: Request.RequestTask = .upload(nil, nil)
765 if let uploadable = uploadable {
766 uploadTask = .upload(uploadable, nil)
769 let underlyingError = error.underlyingAdaptError ?? error
770 let upload = UploadRequest(session: session, requestTask: uploadTask, error: underlyingError)
772 if let retrier = retrier, error is AdaptError {
773 allowRetrier(retrier, toRetry: upload, with: underlyingError)
775 if startRequestsImmediately { upload.resume() }
783 // MARK: - Stream Request
785 // MARK: Hostname and Port
787 /// Creates a `StreamRequest` for bidirectional streaming using the `hostname` and `port`.
789 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
791 /// - parameter hostName: The hostname of the server to connect to.
792 /// - parameter port: The port of the server to connect to.
794 /// - returns: The created `StreamRequest`.
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))
803 /// Creates a `StreamRequest` for bidirectional streaming using the `netService`.
805 /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
807 /// - parameter netService: The net service used to identify the endpoint.
809 /// - returns: The created `StreamRequest`.
811 @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
812 open func stream(with netService: NetService) -> StreamRequest {
813 return stream(.netService(netService))
816 // MARK: Private - Stream Implementation
818 @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
819 private func stream(_ streamable: StreamRequest.Streamable) -> StreamRequest {
821 let task = try streamable.task(session: session, adapter: adapter, queue: queue)
822 let request = StreamRequest(session: session, requestTask: .stream(streamable, task))
824 delegate[task] = request
826 if startRequestsImmediately { request.resume() }
830 return stream(failedWith: error)
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() }
843 // MARK: - Internal - Retry Request
845 func retry(_ request: Request) -> Bool {
846 guard let originalTask = request.originalTask else { return false }
849 let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
851 request.delegate.task = task // resets all task delegate data
853 request.retryCount += 1
854 request.startTime = CFAbsoluteTimeGetCurrent()
855 request.endTime = nil
861 request.delegate.error = error.underlyingAdaptError ?? error
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 }
870 retrier.should(strongSelf, retry: request, with: error) { shouldRetry, timeDelay in
871 guard let strongSelf = self else { return }
873 guard shouldRetry else {
874 if strongSelf.startRequestsImmediately { request.resume() }
878 DispatchQueue.utility.after(timeDelay) {
879 guard let strongSelf = self else { return }
881 let retrySucceeded = strongSelf.retry(request)
883 if retrySucceeded, let task = request.task {
884 strongSelf.delegate[task] = request
886 if strongSelf.startRequestsImmediately { request.resume() }