Skip to content

Instantly share code, notes, and snippets.

@karl-gustav
Last active February 10, 2016 14:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karl-gustav/29274e75bf63ca6e67d0 to your computer and use it in GitHub Desktop.
Save karl-gustav/29274e75bf63ca6e67d0 to your computer and use it in GitHub Desktop.
HTTP request wrapper for swift. It's biggest advantage is that you can replay requests `let r = MyRequest.get("url"); while true { r.makeRequest() };`
import Foundation
import UIKit
class MyRequest {
class func get(url:String) -> RequestHandler {
return RequestHandler(url: url, method: "GET")
}
class func post(url:String) -> RequestHandler {
return RequestHandler(url: url, method: "POST")
}
class func put(url:String) -> RequestHandler {
return RequestHandler(url: url, method: "PUT")
}
class func delete(url:String) -> RequestHandler {
return RequestHandler(url: url, method: "DELETE")
}
}
class RequestHandler {
private let _method: String
private var _headers = [String:String]()
private var _body: NSData?
private var _auth: String?
private let _url: NSURL
private var _boundary: String?
private var errorPlaceholder: NSError?
private static var numberOfOpenRequests: Int = 0
private static var sharedApplication:UIApplication? {
// hack we have to do because UIApplication.sharedApplication is not available in the Apple Watch framework
#if TARGET_MAIN_APP
return UIApplication.sharedApplication()
#else
return nil
#endif
}
enum types {
case JSON, MULTIPART, URL_ENCODED
}
init (url: String, method: String) {
_url = NSURL(string: url)!
_method = method
}
func extraHeaders(headers: [String:String]) -> RequestHandler {
for (key, value) in headers {
if value != "Content-Type" && value != "Accept" {
_headers[key] = value
}
}
return self
}
func multipartBody (data: NSData, boundary: String) -> RequestHandler {
_headers["Content-Type"] = "multipart/form-data; boundary=\(boundary)"
_body = data
return self
}
func jsonBody (data: AnyObject) -> RequestHandler {
_headers["Content-Type"] = "application/json"
if NSJSONSerialization.isValidJSONObject(data) {
let bodyString = RequestHandler.JSONStringify(data, errorPointer: &errorPlaceholder)
_body = bodyString.dataUsingEncoding(NSUTF8StringEncoding)
}
else {
NSException(name: "WRONG_INPUT_TO_JSON_BODY", reason: "You gave it an object that couldn't be parsed to JSON", userInfo: nil).raise()
}
return self
}
func urlEncodeBody (data: [String: String]) -> RequestHandler {
_headers["Content-Type"] = "application/x-www-form-urlencoded"
var parts = [String]()
for (key,val) in data {
parts.append(String(format:"%@=%@", key, val))
}
_body = parts.joinWithSeparator("&").dataUsingEncoding(NSUTF8StringEncoding)
return self
}
func basicAuth(username username: String, password: String) -> RequestHandler {
let loginString = NSString(format: "%@:%@", username, password)
let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64EncodedCredentials = loginData.base64EncodedStringWithOptions([])
_headers["Authorization"] = "Basic \(base64EncodedCredentials)"
return self
}
func bearerAuth(bearer: String) -> RequestHandler {
_headers["Authorization"] = "Bearer \(bearer)"
return self
}
func makeRequest(successHandler successHandler: ([String: AnyObject], NSHTTPURLResponse) -> Void, errorHandler: (NSError, NSHTTPURLResponse?) -> Void) -> RequestHandler {
self.dynamicType.increaseOpenConnectionCount()
var errorPlaceholder: NSError?
let request = NSMutableURLRequest(URL: _url)
request.HTTPMethod = _method
let headers = RequestHandler.addDefaultHeaders(_headers)
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
if _body != nil {
request.HTTPBody = _body
}
if errorPlaceholder != nil {
errorHandler(errorPlaceholder!, nil)
return self
}
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
self.dynamicType.decreaseOpenConnectionCount()
if error != nil {
errorHandler(error!, nil)
return
}
let urlResponse = response as! NSHTTPURLResponse
if urlResponse.statusCode >= 200 && urlResponse.statusCode < 300 { // 200..299
let dataAsString = NSString(data: data!, encoding: NSUTF8StringEncoding) as! String
// This is a patch for the Assignments' API. It returns a 201 but with empty body
if dataAsString.characters.count == 0 {
successHandler([String: AnyObject](), urlResponse)
return
}
let responseDict = RequestHandler.JSONParseStringToDictionary(dataAsString, errorPointer: &errorPlaceholder)
if errorPlaceholder != nil {
errorHandler(errorPlaceholder!, urlResponse)
return
}
successHandler(responseDict, urlResponse)
}
else {
let url = urlResponse.URL?.absoluteString ?? "url(N/A)"
let err = NSError(domain: "MyRequest.RequestHandler", code: -1, userInfo: [
NSLocalizedDescriptionKey: "Got \(urlResponse.statusCode) status code on request to \(url)",
"statusCode": urlResponse.statusCode,
"url": url,
"body": NSString(data: data!, encoding: NSUTF8StringEncoding) as! String
])
errorHandler(err, urlResponse)
}
}
task.resume()
return self
}
private class func addDefaultHeaders(headers: [String:String]) -> [String:String] {
var mutableHeaders = headers
mutableHeaders["Accept"] = "application/json" // This class only supports JSON as return type
mutableHeaders["User-Agent"] = Helper.userAgent()
return mutableHeaders
}
private class func JSONStringify(value: AnyObject = "", errorPointer: NSErrorPointer = nil) -> String {
if NSJSONSerialization.isValidJSONObject(value) {
do {
let data = try NSJSONSerialization.dataWithJSONObject(value, options: [])
if let string = NSString(data: data, encoding: NSUTF8StringEncoding) {
return string as String
}
} catch let error as NSError {
errorPointer.memory = error
}
}
else {
errorPointer.memory = NSError(domain: "MyRequest.RequestHandler", code: -2, userInfo: [
NSLocalizedDescriptionKey: "Got an object that can't be converted to json: \(value)"
])
}
return ""
}
private class func JSONParseStringToDictionary(jsonString: String = "", errorPointer: NSErrorPointer = nil) -> [String: AnyObject] {
if let data = jsonString.dataUsingEncoding(NSUTF8StringEncoding) {
do {
if let dictionary = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(rawValue: 0)) as? [String: AnyObject] {
return dictionary
}
}
catch _ {
}
}
return [String: AnyObject]()
}
private class func increaseOpenConnectionCount() {
numberOfOpenRequests++
sharedApplication?.networkActivityIndicatorVisible = true
}
private class func decreaseOpenConnectionCount() {
if --numberOfOpenRequests <= 0 {
numberOfOpenRequests = 0
sharedApplication?.networkActivityIndicatorVisible = false
}
}
}
@karl-gustav
Copy link
Author

Usage

MyRequest
            .post(url)
            .bearerAuth(AuthenticationData.accessToken)
            .makeRequest(
                        successHandler: { data, response in
                                    //success
                        },
                        errorHandler: { err, response in
                                    //error
                        }    
            )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment