|
|
@@ -0,0 +1,428 @@
|
|
|
+// Alamofire.swift
|
|
|
+//
|
|
|
+// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/)
|
|
|
+//
|
|
|
+// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
+// of this software and associated documentation files (the "Software"), to deal
|
|
|
+// in the Software without restriction, including without limitation the rights
|
|
|
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
+// copies of the Software, and to permit persons to whom the Software is
|
|
|
+// furnished to do so, subject to the following conditions:
|
|
|
+//
|
|
|
+// The above copyright notice and this permission notice shall be included in
|
|
|
+// all copies or substantial portions of the Software.
|
|
|
+//
|
|
|
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
+// THE SOFTWARE.
|
|
|
+
|
|
|
+import Foundation
|
|
|
+
|
|
|
+/**
|
|
|
+ Responsible for sending a request and receiving the response and associated data from the server, as well as managing its underlying `NSURLSessionTask`.
|
|
|
+*/
|
|
|
+public class Request {
|
|
|
+ let delegate: TaskDelegate
|
|
|
+
|
|
|
+ /// The underlying task.
|
|
|
+ public var task: NSURLSessionTask { return delegate.task }
|
|
|
+
|
|
|
+ /// The session belonging to the underlying task.
|
|
|
+ public let session: NSURLSession
|
|
|
+
|
|
|
+ /// The request sent or to be sent to the server.
|
|
|
+ public var request: NSURLRequest { return task.originalRequest }
|
|
|
+
|
|
|
+ /// The response received from the server, if any.
|
|
|
+ public var response: NSHTTPURLResponse? { return task.response as? NSHTTPURLResponse }
|
|
|
+
|
|
|
+ /// The progress of the request lifecycle.
|
|
|
+ public var progress: NSProgress { return delegate.progress }
|
|
|
+
|
|
|
+ init(session: NSURLSession, task: NSURLSessionTask) {
|
|
|
+ self.session = session
|
|
|
+
|
|
|
+ switch task {
|
|
|
+ case is NSURLSessionUploadTask:
|
|
|
+ self.delegate = UploadTaskDelegate(task: task)
|
|
|
+ case is NSURLSessionDataTask:
|
|
|
+ self.delegate = DataTaskDelegate(task: task)
|
|
|
+ case is NSURLSessionDownloadTask:
|
|
|
+ self.delegate = DownloadTaskDelegate(task: task)
|
|
|
+ default:
|
|
|
+ self.delegate = TaskDelegate(task: task)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: Authentication
|
|
|
+
|
|
|
+ /**
|
|
|
+ Associates an HTTP Basic credential with the request.
|
|
|
+
|
|
|
+ :param: user The user.
|
|
|
+ :param: password The password.
|
|
|
+
|
|
|
+ :returns: The request.
|
|
|
+ */
|
|
|
+ public func authenticate(#user: String, password: String) -> Self {
|
|
|
+ let credential = NSURLCredential(user: user, password: password, persistence: .ForSession)
|
|
|
+
|
|
|
+ return authenticate(usingCredential: credential)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ Associates a specified credential with the request.
|
|
|
+
|
|
|
+ :param: credential The credential.
|
|
|
+
|
|
|
+ :returns: The request.
|
|
|
+ */
|
|
|
+ public func authenticate(usingCredential credential: NSURLCredential) -> Self {
|
|
|
+ delegate.credential = credential
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: Progress
|
|
|
+
|
|
|
+ /**
|
|
|
+ Sets a closure to be called periodically during the lifecycle of the request as data is written to or read from the server.
|
|
|
+
|
|
|
+ - For uploads, the progress closure returns the bytes written, total bytes written, and total bytes expected to write.
|
|
|
+ - For downloads, the progress closure returns the bytes read, total bytes read, and total bytes expected to write.
|
|
|
+
|
|
|
+ :param: closure The code to be executed periodically during the lifecycle of the request.
|
|
|
+
|
|
|
+ :returns: The request.
|
|
|
+ */
|
|
|
+ public func progress(closure: ((Int64, Int64, Int64) -> Void)? = nil) -> Self {
|
|
|
+ if let uploadDelegate = delegate as? UploadTaskDelegate {
|
|
|
+ uploadDelegate.uploadProgress = closure
|
|
|
+ } else if let dataDelegate = delegate as? DataTaskDelegate {
|
|
|
+ dataDelegate.dataProgress = closure
|
|
|
+ } else if let downloadDelegate = delegate as? DownloadTaskDelegate {
|
|
|
+ downloadDelegate.downloadProgress = closure
|
|
|
+ }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: Response
|
|
|
+
|
|
|
+ /**
|
|
|
+ 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.
|
|
|
+ */
|
|
|
+ public typealias Serializer = (NSURLRequest, NSHTTPURLResponse?, NSData?) -> (AnyObject?, NSError?)
|
|
|
+
|
|
|
+ /**
|
|
|
+ Creates a response serializer that returns the associated data as-is.
|
|
|
+
|
|
|
+ :returns: A data response serializer.
|
|
|
+ */
|
|
|
+ public class func responseDataSerializer() -> Serializer {
|
|
|
+ return { (request, response, data) in
|
|
|
+ return (data, nil)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ Adds a handler to be called once the request has finished.
|
|
|
+
|
|
|
+ :param: completionHandler The code to be executed once the request has finished.
|
|
|
+
|
|
|
+ :returns: The request.
|
|
|
+ */
|
|
|
+ public func response(completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
|
|
|
+ return response(serializer: Request.responseDataSerializer(), completionHandler: completionHandler)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ Adds a handler to be called once the request has finished.
|
|
|
+
|
|
|
+ :param: queue The queue on which the completion handler is dispatched.
|
|
|
+ :param: serializer The closure responsible for serializing the request, response, and data.
|
|
|
+ :param: completionHandler The code to be executed once the request has finished.
|
|
|
+
|
|
|
+ :returns: The request.
|
|
|
+ */
|
|
|
+ public func response(queue: dispatch_queue_t? = nil, serializer: Serializer, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
|
|
|
+ delegate.queue.addOperationWithBlock {
|
|
|
+ let (responseObject: AnyObject?, serializationError: NSError?) = serializer(self.request, self.response, self.delegate.data)
|
|
|
+
|
|
|
+ dispatch_async(queue ?? dispatch_get_main_queue()) {
|
|
|
+ completionHandler(self.request, self.response, responseObject, self.delegate.error ?? serializationError)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return self
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ Suspends the request.
|
|
|
+ */
|
|
|
+ public func suspend() {
|
|
|
+ task.suspend()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ Resumes the request.
|
|
|
+ */
|
|
|
+ public func resume() {
|
|
|
+ task.resume()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ Cancels the request.
|
|
|
+ */
|
|
|
+ public func cancel() {
|
|
|
+ if let downloadDelegate = delegate as? DownloadTaskDelegate {
|
|
|
+ downloadDelegate.downloadTask.cancelByProducingResumeData { (data) in
|
|
|
+ downloadDelegate.resumeData = data
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ task.cancel()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ class TaskDelegate: NSObject, NSURLSessionTaskDelegate {
|
|
|
+ let task: NSURLSessionTask
|
|
|
+ let queue: NSOperationQueue
|
|
|
+ let progress: NSProgress
|
|
|
+
|
|
|
+ var data: NSData? { return nil }
|
|
|
+ private(set) var error: NSError?
|
|
|
+
|
|
|
+ var credential: NSURLCredential?
|
|
|
+
|
|
|
+ var taskWillPerformHTTPRedirection: ((NSURLSession!, NSURLSessionTask!, NSHTTPURLResponse!, NSURLRequest!) -> (NSURLRequest!))?
|
|
|
+ var taskDidReceiveChallenge: ((NSURLSession!, NSURLSessionTask!, NSURLAuthenticationChallenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential?))?
|
|
|
+ var taskDidSendBodyData: ((NSURLSession!, NSURLSessionTask!, Int64, Int64, Int64) -> Void)?
|
|
|
+ var taskNeedNewBodyStream: ((NSURLSession!, NSURLSessionTask!) -> (NSInputStream!))?
|
|
|
+
|
|
|
+ init(task: NSURLSessionTask) {
|
|
|
+ self.task = task
|
|
|
+ self.progress = NSProgress(totalUnitCount: 0)
|
|
|
+ self.queue = {
|
|
|
+ let operationQueue = NSOperationQueue()
|
|
|
+ operationQueue.maxConcurrentOperationCount = 1
|
|
|
+ operationQueue.qualityOfService = NSQualityOfService.Utility
|
|
|
+ operationQueue.suspended = true
|
|
|
+
|
|
|
+ return operationQueue
|
|
|
+ }()
|
|
|
+ }
|
|
|
+
|
|
|
+ deinit {
|
|
|
+ queue.cancelAllOperations()
|
|
|
+ queue.suspended = true
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: NSURLSessionTaskDelegate
|
|
|
+
|
|
|
+ func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: ((NSURLRequest!) -> Void)) {
|
|
|
+ var redirectRequest = request
|
|
|
+ if taskWillPerformHTTPRedirection != nil {
|
|
|
+ redirectRequest = taskWillPerformHTTPRedirection!(session, task, response, request)
|
|
|
+ }
|
|
|
+
|
|
|
+ completionHandler(redirectRequest)
|
|
|
+ }
|
|
|
+
|
|
|
+ func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void)) {
|
|
|
+ var disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling
|
|
|
+ var credential: NSURLCredential?
|
|
|
+
|
|
|
+ if taskDidReceiveChallenge != nil {
|
|
|
+ (disposition, credential) = taskDidReceiveChallenge!(session, task, challenge)
|
|
|
+ } else {
|
|
|
+ if challenge.previousFailureCount > 0 {
|
|
|
+ disposition = .CancelAuthenticationChallenge
|
|
|
+ } else {
|
|
|
+ credential = self.credential ?? session.configuration.URLCredentialStorage?.defaultCredentialForProtectionSpace(challenge.protectionSpace)
|
|
|
+ if credential != nil {
|
|
|
+ disposition = .UseCredential
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ completionHandler(disposition, credential)
|
|
|
+ }
|
|
|
+
|
|
|
+ func URLSession(session: NSURLSession, task: NSURLSessionTask, needNewBodyStream completionHandler: ((NSInputStream!) -> Void)) {
|
|
|
+ var bodyStream: NSInputStream?
|
|
|
+ if taskNeedNewBodyStream != nil {
|
|
|
+ bodyStream = taskNeedNewBodyStream!(session, task)
|
|
|
+ }
|
|
|
+
|
|
|
+ completionHandler(bodyStream)
|
|
|
+ }
|
|
|
+
|
|
|
+ func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
|
|
|
+ if error != nil {
|
|
|
+ self.error = error
|
|
|
+ }
|
|
|
+
|
|
|
+ queue.suspended = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ class DataTaskDelegate: TaskDelegate, NSURLSessionDataDelegate {
|
|
|
+ var dataTask: NSURLSessionDataTask! { return task as! NSURLSessionDataTask }
|
|
|
+
|
|
|
+ private var mutableData: NSMutableData
|
|
|
+ override var data: NSData? {
|
|
|
+ return mutableData
|
|
|
+ }
|
|
|
+
|
|
|
+ private var expectedContentLength: Int64?
|
|
|
+
|
|
|
+ var dataTaskDidReceiveResponse: ((NSURLSession!, NSURLSessionDataTask!, NSURLResponse!) -> (NSURLSessionResponseDisposition))?
|
|
|
+ var dataTaskDidBecomeDownloadTask: ((NSURLSession!, NSURLSessionDataTask!) -> Void)?
|
|
|
+ var dataTaskDidReceiveData: ((NSURLSession!, NSURLSessionDataTask!, NSData!) -> Void)?
|
|
|
+ var dataTaskWillCacheResponse: ((NSURLSession!, NSURLSessionDataTask!, NSCachedURLResponse!) -> (NSCachedURLResponse))?
|
|
|
+ var dataProgress: ((bytesReceived: Int64, totalBytesReceived: Int64, totalBytesExpectedToReceive: Int64) -> Void)?
|
|
|
+
|
|
|
+ override init(task: NSURLSessionTask) {
|
|
|
+ self.mutableData = NSMutableData()
|
|
|
+ super.init(task: task)
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: NSURLSessionDataDelegate
|
|
|
+
|
|
|
+ func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: ((NSURLSessionResponseDisposition) -> Void)) {
|
|
|
+ var disposition: NSURLSessionResponseDisposition = .Allow
|
|
|
+
|
|
|
+ expectedContentLength = response.expectedContentLength
|
|
|
+
|
|
|
+ if dataTaskDidReceiveResponse != nil {
|
|
|
+ disposition = dataTaskDidReceiveResponse!(session, dataTask, response)
|
|
|
+ }
|
|
|
+
|
|
|
+ completionHandler(disposition)
|
|
|
+ }
|
|
|
+
|
|
|
+ func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didBecomeDownloadTask downloadTask: NSURLSessionDownloadTask) {
|
|
|
+ dataTaskDidBecomeDownloadTask?(session, dataTask)
|
|
|
+ }
|
|
|
+
|
|
|
+ func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
|
|
|
+ dataTaskDidReceiveData?(session, dataTask, data)
|
|
|
+
|
|
|
+ mutableData.appendData(data)
|
|
|
+
|
|
|
+ if let expectedContentLength = dataTask.response?.expectedContentLength {
|
|
|
+ dataProgress?(bytesReceived: Int64(data.length), totalBytesReceived: Int64(mutableData.length), totalBytesExpectedToReceive: expectedContentLength)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: ((NSCachedURLResponse!) -> Void)) {
|
|
|
+ var cachedResponse = proposedResponse
|
|
|
+
|
|
|
+ if dataTaskWillCacheResponse != nil {
|
|
|
+ cachedResponse = dataTaskWillCacheResponse!(session, dataTask, proposedResponse)
|
|
|
+ }
|
|
|
+
|
|
|
+ completionHandler(cachedResponse)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// MARK: - Printable
|
|
|
+
|
|
|
+extension Request: Printable {
|
|
|
+ /// 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.
|
|
|
+ public var description: String {
|
|
|
+ var components: [String] = []
|
|
|
+ if request.HTTPMethod != nil {
|
|
|
+ components.append(request.HTTPMethod!)
|
|
|
+ }
|
|
|
+
|
|
|
+ components.append(request.URL!.absoluteString!)
|
|
|
+
|
|
|
+ if response != nil {
|
|
|
+ components.append("(\(response!.statusCode))")
|
|
|
+ }
|
|
|
+
|
|
|
+ return join(" ", components)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// MARK: - DebugPrintable
|
|
|
+
|
|
|
+extension Request: DebugPrintable {
|
|
|
+ func cURLRepresentation() -> String {
|
|
|
+ var components: [String] = ["$ curl -i"]
|
|
|
+
|
|
|
+ let URL = request.URL
|
|
|
+
|
|
|
+ if request.HTTPMethod != nil && request.HTTPMethod != "GET" {
|
|
|
+ components.append("-X \(request.HTTPMethod!)")
|
|
|
+ }
|
|
|
+
|
|
|
+ if let credentialStorage = self.session.configuration.URLCredentialStorage {
|
|
|
+ let protectionSpace = NSURLProtectionSpace(host: URL!.host!, port: URL!.port?.integerValue ?? 0, `protocol`: URL!.scheme!, realm: URL!.host!, authenticationMethod: NSURLAuthenticationMethodHTTPBasic)
|
|
|
+ if let credentials = credentialStorage.credentialsForProtectionSpace(protectionSpace)?.values.array {
|
|
|
+ for credential: NSURLCredential in (credentials as! [NSURLCredential]) {
|
|
|
+ components.append("-u \(credential.user!):\(credential.password!)")
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if let credential = delegate.credential {
|
|
|
+ components.append("-u \(credential.user!):\(credential.password!)")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Temporarily disabled on OS X due to build failure for CocoaPods
|
|
|
+ // See https://github.com/CocoaPods/swift/issues/24
|
|
|
+ #if !os(OSX)
|
|
|
+ if let cookieStorage = session.configuration.HTTPCookieStorage,
|
|
|
+ cookies = cookieStorage.cookiesForURL(URL!) as? [NSHTTPCookie]
|
|
|
+ where !cookies.isEmpty
|
|
|
+ {
|
|
|
+ let string = cookies.reduce(""){ $0 + "\($1.name)=\($1.value ?? String());" }
|
|
|
+ components.append("-b \"\(string.substringToIndex(string.endIndex.predecessor()))\"")
|
|
|
+ }
|
|
|
+ #endif
|
|
|
+
|
|
|
+ if request.allHTTPHeaderFields != nil {
|
|
|
+ for (field, value) in request.allHTTPHeaderFields! {
|
|
|
+ switch field {
|
|
|
+ case "Cookie":
|
|
|
+ continue
|
|
|
+ default:
|
|
|
+ components.append("-H \"\(field): \(value)\"")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if session.configuration.HTTPAdditionalHeaders != nil {
|
|
|
+ for (field, value) in session.configuration.HTTPAdditionalHeaders! {
|
|
|
+ switch field {
|
|
|
+ case "Cookie":
|
|
|
+ continue
|
|
|
+ default:
|
|
|
+ components.append("-H \"\(field): \(value)\"")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if let HTTPBody = request.HTTPBody,
|
|
|
+ escapedBody = NSString(data: HTTPBody, encoding: NSUTF8StringEncoding)?.stringByReplacingOccurrencesOfString("\"", withString: "\\\"")
|
|
|
+ {
|
|
|
+ components.append("-d \"\(escapedBody)\"")
|
|
|
+ }
|
|
|
+
|
|
|
+ components.append("\"\(URL!.absoluteString!)\"")
|
|
|
+
|
|
|
+ return join(" \\\n\t", components)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// The textual representation used when written to an output stream, in the form of a cURL command.
|
|
|
+ public var debugDescription: String {
|
|
|
+ return cURLRepresentation()
|
|
|
+ }
|
|
|
+}
|