Request.swift 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. // Alamofire.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 managing its underlying `NSURLSessionTask`.
  25. */
  26. public class Request {
  27. // MARK: - Properties
  28. let delegate: TaskDelegate
  29. /// The underlying task.
  30. public var task: NSURLSessionTask { return delegate.task }
  31. /// The session belonging to the underlying task.
  32. public let session: NSURLSession
  33. /// The request sent or to be sent to the server.
  34. public var request: NSURLRequest { return task.originalRequest }
  35. /// The response received from the server, if any.
  36. public var response: NSHTTPURLResponse? { return task.response as? NSHTTPURLResponse }
  37. /// The progress of the request lifecycle.
  38. public var progress: NSProgress { return delegate.progress }
  39. // MARK: - Lifecycle
  40. init(session: NSURLSession, task: NSURLSessionTask) {
  41. self.session = session
  42. switch task {
  43. case is NSURLSessionUploadTask:
  44. self.delegate = UploadTaskDelegate(task: task)
  45. case is NSURLSessionDataTask:
  46. self.delegate = DataTaskDelegate(task: task)
  47. case is NSURLSessionDownloadTask:
  48. self.delegate = DownloadTaskDelegate(task: task)
  49. default:
  50. self.delegate = TaskDelegate(task: task)
  51. }
  52. }
  53. // MARK: - Authentication
  54. /**
  55. Associates an HTTP Basic credential with the request.
  56. :param: user The user.
  57. :param: password The password.
  58. :returns: The request.
  59. */
  60. public func authenticate(#user: String, password: String) -> Self {
  61. let credential = NSURLCredential(user: user, password: password, persistence: .ForSession)
  62. return authenticate(usingCredential: credential)
  63. }
  64. /**
  65. Associates a specified credential with the request.
  66. :param: credential The credential.
  67. :returns: The request.
  68. */
  69. public func authenticate(usingCredential credential: NSURLCredential) -> Self {
  70. delegate.credential = credential
  71. return self
  72. }
  73. // MARK: - Progress
  74. /**
  75. Sets a closure to be called periodically during the lifecycle of the request as data is written to or read from the server.
  76. - For uploads, the progress closure returns the bytes written, total bytes written, and total bytes expected to write.
  77. - For downloads and data tasks, the progress closure returns the bytes read, total bytes read, and total bytes expected to read.
  78. :param: closure The code to be executed periodically during the lifecycle of the request.
  79. :returns: The request.
  80. */
  81. public func progress(closure: ((Int64, Int64, Int64) -> Void)? = nil) -> Self {
  82. if let uploadDelegate = delegate as? UploadTaskDelegate {
  83. uploadDelegate.uploadProgress = closure
  84. } else if let dataDelegate = delegate as? DataTaskDelegate {
  85. dataDelegate.dataProgress = closure
  86. } else if let downloadDelegate = delegate as? DownloadTaskDelegate {
  87. downloadDelegate.downloadProgress = closure
  88. }
  89. return self
  90. }
  91. // MARK: - Response
  92. /**
  93. A closure used by response handlers that takes a request, response, and data and returns a serialized object and any error that occured in the process.
  94. */
  95. public typealias Serializer = (NSURLRequest, NSHTTPURLResponse?, NSData?) -> (AnyObject?, NSError?)
  96. /**
  97. Creates a response serializer that returns the associated data as-is.
  98. :returns: A data response serializer.
  99. */
  100. public class func responseDataSerializer() -> Serializer {
  101. return { (request, response, data) in
  102. return (data, nil)
  103. }
  104. }
  105. /**
  106. Adds a handler to be called once the request has finished.
  107. :param: completionHandler The code to be executed once the request has finished.
  108. :returns: The request.
  109. */
  110. public func response(completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
  111. return response(serializer: Request.responseDataSerializer(), completionHandler: completionHandler)
  112. }
  113. /**
  114. Adds a handler to be called once the request has finished.
  115. :param: queue The queue on which the completion handler is dispatched.
  116. :param: serializer The closure responsible for serializing the request, response, and data.
  117. :param: completionHandler The code to be executed once the request has finished.
  118. :returns: The request.
  119. */
  120. public func response(queue: dispatch_queue_t? = nil, serializer: Serializer, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
  121. delegate.queue.addOperationWithBlock {
  122. let (responseObject: AnyObject?, serializationError: NSError?) = serializer(self.request, self.response, self.delegate.data)
  123. dispatch_async(queue ?? dispatch_get_main_queue()) {
  124. completionHandler(self.request, self.response, responseObject, self.delegate.error ?? serializationError)
  125. }
  126. }
  127. return self
  128. }
  129. // MARK: - State
  130. /**
  131. Suspends the request.
  132. */
  133. public func suspend() {
  134. task.suspend()
  135. }
  136. /**
  137. Resumes the request.
  138. */
  139. public func resume() {
  140. task.resume()
  141. }
  142. /**
  143. Cancels the request.
  144. */
  145. public func cancel() {
  146. if let downloadDelegate = delegate as? DownloadTaskDelegate {
  147. downloadDelegate.downloadTask.cancelByProducingResumeData { (data) in
  148. downloadDelegate.resumeData = data
  149. }
  150. } else {
  151. task.cancel()
  152. }
  153. }
  154. // MARK: - TaskDelegate
  155. class TaskDelegate: NSObject, NSURLSessionTaskDelegate {
  156. let task: NSURLSessionTask
  157. let queue: NSOperationQueue
  158. let progress: NSProgress
  159. var data: NSData? { return nil }
  160. var error: NSError?
  161. var credential: NSURLCredential?
  162. var taskWillPerformHTTPRedirection: ((NSURLSession!, NSURLSessionTask!, NSHTTPURLResponse!, NSURLRequest!) -> (NSURLRequest!))?
  163. var taskDidReceiveChallenge: ((NSURLSession!, NSURLSessionTask!, NSURLAuthenticationChallenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential?))?
  164. var taskDidSendBodyData: ((NSURLSession!, NSURLSessionTask!, Int64, Int64, Int64) -> Void)?
  165. var taskNeedNewBodyStream: ((NSURLSession!, NSURLSessionTask!) -> (NSInputStream!))?
  166. init(task: NSURLSessionTask) {
  167. self.task = task
  168. self.progress = NSProgress(totalUnitCount: 0)
  169. self.queue = {
  170. let operationQueue = NSOperationQueue()
  171. operationQueue.maxConcurrentOperationCount = 1
  172. operationQueue.suspended = true
  173. if operationQueue.respondsToSelector("qualityOfService") {
  174. operationQueue.qualityOfService = NSQualityOfService.Utility
  175. }
  176. return operationQueue
  177. }()
  178. }
  179. deinit {
  180. queue.cancelAllOperations()
  181. queue.suspended = true
  182. }
  183. // MARK: - NSURLSessionTaskDelegate
  184. func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: ((NSURLRequest!) -> Void)) {
  185. var redirectRequest = request
  186. if taskWillPerformHTTPRedirection != nil {
  187. redirectRequest = taskWillPerformHTTPRedirection!(session, task, response, request)
  188. }
  189. completionHandler(redirectRequest)
  190. }
  191. func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void)) {
  192. var disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling
  193. var credential: NSURLCredential?
  194. if taskDidReceiveChallenge != nil {
  195. (disposition, credential) = taskDidReceiveChallenge!(session, task, challenge)
  196. } else {
  197. if challenge.previousFailureCount > 0 {
  198. disposition = .CancelAuthenticationChallenge
  199. } else {
  200. credential = self.credential ?? session.configuration.URLCredentialStorage?.defaultCredentialForProtectionSpace(challenge.protectionSpace)
  201. if credential != nil {
  202. disposition = .UseCredential
  203. }
  204. }
  205. }
  206. completionHandler(disposition, credential)
  207. }
  208. func URLSession(session: NSURLSession, task: NSURLSessionTask, needNewBodyStream completionHandler: ((NSInputStream!) -> Void)) {
  209. var bodyStream: NSInputStream?
  210. if taskNeedNewBodyStream != nil {
  211. bodyStream = taskNeedNewBodyStream!(session, task)
  212. }
  213. completionHandler(bodyStream)
  214. }
  215. func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
  216. if error != nil {
  217. self.error = error
  218. }
  219. queue.suspended = false
  220. }
  221. }
  222. // MARK: - DataTaskDelegate
  223. class DataTaskDelegate: TaskDelegate, NSURLSessionDataDelegate {
  224. var dataTask: NSURLSessionDataTask! { return task as! NSURLSessionDataTask }
  225. private var mutableData: NSMutableData
  226. override var data: NSData? {
  227. return mutableData
  228. }
  229. private var expectedContentLength: Int64?
  230. var dataTaskDidReceiveResponse: ((NSURLSession!, NSURLSessionDataTask!, NSURLResponse!) -> (NSURLSessionResponseDisposition))?
  231. var dataTaskDidBecomeDownloadTask: ((NSURLSession!, NSURLSessionDataTask!) -> Void)?
  232. var dataTaskDidReceiveData: ((NSURLSession!, NSURLSessionDataTask!, NSData!) -> Void)?
  233. var dataTaskWillCacheResponse: ((NSURLSession!, NSURLSessionDataTask!, NSCachedURLResponse!) -> (NSCachedURLResponse))?
  234. var dataProgress: ((bytesReceived: Int64, totalBytesReceived: Int64, totalBytesExpectedToReceive: Int64) -> Void)?
  235. override init(task: NSURLSessionTask) {
  236. self.mutableData = NSMutableData()
  237. super.init(task: task)
  238. }
  239. // MARK: - NSURLSessionDataDelegate
  240. func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: ((NSURLSessionResponseDisposition) -> Void)) {
  241. var disposition: NSURLSessionResponseDisposition = .Allow
  242. expectedContentLength = response.expectedContentLength
  243. if dataTaskDidReceiveResponse != nil {
  244. disposition = dataTaskDidReceiveResponse!(session, dataTask, response)
  245. }
  246. completionHandler(disposition)
  247. }
  248. func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didBecomeDownloadTask downloadTask: NSURLSessionDownloadTask) {
  249. dataTaskDidBecomeDownloadTask?(session, dataTask)
  250. }
  251. func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
  252. dataTaskDidReceiveData?(session, dataTask, data)
  253. mutableData.appendData(data)
  254. let totalBytesReceived = Int64(mutableData.length)
  255. let totalBytesExpectedToReceive = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
  256. progress.totalUnitCount = totalBytesExpectedToReceive
  257. progress.completedUnitCount = totalBytesReceived
  258. dataProgress?(bytesReceived: Int64(data.length), totalBytesReceived: totalBytesReceived, totalBytesExpectedToReceive: totalBytesExpectedToReceive)
  259. }
  260. func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: ((NSCachedURLResponse!) -> Void)) {
  261. var cachedResponse = proposedResponse
  262. if dataTaskWillCacheResponse != nil {
  263. cachedResponse = dataTaskWillCacheResponse!(session, dataTask, proposedResponse)
  264. }
  265. completionHandler(cachedResponse)
  266. }
  267. }
  268. }
  269. // MARK: - Printable
  270. extension Request: Printable {
  271. /// The textual representation used when written to an output stream, which includes the HTTP method and URL, as well as the response status code if a response has been received.
  272. public var description: String {
  273. var components: [String] = []
  274. if request.HTTPMethod != nil {
  275. components.append(request.HTTPMethod!)
  276. }
  277. components.append(request.URL!.absoluteString!)
  278. if response != nil {
  279. components.append("(\(response!.statusCode))")
  280. }
  281. return join(" ", components)
  282. }
  283. }
  284. // MARK: - DebugPrintable
  285. extension Request: DebugPrintable {
  286. func cURLRepresentation() -> String {
  287. var components: [String] = ["$ curl -i"]
  288. let URL = request.URL
  289. if request.HTTPMethod != nil && request.HTTPMethod != "GET" {
  290. components.append("-X \(request.HTTPMethod!)")
  291. }
  292. if let credentialStorage = self.session.configuration.URLCredentialStorage {
  293. let protectionSpace = NSURLProtectionSpace(host: URL!.host!, port: URL!.port?.integerValue ?? 0, `protocol`: URL!.scheme!, realm: URL!.host!, authenticationMethod: NSURLAuthenticationMethodHTTPBasic)
  294. if let credentials = credentialStorage.credentialsForProtectionSpace(protectionSpace)?.values.array {
  295. for credential: NSURLCredential in (credentials as! [NSURLCredential]) {
  296. components.append("-u \(credential.user!):\(credential.password!)")
  297. }
  298. } else {
  299. if let credential = delegate.credential {
  300. components.append("-u \(credential.user!):\(credential.password!)")
  301. }
  302. }
  303. }
  304. // Temporarily disabled on OS X due to build failure for CocoaPods
  305. // See https://github.com/CocoaPods/swift/issues/24
  306. #if !os(OSX)
  307. if let cookieStorage = session.configuration.HTTPCookieStorage,
  308. cookies = cookieStorage.cookiesForURL(URL!) as? [NSHTTPCookie]
  309. where !cookies.isEmpty
  310. {
  311. let string = cookies.reduce(""){ $0 + "\($1.name)=\($1.value ?? String());" }
  312. components.append("-b \"\(string.substringToIndex(string.endIndex.predecessor()))\"")
  313. }
  314. #endif
  315. if request.allHTTPHeaderFields != nil {
  316. for (field, value) in request.allHTTPHeaderFields! {
  317. switch field {
  318. case "Cookie":
  319. continue
  320. default:
  321. components.append("-H \"\(field): \(value)\"")
  322. }
  323. }
  324. }
  325. if session.configuration.HTTPAdditionalHeaders != nil {
  326. for (field, value) in session.configuration.HTTPAdditionalHeaders! {
  327. switch field {
  328. case "Cookie":
  329. continue
  330. default:
  331. components.append("-H \"\(field): \(value)\"")
  332. }
  333. }
  334. }
  335. if let HTTPBody = request.HTTPBody,
  336. escapedBody = NSString(data: HTTPBody, encoding: NSUTF8StringEncoding)?.stringByReplacingOccurrencesOfString("\"", withString: "\\\"")
  337. {
  338. components.append("-d \"\(escapedBody)\"")
  339. }
  340. components.append("\"\(URL!.absoluteString!)\"")
  341. return join(" \\\n\t", components)
  342. }
  343. /// The textual representation used when written to an output stream, in the form of a cURL command.
  344. public var debugDescription: String {
  345. return cURLRepresentation()
  346. }
  347. }