Request.swift 17 KB

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