Request.swift 17 KB

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