Request.swift 19 KB

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