Request.swift 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  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. /**
  128. Suspends the request.
  129. */
  130. public func suspend() {
  131. task.suspend()
  132. }
  133. /**
  134. Resumes the request.
  135. */
  136. public func resume() {
  137. task.resume()
  138. }
  139. /**
  140. Cancels the request.
  141. */
  142. public func cancel() {
  143. if let downloadDelegate = delegate as? DownloadTaskDelegate {
  144. downloadDelegate.downloadTask.cancelByProducingResumeData { (data) in
  145. downloadDelegate.resumeData = data
  146. }
  147. } else {
  148. task.cancel()
  149. }
  150. }
  151. class TaskDelegate: NSObject, NSURLSessionTaskDelegate {
  152. let task: NSURLSessionTask
  153. let queue: NSOperationQueue
  154. let progress: NSProgress
  155. var data: NSData? { return nil }
  156. private(set) var error: NSError?
  157. var credential: NSURLCredential?
  158. var taskWillPerformHTTPRedirection: ((NSURLSession!, NSURLSessionTask!, NSHTTPURLResponse!, NSURLRequest!) -> (NSURLRequest!))?
  159. var taskDidReceiveChallenge: ((NSURLSession!, NSURLSessionTask!, NSURLAuthenticationChallenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential?))?
  160. var taskDidSendBodyData: ((NSURLSession!, NSURLSessionTask!, Int64, Int64, Int64) -> Void)?
  161. var taskNeedNewBodyStream: ((NSURLSession!, NSURLSessionTask!) -> (NSInputStream!))?
  162. init(task: NSURLSessionTask) {
  163. self.task = task
  164. self.progress = NSProgress(totalUnitCount: 0)
  165. self.queue = {
  166. let operationQueue = NSOperationQueue()
  167. operationQueue.maxConcurrentOperationCount = 1
  168. operationQueue.qualityOfService = NSQualityOfService.Utility
  169. operationQueue.suspended = true
  170. return operationQueue
  171. }()
  172. }
  173. deinit {
  174. queue.cancelAllOperations()
  175. queue.suspended = true
  176. }
  177. // MARK: NSURLSessionTaskDelegate
  178. func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: ((NSURLRequest!) -> Void)) {
  179. var redirectRequest = request
  180. if taskWillPerformHTTPRedirection != nil {
  181. redirectRequest = taskWillPerformHTTPRedirection!(session, task, response, request)
  182. }
  183. completionHandler(redirectRequest)
  184. }
  185. func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void)) {
  186. var disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling
  187. var credential: NSURLCredential?
  188. if taskDidReceiveChallenge != nil {
  189. (disposition, credential) = taskDidReceiveChallenge!(session, task, challenge)
  190. } else {
  191. if challenge.previousFailureCount > 0 {
  192. disposition = .CancelAuthenticationChallenge
  193. } else {
  194. credential = self.credential ?? session.configuration.URLCredentialStorage?.defaultCredentialForProtectionSpace(challenge.protectionSpace)
  195. if credential != nil {
  196. disposition = .UseCredential
  197. }
  198. }
  199. }
  200. completionHandler(disposition, credential)
  201. }
  202. func URLSession(session: NSURLSession, task: NSURLSessionTask, needNewBodyStream completionHandler: ((NSInputStream!) -> Void)) {
  203. var bodyStream: NSInputStream?
  204. if taskNeedNewBodyStream != nil {
  205. bodyStream = taskNeedNewBodyStream!(session, task)
  206. }
  207. completionHandler(bodyStream)
  208. }
  209. func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
  210. if error != nil {
  211. self.error = error
  212. }
  213. queue.suspended = false
  214. }
  215. }
  216. class DataTaskDelegate: TaskDelegate, NSURLSessionDataDelegate {
  217. var dataTask: NSURLSessionDataTask! { return task as! NSURLSessionDataTask }
  218. private var mutableData: NSMutableData
  219. override var data: NSData? {
  220. return mutableData
  221. }
  222. private var expectedContentLength: Int64?
  223. var dataTaskDidReceiveResponse: ((NSURLSession!, NSURLSessionDataTask!, NSURLResponse!) -> (NSURLSessionResponseDisposition))?
  224. var dataTaskDidBecomeDownloadTask: ((NSURLSession!, NSURLSessionDataTask!) -> Void)?
  225. var dataTaskDidReceiveData: ((NSURLSession!, NSURLSessionDataTask!, NSData!) -> Void)?
  226. var dataTaskWillCacheResponse: ((NSURLSession!, NSURLSessionDataTask!, NSCachedURLResponse!) -> (NSCachedURLResponse))?
  227. var dataProgress: ((bytesReceived: Int64, totalBytesReceived: Int64, totalBytesExpectedToReceive: Int64) -> Void)?
  228. override init(task: NSURLSessionTask) {
  229. self.mutableData = NSMutableData()
  230. super.init(task: task)
  231. }
  232. // MARK: NSURLSessionDataDelegate
  233. func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: ((NSURLSessionResponseDisposition) -> Void)) {
  234. var disposition: NSURLSessionResponseDisposition = .Allow
  235. expectedContentLength = response.expectedContentLength
  236. if dataTaskDidReceiveResponse != nil {
  237. disposition = dataTaskDidReceiveResponse!(session, dataTask, response)
  238. }
  239. completionHandler(disposition)
  240. }
  241. func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didBecomeDownloadTask downloadTask: NSURLSessionDownloadTask) {
  242. dataTaskDidBecomeDownloadTask?(session, dataTask)
  243. }
  244. func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
  245. dataTaskDidReceiveData?(session, dataTask, data)
  246. mutableData.appendData(data)
  247. if let expectedContentLength = dataTask.response?.expectedContentLength {
  248. dataProgress?(bytesReceived: Int64(data.length), totalBytesReceived: Int64(mutableData.length), totalBytesExpectedToReceive: expectedContentLength)
  249. }
  250. }
  251. func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: ((NSCachedURLResponse!) -> Void)) {
  252. var cachedResponse = proposedResponse
  253. if dataTaskWillCacheResponse != nil {
  254. cachedResponse = dataTaskWillCacheResponse!(session, dataTask, proposedResponse)
  255. }
  256. completionHandler(cachedResponse)
  257. }
  258. }
  259. }
  260. // MARK: - Printable
  261. extension Request: Printable {
  262. /// 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.
  263. public var description: String {
  264. var components: [String] = []
  265. if request.HTTPMethod != nil {
  266. components.append(request.HTTPMethod!)
  267. }
  268. components.append(request.URL!.absoluteString!)
  269. if response != nil {
  270. components.append("(\(response!.statusCode))")
  271. }
  272. return join(" ", components)
  273. }
  274. }
  275. // MARK: - DebugPrintable
  276. extension Request: DebugPrintable {
  277. func cURLRepresentation() -> String {
  278. var components: [String] = ["$ curl -i"]
  279. let URL = request.URL
  280. if request.HTTPMethod != nil && request.HTTPMethod != "GET" {
  281. components.append("-X \(request.HTTPMethod!)")
  282. }
  283. if let credentialStorage = self.session.configuration.URLCredentialStorage {
  284. let protectionSpace = NSURLProtectionSpace(host: URL!.host!, port: URL!.port?.integerValue ?? 0, `protocol`: URL!.scheme!, realm: URL!.host!, authenticationMethod: NSURLAuthenticationMethodHTTPBasic)
  285. if let credentials = credentialStorage.credentialsForProtectionSpace(protectionSpace)?.values.array {
  286. for credential: NSURLCredential in (credentials as! [NSURLCredential]) {
  287. components.append("-u \(credential.user!):\(credential.password!)")
  288. }
  289. } else {
  290. if let credential = delegate.credential {
  291. components.append("-u \(credential.user!):\(credential.password!)")
  292. }
  293. }
  294. }
  295. // Temporarily disabled on OS X due to build failure for CocoaPods
  296. // See https://github.com/CocoaPods/swift/issues/24
  297. #if !os(OSX)
  298. if let cookieStorage = session.configuration.HTTPCookieStorage,
  299. cookies = cookieStorage.cookiesForURL(URL!) as? [NSHTTPCookie]
  300. where !cookies.isEmpty
  301. {
  302. let string = cookies.reduce(""){ $0 + "\($1.name)=\($1.value ?? String());" }
  303. components.append("-b \"\(string.substringToIndex(string.endIndex.predecessor()))\"")
  304. }
  305. #endif
  306. if request.allHTTPHeaderFields != nil {
  307. for (field, value) in request.allHTTPHeaderFields! {
  308. switch field {
  309. case "Cookie":
  310. continue
  311. default:
  312. components.append("-H \"\(field): \(value)\"")
  313. }
  314. }
  315. }
  316. if session.configuration.HTTPAdditionalHeaders != nil {
  317. for (field, value) in session.configuration.HTTPAdditionalHeaders! {
  318. switch field {
  319. case "Cookie":
  320. continue
  321. default:
  322. components.append("-H \"\(field): \(value)\"")
  323. }
  324. }
  325. }
  326. if let HTTPBody = request.HTTPBody,
  327. escapedBody = NSString(data: HTTPBody, encoding: NSUTF8StringEncoding)?.stringByReplacingOccurrencesOfString("\"", withString: "\\\"")
  328. {
  329. components.append("-d \"\(escapedBody)\"")
  330. }
  331. components.append("\"\(URL!.absoluteString!)\"")
  332. return join(" \\\n\t", components)
  333. }
  334. /// The textual representation used when written to an output stream, in the form of a cURL command.
  335. public var debugDescription: String {
  336. return cURLRepresentation()
  337. }
  338. }