| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- // 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()
- }
- }
|