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

465 lines
18 KiB

6 years ago
  1. //
  2. // Alamofire.swift
  3. //
  4. // Copyright (c) 2014-2018 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. /// Types adopting the `URLConvertible` protocol can be used to construct URLs, which are then used to construct
  26. /// URL requests.
  27. public protocol URLConvertible {
  28. /// Returns a URL that conforms to RFC 2396 or throws an `Error`.
  29. ///
  30. /// - throws: An `Error` if the type cannot be converted to a `URL`.
  31. ///
  32. /// - returns: A URL or throws an `Error`.
  33. func asURL() throws -> URL
  34. }
  35. extension String: URLConvertible {
  36. /// Returns a URL if `self` represents a valid URL string that conforms to RFC 2396 or throws an `AFError`.
  37. ///
  38. /// - throws: An `AFError.invalidURL` if `self` is not a valid URL string.
  39. ///
  40. /// - returns: A URL or throws an `AFError`.
  41. public func asURL() throws -> URL {
  42. guard let url = URL(string: self) else { throw AFError.invalidURL(url: self) }
  43. return url
  44. }
  45. }
  46. extension URL: URLConvertible {
  47. /// Returns self.
  48. public func asURL() throws -> URL { return self }
  49. }
  50. extension URLComponents: URLConvertible {
  51. /// Returns a URL if `url` is not nil, otherwise throws an `Error`.
  52. ///
  53. /// - throws: An `AFError.invalidURL` if `url` is `nil`.
  54. ///
  55. /// - returns: A URL or throws an `AFError`.
  56. public func asURL() throws -> URL {
  57. guard let url = url else { throw AFError.invalidURL(url: self) }
  58. return url
  59. }
  60. }
  61. // MARK: -
  62. /// Types adopting the `URLRequestConvertible` protocol can be used to construct URL requests.
  63. public protocol URLRequestConvertible {
  64. /// Returns a URL request or throws if an `Error` was encountered.
  65. ///
  66. /// - throws: An `Error` if the underlying `URLRequest` is `nil`.
  67. ///
  68. /// - returns: A URL request.
  69. func asURLRequest() throws -> URLRequest
  70. }
  71. extension URLRequestConvertible {
  72. /// The URL request.
  73. public var urlRequest: URLRequest? { return try? asURLRequest() }
  74. }
  75. extension URLRequest: URLRequestConvertible {
  76. /// Returns a URL request or throws if an `Error` was encountered.
  77. public func asURLRequest() throws -> URLRequest { return self }
  78. }
  79. // MARK: -
  80. extension URLRequest {
  81. /// Creates an instance with the specified `method`, `urlString` and `headers`.
  82. ///
  83. /// - parameter url: The URL.
  84. /// - parameter method: The HTTP method.
  85. /// - parameter headers: The HTTP headers. `nil` by default.
  86. ///
  87. /// - returns: The new `URLRequest` instance.
  88. public init(url: URLConvertible, method: HTTPMethod, headers: HTTPHeaders? = nil) throws {
  89. let url = try url.asURL()
  90. self.init(url: url)
  91. httpMethod = method.rawValue
  92. if let headers = headers {
  93. for (headerField, headerValue) in headers {
  94. setValue(headerValue, forHTTPHeaderField: headerField)
  95. }
  96. }
  97. }
  98. func adapt(using adapter: RequestAdapter?) throws -> URLRequest {
  99. guard let adapter = adapter else { return self }
  100. return try adapter.adapt(self)
  101. }
  102. }
  103. // MARK: - Data Request
  104. /// Creates a `DataRequest` using the default `SessionManager` to retrieve the contents of the specified `url`,
  105. /// `method`, `parameters`, `encoding` and `headers`.
  106. ///
  107. /// - parameter url: The URL.
  108. /// - parameter method: The HTTP method. `.get` by default.
  109. /// - parameter parameters: The parameters. `nil` by default.
  110. /// - parameter encoding: The parameter encoding. `URLEncoding.default` by default.
  111. /// - parameter headers: The HTTP headers. `nil` by default.
  112. ///
  113. /// - returns: The created `DataRequest`.
  114. @discardableResult
  115. public func request(
  116. _ url: URLConvertible,
  117. method: HTTPMethod = .get,
  118. parameters: Parameters? = nil,
  119. encoding: ParameterEncoding = URLEncoding.default,
  120. headers: HTTPHeaders? = nil)
  121. -> DataRequest
  122. {
  123. return SessionManager.default.request(
  124. url,
  125. method: method,
  126. parameters: parameters,
  127. encoding: encoding,
  128. headers: headers
  129. )
  130. }
  131. /// Creates a `DataRequest` using the default `SessionManager` to retrieve the contents of a URL based on the
  132. /// specified `urlRequest`.
  133. ///
  134. /// - parameter urlRequest: The URL request
  135. ///
  136. /// - returns: The created `DataRequest`.
  137. @discardableResult
  138. public func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
  139. return SessionManager.default.request(urlRequest)
  140. }
  141. // MARK: - Download Request
  142. // MARK: URL Request
  143. /// Creates a `DownloadRequest` using the default `SessionManager` to retrieve the contents of the specified `url`,
  144. /// `method`, `parameters`, `encoding`, `headers` and save them to the `destination`.
  145. ///
  146. /// If `destination` is not specified, the contents will remain in the temporary location determined by the
  147. /// underlying URL session.
  148. ///
  149. /// - parameter url: The URL.
  150. /// - parameter method: The HTTP method. `.get` by default.
  151. /// - parameter parameters: The parameters. `nil` by default.
  152. /// - parameter encoding: The parameter encoding. `URLEncoding.default` by default.
  153. /// - parameter headers: The HTTP headers. `nil` by default.
  154. /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
  155. ///
  156. /// - returns: The created `DownloadRequest`.
  157. @discardableResult
  158. public func download(
  159. _ url: URLConvertible,
  160. method: HTTPMethod = .get,
  161. parameters: Parameters? = nil,
  162. encoding: ParameterEncoding = URLEncoding.default,
  163. headers: HTTPHeaders? = nil,
  164. to destination: DownloadRequest.DownloadFileDestination? = nil)
  165. -> DownloadRequest
  166. {
  167. return SessionManager.default.download(
  168. url,
  169. method: method,
  170. parameters: parameters,
  171. encoding: encoding,
  172. headers: headers,
  173. to: destination
  174. )
  175. }
  176. /// Creates a `DownloadRequest` using the default `SessionManager` to retrieve the contents of a URL based on the
  177. /// specified `urlRequest` and save them to the `destination`.
  178. ///
  179. /// If `destination` is not specified, the contents will remain in the temporary location determined by the
  180. /// underlying URL session.
  181. ///
  182. /// - parameter urlRequest: The URL request.
  183. /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
  184. ///
  185. /// - returns: The created `DownloadRequest`.
  186. @discardableResult
  187. public func download(
  188. _ urlRequest: URLRequestConvertible,
  189. to destination: DownloadRequest.DownloadFileDestination? = nil)
  190. -> DownloadRequest
  191. {
  192. return SessionManager.default.download(urlRequest, to: destination)
  193. }
  194. // MARK: Resume Data
  195. /// Creates a `DownloadRequest` using the default `SessionManager` from the `resumeData` produced from a
  196. /// previous request cancellation to retrieve the contents of the original request and save them to the `destination`.
  197. ///
  198. /// If `destination` is not specified, the contents will remain in the temporary location determined by the
  199. /// underlying URL session.
  200. ///
  201. /// On the latest release of all the Apple platforms (iOS 10, macOS 10.12, tvOS 10, watchOS 3), `resumeData` is broken
  202. /// on background URL session configurations. There's an underlying bug in the `resumeData` generation logic where the
  203. /// data is written incorrectly and will always fail to resume the download. For more information about the bug and
  204. /// possible workarounds, please refer to the following Stack Overflow post:
  205. ///
  206. /// - http://stackoverflow.com/a/39347461/1342462
  207. ///
  208. /// - parameter resumeData: The resume data. This is an opaque data blob produced by `URLSessionDownloadTask`
  209. /// when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for additional
  210. /// information.
  211. /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
  212. ///
  213. /// - returns: The created `DownloadRequest`.
  214. @discardableResult
  215. public func download(
  216. resumingWith resumeData: Data,
  217. to destination: DownloadRequest.DownloadFileDestination? = nil)
  218. -> DownloadRequest
  219. {
  220. return SessionManager.default.download(resumingWith: resumeData, to: destination)
  221. }
  222. // MARK: - Upload Request
  223. // MARK: File
  224. /// Creates an `UploadRequest` using the default `SessionManager` from the specified `url`, `method` and `headers`
  225. /// for uploading the `file`.
  226. ///
  227. /// - parameter file: The file to upload.
  228. /// - parameter url: The URL.
  229. /// - parameter method: The HTTP method. `.post` by default.
  230. /// - parameter headers: The HTTP headers. `nil` by default.
  231. ///
  232. /// - returns: The created `UploadRequest`.
  233. @discardableResult
  234. public func upload(
  235. _ fileURL: URL,
  236. to url: URLConvertible,
  237. method: HTTPMethod = .post,
  238. headers: HTTPHeaders? = nil)
  239. -> UploadRequest
  240. {
  241. return SessionManager.default.upload(fileURL, to: url, method: method, headers: headers)
  242. }
  243. /// Creates a `UploadRequest` using the default `SessionManager` from the specified `urlRequest` for
  244. /// uploading the `file`.
  245. ///
  246. /// - parameter file: The file to upload.
  247. /// - parameter urlRequest: The URL request.
  248. ///
  249. /// - returns: The created `UploadRequest`.
  250. @discardableResult
  251. public func upload(_ fileURL: URL, with urlRequest: URLRequestConvertible) -> UploadRequest {
  252. return SessionManager.default.upload(fileURL, with: urlRequest)
  253. }
  254. // MARK: Data
  255. /// Creates an `UploadRequest` using the default `SessionManager` from the specified `url`, `method` and `headers`
  256. /// for uploading the `data`.
  257. ///
  258. /// - parameter data: The data to upload.
  259. /// - parameter url: The URL.
  260. /// - parameter method: The HTTP method. `.post` by default.
  261. /// - parameter headers: The HTTP headers. `nil` by default.
  262. ///
  263. /// - returns: The created `UploadRequest`.
  264. @discardableResult
  265. public func upload(
  266. _ data: Data,
  267. to url: URLConvertible,
  268. method: HTTPMethod = .post,
  269. headers: HTTPHeaders? = nil)
  270. -> UploadRequest
  271. {
  272. return SessionManager.default.upload(data, to: url, method: method, headers: headers)
  273. }
  274. /// Creates an `UploadRequest` using the default `SessionManager` from the specified `urlRequest` for
  275. /// uploading the `data`.
  276. ///
  277. /// - parameter data: The data to upload.
  278. /// - parameter urlRequest: The URL request.
  279. ///
  280. /// - returns: The created `UploadRequest`.
  281. @discardableResult
  282. public func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> UploadRequest {
  283. return SessionManager.default.upload(data, with: urlRequest)
  284. }
  285. // MARK: InputStream
  286. /// Creates an `UploadRequest` using the default `SessionManager` from the specified `url`, `method` and `headers`
  287. /// for uploading the `stream`.
  288. ///
  289. /// - parameter stream: The stream to upload.
  290. /// - parameter url: The URL.
  291. /// - parameter method: The HTTP method. `.post` by default.
  292. /// - parameter headers: The HTTP headers. `nil` by default.
  293. ///
  294. /// - returns: The created `UploadRequest`.
  295. @discardableResult
  296. public func upload(
  297. _ stream: InputStream,
  298. to url: URLConvertible,
  299. method: HTTPMethod = .post,
  300. headers: HTTPHeaders? = nil)
  301. -> UploadRequest
  302. {
  303. return SessionManager.default.upload(stream, to: url, method: method, headers: headers)
  304. }
  305. /// Creates an `UploadRequest` using the default `SessionManager` from the specified `urlRequest` for
  306. /// uploading the `stream`.
  307. ///
  308. /// - parameter urlRequest: The URL request.
  309. /// - parameter stream: The stream to upload.
  310. ///
  311. /// - returns: The created `UploadRequest`.
  312. @discardableResult
  313. public func upload(_ stream: InputStream, with urlRequest: URLRequestConvertible) -> UploadRequest {
  314. return SessionManager.default.upload(stream, with: urlRequest)
  315. }
  316. // MARK: MultipartFormData
  317. /// Encodes `multipartFormData` using `encodingMemoryThreshold` with the default `SessionManager` and calls
  318. /// `encodingCompletion` with new `UploadRequest` using the `url`, `method` and `headers`.
  319. ///
  320. /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  321. /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  322. /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  323. /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  324. /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  325. /// used for larger payloads such as video content.
  326. ///
  327. /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  328. /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  329. /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  330. /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  331. /// technique was used.
  332. ///
  333. /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
  334. /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
  335. /// `multipartFormDataEncodingMemoryThreshold` by default.
  336. /// - parameter url: The URL.
  337. /// - parameter method: The HTTP method. `.post` by default.
  338. /// - parameter headers: The HTTP headers. `nil` by default.
  339. /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
  340. public func upload(
  341. multipartFormData: @escaping (MultipartFormData) -> Void,
  342. usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
  343. to url: URLConvertible,
  344. method: HTTPMethod = .post,
  345. headers: HTTPHeaders? = nil,
  346. encodingCompletion: ((SessionManager.MultipartFormDataEncodingResult) -> Void)?)
  347. {
  348. return SessionManager.default.upload(
  349. multipartFormData: multipartFormData,
  350. usingThreshold: encodingMemoryThreshold,
  351. to: url,
  352. method: method,
  353. headers: headers,
  354. encodingCompletion: encodingCompletion
  355. )
  356. }
  357. /// Encodes `multipartFormData` using `encodingMemoryThreshold` and the default `SessionManager` and
  358. /// calls `encodingCompletion` with new `UploadRequest` using the `urlRequest`.
  359. ///
  360. /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  361. /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  362. /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  363. /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  364. /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  365. /// used for larger payloads such as video content.
  366. ///
  367. /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  368. /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  369. /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  370. /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  371. /// technique was used.
  372. ///
  373. /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
  374. /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
  375. /// `multipartFormDataEncodingMemoryThreshold` by default.
  376. /// - parameter urlRequest: The URL request.
  377. /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
  378. public func upload(
  379. multipartFormData: @escaping (MultipartFormData) -> Void,
  380. usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
  381. with urlRequest: URLRequestConvertible,
  382. encodingCompletion: ((SessionManager.MultipartFormDataEncodingResult) -> Void)?)
  383. {
  384. return SessionManager.default.upload(
  385. multipartFormData: multipartFormData,
  386. usingThreshold: encodingMemoryThreshold,
  387. with: urlRequest,
  388. encodingCompletion: encodingCompletion
  389. )
  390. }
  391. #if !os(watchOS)
  392. // MARK: - Stream Request
  393. // MARK: Hostname and Port
  394. /// Creates a `StreamRequest` using the default `SessionManager` for bidirectional streaming with the `hostname`
  395. /// and `port`.
  396. ///
  397. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  398. ///
  399. /// - parameter hostName: The hostname of the server to connect to.
  400. /// - parameter port: The port of the server to connect to.
  401. ///
  402. /// - returns: The created `StreamRequest`.
  403. @discardableResult
  404. @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
  405. public func stream(withHostName hostName: String, port: Int) -> StreamRequest {
  406. return SessionManager.default.stream(withHostName: hostName, port: port)
  407. }
  408. // MARK: NetService
  409. /// Creates a `StreamRequest` using the default `SessionManager` for bidirectional streaming with the `netService`.
  410. ///
  411. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  412. ///
  413. /// - parameter netService: The net service used to identify the endpoint.
  414. ///
  415. /// - returns: The created `StreamRequest`.
  416. @discardableResult
  417. @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
  418. public func stream(with netService: NetService) -> StreamRequest {
  419. return SessionManager.default.stream(with: netService)
  420. }
  421. #endif