Okechi Onyeje a01758cccd fixed pod stuff
also added error handling for case when firebae says app runs out of storage
2016-09-07 17:44:07 -04:00

616 lines
29 KiB
Swift

/*
* JBoss, Home of Professional Open Source.
* Copyright Red Hat, Inc., and individual contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/**
The HTTP method verb:
- GET: GET http verb
- HEAD: HEAD http verb
- DELETE: DELETE http verb
- POST: POST http verb
- PUT: PUT http verb
*/
public enum HttpMethod: String {
case GET = "GET"
case HEAD = "HEAD"
case DELETE = "DELETE"
case POST = "POST"
case PUT = "PUT"
}
/**
The file request type:
- Download: Download request
- Upload: Upload request
*/
enum FileRequestType {
case Download(String?)
case Upload(UploadType)
}
/**
The Upload enum type:
- Data: for a generic NSData object
- File: for File passing the URL of the local file to upload
- Stream: for a Stream request passing the actual NSInputStream
*/
enum UploadType {
case Data(NSData)
case File(NSURL)
case Stream(NSInputStream)
}
/**
Error domain.
**/
public let HttpErrorDomain = "HttpDomain"
/**
Request error.
**/
public let NetworkingOperationFailingURLRequestErrorKey = "NetworkingOperationFailingURLRequestErrorKey"
/**
Response error.
**/
public let NetworkingOperationFailingURLResponseErrorKey = "NetworkingOperationFailingURLResponseErrorKey"
public typealias ProgressBlock = (Int64, Int64, Int64) -> Void
public typealias CompletionBlock = (AnyObject?, NSError?) -> Void
/**
Main class for performing HTTP operations across RESTful resources.
*/
public class Http {
var baseURL: String?
var session: NSURLSession
var requestSerializer: RequestSerializer
var responseSerializer: ResponseSerializer
public var authzModule: AuthzModule?
private var delegate: SessionDelegate
/**
Initialize an HTTP object.
:param: baseURL the remote base URL of the application (optional).
:param: sessionConfig the SessionConfiguration object (by default it uses a defaultSessionConfiguration).
:param: requestSerializer the actual request serializer to use when performing requests.
:param: responseSerializer the actual response serializer to use upon receiving a response.
:returns: the newly intitialized HTTP object
*/
public init(baseURL: String? = nil,
sessionConfig: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration(),
requestSerializer: RequestSerializer = JsonRequestSerializer(),
responseSerializer: ResponseSerializer = JsonResponseSerializer()) {
self.baseURL = baseURL
self.delegate = SessionDelegate()
self.session = NSURLSession(configuration: sessionConfig, delegate: self.delegate, delegateQueue: NSOperationQueue.mainQueue())
self.requestSerializer = requestSerializer
self.responseSerializer = responseSerializer
}
deinit {
self.session.finishTasksAndInvalidate()
}
/**
Gateway to perform different http requests including multipart.
:param: url the url of the resource.
:param: parameters the request parameters.
:param: method the method to be used.
:param: completionHandler A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: The object created from the response data of request and the `NSError` object describing the network or parsing error that occurred.
*/
public func request(method: HttpMethod, path: String, parameters: [String: AnyObject]? = nil, credential: NSURLCredential? = nil, responseSerializer: ResponseSerializer? = nil, completionHandler: CompletionBlock) {
let block: () -> Void = {
let finalOptURL = self.calculateURL(self.baseURL, url: path)
guard let finalURL = finalOptURL else {
let error = NSError(domain: "AeroGearHttp", code: 0, userInfo: [NSLocalizedDescriptionKey: "Malformed URL"])
completionHandler(nil, error)
return
}
var request: NSURLRequest
var task: NSURLSessionTask?
var delegate: TaskDataDelegate
// Merge headers
let headers = merge(self.requestSerializer.headers, self.authzModule?.authorizationFields())
// care for multipart request is multipart data are set
if (self.hasMultiPartData(parameters)) {
request = self.requestSerializer.multipartRequest(finalURL, method: method, parameters: parameters, headers: headers)
task = self.session.uploadTaskWithStreamedRequest(request)
delegate = TaskUploadDelegate()
} else {
request = self.requestSerializer.request(finalURL, method: method, parameters: parameters, headers: headers)
task = self.session.dataTaskWithRequest(request);
delegate = TaskDataDelegate()
}
delegate.completionHandler = completionHandler
delegate.responseSerializer = responseSerializer == nil ? self.responseSerializer : responseSerializer
delegate.credential = credential
self.delegate[task] = delegate
if let task = task {task.resume()}
}
// cater for authz and pre-authorize prior to performing request
if (self.authzModule != nil) {
self.authzModule?.requestAccess({ (response, error ) in
// if there was an error during authz, no need to continue
if (error != nil) {
completionHandler(nil, error)
return
}
// ..otherwise proceed normally
block();
})
} else {
block()
}
}
/**
Gateway to perform different file requests either download or upload.
:param: url the url of the resource.
:param: parameters the request parameters.
:param: method the method to be used.
:param: responseSerializer the actual response serializer to use upon receiving a response
:param: type the file request type
:param: progress a block that will be invoked to report progress during either download or upload.
:param: completionHandler A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: The object created from the response data of request and the `NSError` object describing the network or parsing error that occurred.
*/
private func fileRequest(url: String, parameters: [String: AnyObject]? = nil, method: HttpMethod, credential: NSURLCredential? = nil, responseSerializer: ResponseSerializer? = nil, type: FileRequestType, progress: ProgressBlock?, completionHandler: CompletionBlock) {
let block: () -> Void = {
let finalOptURL = self.calculateURL(self.baseURL, url: url)
guard let finalURL = finalOptURL else {
let error = NSError(domain: "AeroGearHttp", code: 0, userInfo: [NSLocalizedDescriptionKey: "Malformed URL"])
completionHandler(nil, error)
return
}
var request: NSURLRequest
// Merge headers
let headers = merge(self.requestSerializer.headers, self.authzModule?.authorizationFields())
// care for multipart request is multipart data are set
if (self.hasMultiPartData(parameters)) {
request = self.requestSerializer.multipartRequest(finalURL, method: method, parameters: parameters, headers: headers)
} else {
request = self.requestSerializer.request(finalURL, method: method, parameters: parameters, headers: headers)
}
var task: NSURLSessionTask?
switch type {
case .Download(let destinationDirectory):
task = self.session.downloadTaskWithRequest(request)
let delegate = TaskDownloadDelegate()
delegate.downloadProgress = progress
delegate.destinationDirectory = destinationDirectory;
delegate.completionHandler = completionHandler
delegate.credential = credential
delegate.responseSerializer = responseSerializer == nil ? self.responseSerializer : responseSerializer
self.delegate[task] = delegate
case .Upload(let uploadType):
switch uploadType {
case .Data(let data):
task = self.session.uploadTaskWithRequest(request, fromData: data)
case .File(let url):
task = self.session.uploadTaskWithRequest(request, fromFile: url)
case .Stream(_):
task = self.session.uploadTaskWithStreamedRequest(request)
}
let delegate = TaskUploadDelegate()
delegate.uploadProgress = progress
delegate.completionHandler = completionHandler
delegate.credential = credential
delegate.responseSerializer = responseSerializer
self.delegate[task] = delegate
}
if let task = task {task.resume()}
}
// cater for authz and pre-authorize prior to performing request
if (self.authzModule != nil) {
self.authzModule?.requestAccess({ (response, error ) in
// if there was an error during authz, no need to continue
if (error != nil) {
completionHandler(nil, error)
return
}
// ..otherwise proceed normally
block();
})
} else {
block()
}
}
/**
Request to download a file.
:param: url the URL of the downloadable resource.
:param: destinationDirectory the destination directory where the file would be stored, if not specified. application's default '.Documents' directory would be used.
:param: parameters the request parameters.
:param: credential the credentials to use for basic/digest auth (Note: it is advised that HTTPS should be used by default).
:param: method the method to be used, by default a .GET request.
:param: progress a block that will be invoked to report progress during download.
:param: completionHandler a block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: The object created from the response data of request and the `NSError` object describing the network or parsing error that occurred.
*/
public func download(url: String, destinationDirectory: String? = nil, parameters: [String: AnyObject]? = nil, credential: NSURLCredential? = nil, method: HttpMethod = .GET, progress: ProgressBlock?, completionHandler: CompletionBlock) {
fileRequest(url, parameters: parameters, method: method, credential: credential, type: .Download(destinationDirectory), progress: progress, completionHandler: completionHandler)
}
/**
Request to upload a file using an NURL of a local file.
:param: url the URL to upload resource into.
:param: file the URL of the local file to be uploaded.
:param: parameters the request parameters.
:param: credential the credentials to use for basic/digest auth (Note: it is advised that HTTPS should be used by default).
:param: method the method to be used, by default a .POST request.
:param: responseSerializer the actual response serializer to use upon receiving a response.
:param: progress a block that will be invoked to report progress during upload.
:param: completionHandler A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: The object created from the response data of request and the `NSError` object describing the network or parsing error that occurred.
*/
public func upload(url: String, file: NSURL, parameters: [String: AnyObject]? = nil, credential: NSURLCredential? = nil, method: HttpMethod = .POST, responseSerializer: ResponseSerializer? = nil, progress: ProgressBlock?, completionHandler: CompletionBlock) {
fileRequest(url, parameters: parameters, method: method, credential: credential, responseSerializer: responseSerializer, type: .Upload(.File(file)), progress: progress, completionHandler: completionHandler)
}
/**
Request to upload a file using a raw NSData object.
:param: url the URL to upload resource into.
:param: data the data to be uploaded.
:param: parameters the request parameters.
:param: credential the credentials to use for basic/digest auth (Note: it is advised that HTTPS should be used by default).
:param: method the method to be used, by default a .POST request.
:param: responseSerializer the actual response serializer to use upon receiving a response.
:param: progress a block that will be invoked to report progress during upload.
:param: completionHandler A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: The object created from the response data of request and the `NSError` object describing the network or parsing error that occurred.
*/
public func upload(url: String, data: NSData, parameters: [String: AnyObject]? = nil, credential: NSURLCredential? = nil, method: HttpMethod = .POST, responseSerializer: ResponseSerializer? = nil, progress: ProgressBlock?, completionHandler: CompletionBlock) {
fileRequest(url, parameters: parameters, method: method, credential: credential, responseSerializer: responseSerializer, type: .Upload(.Data(data)), progress: progress, completionHandler: completionHandler)
}
/**
Request to upload a file using an NSInputStream object.
- parameter url: the URL to upload resource into.
- parameter stream: the stream that will be used for uploading.
- parameter parameters: the request parameters.
- parameter credential: the credentials to use for basic/digest auth (Note: it is advised that HTTPS should be used by default).
- parameter method: the method to be used, by default a .POST request.
- parameter responseSerializer: the actual response serializer to use upon receiving a response.
- parameter progress: a block that will be invoked to report progress during upload.
- parameter completionHandler: A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: The object created from the response data of request and the `NSError` object describing the network or parsing error that occurred.
*/
public func upload(url: String, stream: NSInputStream, parameters: [String: AnyObject]? = nil, credential: NSURLCredential? = nil, method: HttpMethod = .POST, responseSerializer: ResponseSerializer? = nil, progress: ProgressBlock?, completionHandler: CompletionBlock) {
fileRequest(url, parameters: parameters, method: method, credential: credential, responseSerializer: responseSerializer, type: .Upload(.Stream(stream)), progress: progress, completionHandler: completionHandler)
}
// MARK: Private API
// MARK: SessionDelegate
class SessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {
private var delegates: [Int: TaskDelegate]
private subscript(task: NSURLSessionTask?) -> TaskDelegate? {
get {
guard let task = task else {
return nil
}
return self.delegates[task.taskIdentifier]
}
set (newValue) {
guard let task = task else {
return
}
self.delegates[task.taskIdentifier] = newValue
}
}
required override init() {
self.delegates = Dictionary()
super.init()
}
func URLSession(session: NSURLSession, didBecomeInvalidWithError error: NSError?) {
// TODO
}
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
completionHandler(.PerformDefaultHandling, nil)
}
func URLSessionDidFinishEventsForBackgroundURLSession(session: NSURLSession) {
// TODO
}
// MARK: NSURLSessionTaskDelegate
func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: (NSURLRequest?) -> Void) {
if let delegate = self[task] {
delegate.URLSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request, completionHandler: completionHandler)
}
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
if let delegate = self[task] {
delegate.URLSession(session, task: task, didReceiveChallenge: challenge, completionHandler: completionHandler)
} else {
self.URLSession(session, didReceiveChallenge: challenge, completionHandler: completionHandler)
}
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, needNewBodyStream completionHandler: (NSInputStream?) -> Void) {
if let delegate = self[task] {
delegate.URLSession(session, task: task, needNewBodyStream: completionHandler)
}
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
if let delegate = self[task] as? TaskUploadDelegate {
delegate.URLSession(session, task: task, didSendBodyData: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend)
}
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if let delegate = self[task] {
delegate.URLSession(session, task: task, didCompleteWithError: error)
self[task] = nil
}
}
// MARK: NSURLSessionDataDelegate
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
completionHandler(.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didBecomeDownloadTask downloadTask: NSURLSessionDownloadTask) {
let downloadDelegate = TaskDownloadDelegate()
self[downloadTask] = downloadDelegate
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
if let delegate = self[dataTask] as? TaskDataDelegate {
delegate.URLSession(session, dataTask: dataTask, didReceiveData: data)
}
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse?) -> Void) {
completionHandler(proposedResponse)
}
// MARK: NSURLSessionDownloadDelegate
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
if let delegate = self[downloadTask] as? TaskDownloadDelegate {
delegate.URLSession(session, downloadTask: downloadTask, didFinishDownloadingToURL: location)
}
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if let delegate = self[downloadTask] as? TaskDownloadDelegate {
delegate.URLSession(session, downloadTask: downloadTask, didWriteData: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite)
}
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
if let delegate = self[downloadTask] as? TaskDownloadDelegate {
delegate.URLSession(session, downloadTask: downloadTask, didResumeAtOffset: fileOffset, expectedTotalBytes: expectedTotalBytes)
}
}
}
// MARK: NSURLSessionTaskDelegate
class TaskDelegate: NSObject, NSURLSessionTaskDelegate {
var data: NSData? { return nil }
var completionHandler: ((AnyObject?, NSError?) -> Void)?
var responseSerializer: ResponseSerializer?
var credential: NSURLCredential?
func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: (NSURLRequest?) -> Void) {
completionHandler(request)
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
var disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling
var credential: NSURLCredential?
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)) {
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error != nil {
completionHandler?(nil, error)
return
}
let response = task.response as! NSHTTPURLResponse
if let _ = task as? NSURLSessionDownloadTask {
completionHandler?(response, error)
return
}
var responseObject: AnyObject? = nil
do {
if let data = data {
try self.responseSerializer?.validateResponse(response, data)
responseObject = self.responseSerializer?.response(data, response.statusCode)
completionHandler?(responseObject, nil)
}
} catch let error as NSError {
var userInfo = error.userInfo
userInfo["StatusCode"] = response.statusCode
let errorToRethrow = NSError(domain: error.domain, code: error.code, userInfo: userInfo)
completionHandler?(responseObject, errorToRethrow)
}
}
}
// MARK: NSURLSessionDataDelegate
class TaskDataDelegate: TaskDelegate, NSURLSessionDataDelegate {
private var mutableData: NSMutableData
override var data: NSData? {
return self.mutableData
}
override init() {
self.mutableData = NSMutableData()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
completionHandler(.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.mutableData.appendData(data)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse?) -> Void) {
let cachedResponse = proposedResponse
completionHandler(cachedResponse)
}
}
// MARK: NSURLSessionDownloadDelegate
class TaskDownloadDelegate: TaskDelegate, NSURLSessionDownloadDelegate {
var downloadProgress: ((Int64, Int64, Int64) -> Void)?
var resumeData: NSData?
var destinationDirectory: NSString?
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
let filename = downloadTask.response?.suggestedFilename
// calculate final destination
var finalDestination: NSURL
if (destinationDirectory == nil) { // use 'default documents' directory if not set
// use default documents directory
let documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as NSURL
#if swift(>=2.3)
// this compiles on Xcode 8 / Swift 2.3 / iOS 10
finalDestination = documentsDirectory.URLByAppendingPathComponent(filename!)!
#else
// this compiles on Xcode 7 / Swift 2.2 / iOS 9
finalDestination = documentsDirectory.URLByAppendingPathComponent(filename!)
#endif
} else {
// check that the directory exists
let path = destinationDirectory?.stringByAppendingPathComponent(filename!)
finalDestination = NSURL(fileURLWithPath: path!)
}
do {
try NSFileManager.defaultManager().moveItemAtURL(location, toURL: finalDestination)
} catch _ {
}
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
self.downloadProgress?(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
}
}
// MARK: NSURLSessionTaskDelegate
class TaskUploadDelegate: TaskDataDelegate {
var uploadProgress: ((Int64, Int64, Int64) -> Void)?
func URLSession(session: NSURLSession, task: NSURLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
self.uploadProgress?(bytesSent, totalBytesSent, totalBytesExpectedToSend)
}
}
// MARK: Utility methods
public func calculateURL(baseURL: String?, url: String) -> NSURL? {
var url = url
if (baseURL == nil || url.hasPrefix("http")) {
return NSURL(string: url)!
}
guard let finalURL = NSURL(string: baseURL!) else {return nil}
if (url.hasPrefix("/")) {
url = url.substringFromIndex(url.startIndex.advancedBy(1))
}
return finalURL.URLByAppendingPathComponent(url);
}
public func hasMultiPartData(parameters: [String: AnyObject]?) -> Bool {
if (parameters == nil) {
return false
}
var isMultiPart = false
for (_, value) in parameters! {
if value is MultiPartData {
isMultiPart = true
break
}
}
return isMultiPart
}
}