Request.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. //
  2. // Request.swift
  3. //
  4. // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. //
  24. import Foundation
  25. /**
  26. Responsible for sending a request and receiving the response and associated data from the server, as well as
  27. managing its underlying `NSURLSessionTask`.
  28. */
  29. public class Request {
  30. // MARK: Properties
  31. /// The delegate for the underlying task.
  32. public let delegate: TaskDelegate
  33. /// The underlying task.
  34. public var task: URLSessionTask { return delegate.task }
  35. /// The session belonging to the underlying task.
  36. public let session: URLSession
  37. /// The request sent or to be sent to the server.
  38. public var request: Foundation.URLRequest? { return task.originalRequest }
  39. /// The response received from the server, if any.
  40. public var response: HTTPURLResponse? { return task.response as? HTTPURLResponse }
  41. /// The progress of the request lifecycle.
  42. public var progress: Progress { return delegate.progress }
  43. var startTime: CFAbsoluteTime?
  44. var endTime: CFAbsoluteTime?
  45. // MARK: Lifecycle
  46. init(session: URLSession, task: URLSessionTask) {
  47. self.session = session
  48. switch task {
  49. case is URLSessionUploadTask:
  50. delegate = UploadTaskDelegate(task: task)
  51. case is URLSessionDataTask:
  52. delegate = DataTaskDelegate(task: task)
  53. case is URLSessionDownloadTask:
  54. delegate = DownloadTaskDelegate(task: task)
  55. default:
  56. delegate = TaskDelegate(task: task)
  57. }
  58. delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
  59. }
  60. // MARK: Authentication
  61. /**
  62. Associates an HTTP Basic credential with the request.
  63. - parameter user: The user.
  64. - parameter password: The password.
  65. - parameter persistence: The URL credential persistence. `.ForSession` by default.
  66. - returns: The request.
  67. */
  68. @discardableResult
  69. public func authenticate(
  70. user: String,
  71. password: String,
  72. persistence: URLCredential.Persistence = .forSession)
  73. -> Self
  74. {
  75. let credential = URLCredential(user: user, password: password, persistence: persistence)
  76. return authenticate(usingCredential: credential)
  77. }
  78. /**
  79. Associates a specified credential with the request.
  80. - parameter credential: The credential.
  81. - returns: The request.
  82. */
  83. @discardableResult
  84. public func authenticate(usingCredential credential: URLCredential) -> Self {
  85. delegate.credential = credential
  86. return self
  87. }
  88. /**
  89. Returns a base64 encoded basic authentication credential as an authorization header dictionary.
  90. - parameter user: The user.
  91. - parameter password: The password.
  92. - returns: A dictionary with Authorization key and credential value or empty dictionary if encoding fails.
  93. */
  94. public static func authorizationHeader(user: String, password: String) -> [String: String] {
  95. guard let data = "\(user):\(password)".data(using: String.Encoding.utf8) else { return [:] }
  96. let credential = data.base64EncodedString(options: [])
  97. return ["Authorization": "Basic \(credential)"]
  98. }
  99. // MARK: Progress
  100. /**
  101. Sets a closure to be called periodically during the lifecycle of the request as data is written to or read
  102. from the server.
  103. - For uploads, the progress closure returns the bytes written, total bytes written, and total bytes expected
  104. to write.
  105. - For downloads and data tasks, the progress closure returns the bytes read, total bytes read, and total bytes
  106. expected to read.
  107. - parameter closure: The code to be executed periodically during the lifecycle of the request.
  108. - returns: The request.
  109. */
  110. @discardableResult
  111. public func progress(_ closure: ((Int64, Int64, Int64) -> Void)? = nil) -> Self {
  112. if let uploadDelegate = delegate as? UploadTaskDelegate {
  113. uploadDelegate.uploadProgress = closure
  114. } else if let dataDelegate = delegate as? DataTaskDelegate {
  115. dataDelegate.dataProgress = closure
  116. } else if let downloadDelegate = delegate as? DownloadTaskDelegate {
  117. downloadDelegate.downloadProgress = closure
  118. }
  119. return self
  120. }
  121. /**
  122. Sets a closure to be called periodically during the lifecycle of the request as data is read from the server.
  123. This closure returns the bytes most recently received from the server, not including data from previous calls.
  124. If this closure is set, data will only be available within this closure, and will not be saved elsewhere. It is
  125. also important to note that the `response` closure will be called with nil `responseData`.
  126. - parameter closure: The code to be executed periodically during the lifecycle of the request.
  127. - returns: The request.
  128. */
  129. @discardableResult
  130. public func stream(_ closure: ((Data) -> Void)? = nil) -> Self {
  131. if let dataDelegate = delegate as? DataTaskDelegate {
  132. dataDelegate.dataStream = closure
  133. }
  134. return self
  135. }
  136. // MARK: State
  137. /**
  138. Resumes the request.
  139. */
  140. public func resume() {
  141. if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
  142. task.resume()
  143. NotificationCenter.default.post(
  144. name: Notification.Name.Task.DidResume,
  145. object: self,
  146. userInfo: [Notification.Key.Task: task]
  147. )
  148. }
  149. /**
  150. Suspends the request.
  151. */
  152. public func suspend() {
  153. task.suspend()
  154. NotificationCenter.default.post(
  155. name: Notification.Name.Task.DidSuspend,
  156. object: self,
  157. userInfo: [Notification.Key.Task: task]
  158. )
  159. }
  160. /**
  161. Cancels the request.
  162. */
  163. public func cancel() {
  164. if let downloadDelegate = delegate as? DownloadTaskDelegate,
  165. let downloadTask = downloadDelegate.downloadTask
  166. {
  167. downloadTask.cancel { data in
  168. downloadDelegate.resumeData = data
  169. }
  170. } else {
  171. task.cancel()
  172. }
  173. NotificationCenter.default.post(
  174. name: Notification.Name.Task.DidCancel,
  175. object: self,
  176. userInfo: [Notification.Key.Task: task]
  177. )
  178. }
  179. }
  180. // MARK: - CustomStringConvertible
  181. extension Request: CustomStringConvertible {
  182. /**
  183. The textual representation used when written to an output stream, which includes the HTTP method and URL, as
  184. well as the response status code if a response has been received.
  185. */
  186. public var description: String {
  187. var components: [String] = []
  188. if let HTTPMethod = request?.httpMethod {
  189. components.append(HTTPMethod)
  190. }
  191. if let URLString = request?.url?.absoluteString {
  192. components.append(URLString)
  193. }
  194. if let response = response {
  195. components.append("(\(response.statusCode))")
  196. }
  197. return components.joined(separator: " ")
  198. }
  199. }
  200. // MARK: - CustomDebugStringConvertible
  201. extension Request: CustomDebugStringConvertible {
  202. func cURLRepresentation() -> String {
  203. var components = ["$ curl -i"]
  204. guard let request = self.request,
  205. let URL = request.url,
  206. let host = URL.host
  207. else {
  208. return "$ curl command could not be created"
  209. }
  210. if let httpMethod = request.httpMethod, httpMethod != "GET" {
  211. components.append("-X \(httpMethod)")
  212. }
  213. if let credentialStorage = self.session.configuration.urlCredentialStorage {
  214. let protectionSpace = URLProtectionSpace(
  215. host: host,
  216. port: (URL as NSURL).port?.intValue ?? 0,
  217. protocol: URL.scheme,
  218. realm: host,
  219. authenticationMethod: NSURLAuthenticationMethodHTTPBasic
  220. )
  221. if let credentials = credentialStorage.credentials(for: protectionSpace)?.values {
  222. for credential in credentials {
  223. components.append("-u \(credential.user!):\(credential.password!)")
  224. }
  225. } else {
  226. if let credential = delegate.credential {
  227. components.append("-u \(credential.user!):\(credential.password!)")
  228. }
  229. }
  230. }
  231. if session.configuration.httpShouldSetCookies {
  232. if let cookieStorage = session.configuration.httpCookieStorage,
  233. let cookies = cookieStorage.cookies(for: URL), !cookies.isEmpty
  234. {
  235. let string = cookies.reduce("") { $0 + "\($1.name)=\($1.value ?? String());" }
  236. components.append("-b \"\(string.substring(to: string.characters.index(before: string.endIndex)))\"")
  237. }
  238. }
  239. var headers: [NSObject: AnyObject] = [:]
  240. if let additionalHeaders = session.configuration.httpAdditionalHeaders {
  241. for (field, value) in additionalHeaders where field != "Cookie" {
  242. headers[field] = value
  243. }
  244. }
  245. if let headerFields = request.allHTTPHeaderFields {
  246. for (field, value) in headerFields where field != "Cookie" {
  247. headers[field] = value
  248. }
  249. }
  250. for (field, value) in headers {
  251. components.append("-H \"\(field): \(value)\"")
  252. }
  253. if let httpBodyData = request.httpBody,
  254. let httpBody = String(data: httpBodyData, encoding: String.Encoding.utf8)
  255. {
  256. var escapedBody = httpBody.replacingOccurrences(of: "\\\"", with: "\\\\\"")
  257. escapedBody = escapedBody.replacingOccurrences(of: "\"", with: "\\\"")
  258. components.append("-d \"\(escapedBody)\"")
  259. }
  260. components.append("\"\(URL.absoluteString)\"")
  261. return components.joined(separator: " \\\n\t")
  262. }
  263. /// The textual representation used when written to an output stream, in the form of a cURL command.
  264. public var debugDescription: String {
  265. return cURLRepresentation()
  266. }
  267. }
  268. // MARK: - TaskDelegate
  269. extension Request {
  270. /**
  271. The task delegate is responsible for handling all delegate callbacks for the underlying task as well as
  272. executing all operations attached to the serial operation queue upon task completion.
  273. */
  274. public class TaskDelegate: NSObject {
  275. // MARK: Properties
  276. /// The serial operation queue used to execute all operations after the task completes.
  277. public let queue: OperationQueue
  278. let task: URLSessionTask
  279. let progress: Progress
  280. var data: Data? { return nil }
  281. var error: NSError?
  282. var initialResponseTime: CFAbsoluteTime?
  283. var credential: URLCredential?
  284. // MARK: Lifecycle
  285. init(task: URLSessionTask) {
  286. self.task = task
  287. self.progress = Progress(totalUnitCount: 0)
  288. self.queue = {
  289. let operationQueue = OperationQueue()
  290. operationQueue.maxConcurrentOperationCount = 1
  291. operationQueue.isSuspended = true
  292. operationQueue.qualityOfService = .utility
  293. return operationQueue
  294. }()
  295. }
  296. deinit {
  297. queue.cancelAllOperations()
  298. queue.isSuspended = false
  299. }
  300. // MARK: NSURLSessionTaskDelegate
  301. var taskWillPerformHTTPRedirection: ((Foundation.URLSession, URLSessionTask, HTTPURLResponse, Foundation.URLRequest) -> Foundation.URLRequest?)?
  302. var taskDidReceiveChallenge: ((Foundation.URLSession, URLSessionTask, URLAuthenticationChallenge) -> (Foundation.URLSession.AuthChallengeDisposition, URLCredential?))?
  303. var taskNeedNewBodyStream: ((Foundation.URLSession, URLSessionTask) -> InputStream?)?
  304. var taskDidCompleteWithError: ((Foundation.URLSession, URLSessionTask, NSError?) -> Void)?
  305. // RDAR
  306. @objc(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)
  307. func urlSession(
  308. _ session: Foundation.URLSession,
  309. task: URLSessionTask,
  310. willPerformHTTPRedirection response: HTTPURLResponse,
  311. newRequest request: Foundation.URLRequest,
  312. completionHandler: ((Foundation.URLRequest?) -> Void))
  313. {
  314. var redirectRequest: Foundation.URLRequest? = request
  315. if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection {
  316. redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request)
  317. }
  318. completionHandler(redirectRequest)
  319. }
  320. @objc(URLSession:task:didReceiveChallenge:completionHandler:)
  321. func urlSession(
  322. _ session: Foundation.URLSession,
  323. task: URLSessionTask,
  324. didReceive challenge: URLAuthenticationChallenge,
  325. completionHandler: ((Foundation.URLSession.AuthChallengeDisposition, URLCredential?) -> Void))
  326. {
  327. var disposition: Foundation.URLSession.AuthChallengeDisposition = .performDefaultHandling
  328. var credential: URLCredential?
  329. if let taskDidReceiveChallenge = taskDidReceiveChallenge {
  330. (disposition, credential) = taskDidReceiveChallenge(session, task, challenge)
  331. } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
  332. let host = challenge.protectionSpace.host
  333. if let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicyForHost(host),
  334. let serverTrust = challenge.protectionSpace.serverTrust
  335. {
  336. if serverTrustPolicy.evaluateServerTrust(serverTrust, isValidForHost: host) {
  337. disposition = .useCredential
  338. credential = URLCredential(trust: serverTrust)
  339. } else {
  340. disposition = .cancelAuthenticationChallenge
  341. }
  342. }
  343. } else {
  344. if challenge.previousFailureCount > 0 {
  345. disposition = .rejectProtectionSpace
  346. } else {
  347. credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
  348. if credential != nil {
  349. disposition = .useCredential
  350. }
  351. }
  352. }
  353. completionHandler(disposition, credential)
  354. }
  355. @objc(URLSession:task:needNewBodyStream:)
  356. func urlSession(
  357. _ session: Foundation.URLSession,
  358. task: URLSessionTask,
  359. needNewBodyStream completionHandler: ((InputStream?) -> Void))
  360. {
  361. var bodyStream: InputStream?
  362. if let taskNeedNewBodyStream = taskNeedNewBodyStream {
  363. bodyStream = taskNeedNewBodyStream(session, task)
  364. }
  365. completionHandler(bodyStream)
  366. }
  367. @objc(URLSession:task:didCompleteWithError:)
  368. func urlSession(_ session: Foundation.URLSession, task: URLSessionTask, didCompleteWithError error: NSError?) {
  369. if let taskDidCompleteWithError = taskDidCompleteWithError {
  370. taskDidCompleteWithError(session, task, error)
  371. } else {
  372. if let error = error {
  373. self.error = error
  374. if let downloadDelegate = self as? DownloadTaskDelegate,
  375. let userInfo = error.userInfo as? [String: AnyObject],
  376. let resumeData = userInfo[NSURLSessionDownloadTaskResumeData] as? Data
  377. {
  378. downloadDelegate.resumeData = resumeData
  379. }
  380. }
  381. queue.isSuspended = false
  382. }
  383. }
  384. }
  385. }
  386. // MARK: - DataTaskDelegate
  387. extension Request {
  388. class DataTaskDelegate: TaskDelegate, URLSessionDataDelegate {
  389. // MARK: Properties
  390. var dataTask: URLSessionDataTask? { return task as? URLSessionDataTask }
  391. override var data: Data? {
  392. if dataStream != nil {
  393. return nil
  394. } else {
  395. return mutableData as Data
  396. }
  397. }
  398. private var totalBytesReceived: Int64 = 0
  399. private var mutableData: Data
  400. private var expectedContentLength: Int64?
  401. private var dataProgress: ((bytesReceived: Int64, totalBytesReceived: Int64, totalBytesExpectedToReceive: Int64) -> Void)?
  402. private var dataStream: ((data: Data) -> Void)?
  403. // MARK: Lifecycle
  404. override init(task: URLSessionTask) {
  405. mutableData = Data()
  406. super.init(task: task)
  407. }
  408. // MARK: NSURLSessionDataDelegate
  409. var dataTaskDidReceiveResponse: ((Foundation.URLSession, URLSessionDataTask, URLResponse) -> Foundation.URLSession.ResponseDisposition)?
  410. var dataTaskDidBecomeDownloadTask: ((Foundation.URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)?
  411. var dataTaskDidReceiveData: ((Foundation.URLSession, URLSessionDataTask, Data) -> Void)?
  412. var dataTaskWillCacheResponse: ((Foundation.URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
  413. func urlSession(
  414. _ session: URLSession,
  415. dataTask: URLSessionDataTask,
  416. didReceive response: URLResponse,
  417. completionHandler: ((Foundation.URLSession.ResponseDisposition) -> Void))
  418. {
  419. var disposition: Foundation.URLSession.ResponseDisposition = .allow
  420. expectedContentLength = response.expectedContentLength
  421. if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
  422. disposition = dataTaskDidReceiveResponse(session, dataTask, response)
  423. }
  424. completionHandler(disposition)
  425. }
  426. func urlSession(
  427. _ session: URLSession,
  428. dataTask: URLSessionDataTask,
  429. didBecome downloadTask: URLSessionDownloadTask)
  430. {
  431. dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask)
  432. }
  433. func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
  434. if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
  435. if let dataTaskDidReceiveData = dataTaskDidReceiveData {
  436. dataTaskDidReceiveData(session, dataTask, data)
  437. } else {
  438. if let dataStream = dataStream {
  439. dataStream(data: data)
  440. } else {
  441. mutableData.append(data)
  442. }
  443. totalBytesReceived += data.count
  444. let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
  445. progress.totalUnitCount = totalBytesExpected
  446. progress.completedUnitCount = totalBytesReceived
  447. dataProgress?(
  448. bytesReceived: Int64(data.count),
  449. totalBytesReceived: totalBytesReceived,
  450. totalBytesExpectedToReceive: totalBytesExpected
  451. )
  452. }
  453. }
  454. func urlSession(
  455. _ session: URLSession,
  456. dataTask: URLSessionDataTask,
  457. willCacheResponse proposedResponse: CachedURLResponse,
  458. completionHandler: ((CachedURLResponse?) -> Void))
  459. {
  460. var cachedResponse: CachedURLResponse? = proposedResponse
  461. if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
  462. cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse)
  463. }
  464. completionHandler(cachedResponse)
  465. }
  466. }
  467. }