Request.swift 19 KB

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