Swift 2 OSX How do I implement proxy settings with NSURLSession? - macos

With iOS I was able to get as far as a 407 error requiring authorization. With OSX, no such luck. After the task resumes, it just hangs for a long time then reports that a connection couldn't be made.
If you answer, please also include how to pass proxy username and password.
func sendRequest() {
var proxyHost : CFString = NSString(string: "12.345.67.89") as CFString
var proxyPort : CFString = NSString(string: "1234") as CFString
var proxyEnable : CFNumber = NSNumber(int: 1) as CFNumber
var proxyDict: [NSObject : AnyObject] = [
kCFNetworkProxiesHTTPEnable: proxyEnable,
kCFStreamPropertyHTTPProxyHost: proxyHost,
kCFStreamPropertyHTTPProxyPort: proxyPort,
kCFStreamPropertyHTTPSProxyHost: proxyHost,
kCFStreamPropertyHTTPSProxyPort: proxyPort,
kCFProxyTypeKey: kCFProxyTypeHTTPS
]
let request = NSMutableURLRequest(URL: NSURL(string:https://www.someurl.com/login))
var configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration()
let configuration.connectionProxyDictionary = proxyDict
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
let task = session.dataTaskWithRequest(request) { (data, response, error) in
NSHTTPCookieStorage.sharedHTTPCookieStorage().setCookies([NSHTTPCookie](), forURL: self.URL, mainDocumentURL: nil)
if data != nil {
do {
let responseHeaders = response as! NSHTTPURLResponse
self.statusCode = responseHeaders.statusCode
switch self.statusCode {
case 200:
self.contentsOfURL = try NSString(contentsOfURL: self.URL, encoding: NSUTF8StringEncoding)
self.cookies = NSHTTPCookieStorage.sharedHTTPCookieStorage().cookiesForURL(self.URL)!
for cookie in self.cookies {
if cookie.name == "session" {
self.sessionCookie = cookie.value
}
}
case 400:
print("400: page not found on web")
case 404:
print("404: page not found on server")
case 407:
print("407: failed authenticate proxy credentials")
default:
print("unable to get statusCode")
}
} catch {
}
} else {
print("\(self.statusCode): unable to get response ")
}
dispatch_semaphore_signal(semaphore)
}
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}

Related

Parsing JSON Swift 3 with error Code=3840

I have this error.
Error Domain=NSCocoaErrorDomain Code=3840 "Garbage at end." UserInfo={NSDebugDescription=Garbage at end.}
Here is my swift code.
let request = NSMutableURLRequest(url: NSURL(string: "http://example.org/file.php")! as URL)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request as URLRequest) {
old_data, response, error in
//Error Checking
if error != nil {
print("error=\(error)")
return
}
print("response = \(response)")
//Response string built up
let responseString = NSString(data: old_data!, encoding: String.Encoding.utf8.rawValue)
print("responseString = \(responseString)")
//Manipulating the JSON data
var names = [String]()
do {
if let new_data = old_data,
let json = try JSONSerialization.jsonObject(with: new_data) as? [String: Any],
let buildings = json["buildings"] as? [[String: Any]] {
for building in buildings {
if let name = building["BuildingName"] as? String {
names.append(name)
}
}
}
} catch {
print(error)
}
print("Names: \(names)")
}
task.resume()
The error is happening in my 'do' statement where 'let buildings = json["buildings"]'
And below is what I get when I print the response string.
responseString = Optional({"buildings":[{"BuildingID":"2","BuildingName":"School of Informatics and Comp","Info":"School of higher learning.","Latitude":"39.172085","Longitude":"-86.522908"}]}Successfully Retrieved. SELECT * FROM Buildings;)
Any questions or advice is appreciated

Swift 2 How do I isolate resources in a series of NSMutableURLRequests?

Assume I've already logged to two accounts and have obtained unique session cookies for each.
When executing ViewController.run(), which uses nested closures, a series of 80 unique URL requests is made (40 for each of the two accounts) .
Though I'm able to make all 80 unique URL requests, somehow one account will sometimes make a request of a URL that only the other account should be making.
I'm pretty certain the resources between each account as well as each account's request are isolated. Both executions of run() construct their own instances of Visit(_:), URLVisitor(_:) and Request(_:).
Note: assume that neither account's username array contains a username that the other has in it's array.
ViewController.swift
func run(completion: () -> Void) {
// 40 usernames appended to array
var usernames: [String] = ["username1",..., username40]
for username in usernames {
let visit = Visit()
visit.sessionCookie = sessionCookie
visit.visitProfile(username) {
NSThread.sleepForTimeInterval(5.0)
}
}
}
Visit.swift
var contentsOfURL = NSString()
var sessionCookie = String()
func visitprofile(username: String, completion: () -> Void) {
let url = "https://www.someurl.com/profile/\(username)"
let encodedURL = url.stringByAddingPercentEncodingWithAllowedCharacters(
NSCharacterSet.URLFragmentAllowedCharacterSet()),
URL = NSURL(string: encodedURL!)
let vis = URLVisitor(URL: URL!)
vis.sessionCookie = self.sessionCookie
vis.execute {
if vis.containsString(profileName) {
print("\(profileName) visited: OK")
} else {
print("\(profileName) visited: FAIL")
}
completion()
}
}
URLVisitor.swift
var contentsOfURL = NSString()
var sessionCookie = String()
var URL = NSURL()
init(URL: NSURL) {
self.URL = URL
}
func execute(completion: () -> Void) {
let request = Request()
request.sessionCookie = self.sessionCookie
request.accessToken = self.accessToken
request.sessionCookie = self.sessionCookie
request.sendRequest(self.URL, completion: () -> Void) {
self.sessionCookie = request.sessionCookie
self.contentsOfURL = request.contentsOfURL
completion()
}
}
Request.swift: NSObject, NSURLSessionDelegate
var contentsOfURL = NSString()
var responseCookies = String()
var sessionCookie = String()
func sendRequest(URL: NSURL, completion: () -> Void) {
var request = NSMutableURLRequest(URL: URL)
var session = NSURLSession.sharedSession()
var config = NSURLSessionConfiguration.defaultSessionConfiguration()
if sessionCookie != "" {
config.HTTPCookieStorage = nil
config.requestCachePolicy = .ReloadIgnoringLocalAndRemoteCacheData
request.setValue(sessionCookie, forHTTPHeaderField: "Cookie")
session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil)
}
request.HTTPBody = params.dataUsingEncoding(NSUTF8StringEncoding)
request.HTTPMethod = "GET"
let task = session.dataTaskWithRequest(request) { (data, response, error) in
let response = response as! NSHTTPURLResponse
do {
self.contentsOfURL = try NSString(contentsOfURL: URL, encoding: NSUTF8StringEncoding)
} catch{
}
if self.sessionCookie == "" {
self.sessionCookie = // obtained here during login
}
completion()
}
task.resume()
}

Swift 2 How do I wait for NSURLSessionDataTask to complete before continuing code

In this code, request.getStatusCode() and request.getIsConnected() both execute before the code inside the task block is executed
i.e. dataTask.resume() doesn't execute until after any subsequent code is executed, be it code in the same function, class, or separate class.
I've tried putting the function call in the main queue (serial), a global queue (concurrent), a manual serial queue, a manual concurrent queue and an NSOperationQueue, all followed by a a while loop to wait until the closure completes.
while isDoingSomething {
NSThread.sleepForTimeInterval(0.0)
}
loop.
I've left any GCDs or operations out of this code to avoid the clutter of each queue scenario I've tried.
ViewController.swift
import Cocoa
class ViewController: NSViewController {
.
.
.
func login(username username: String, password: String) {
let url = "https://www.awebsite.com/login"
let URL = NSURL(string: url)
let method = "POST"
let params = "username=\(username)&password=\(password)"
vis = URLVisitor(URL: URL!, params: params, method: method, jsonParams: [:])
vis.execute()
cookies = vis.getCookies()
let contentsOfURL = vis.getContentsOfURL()
}
.
.
.
}
URLVisitor.swift
import Cocoa
let queue = NSOperationQueue
class URLVisitor: NSOperation {
.
.
.
func execute() {
let request = Request(URL: URL!, params: params, method: method, jsonParams: jsonParams)
if !self.cookies.isEmpty {
request._setCookies(self.cookies)
}
request._setAuthorizationHeader(self.authorizationHeader)
request.sendRequest()
self.statusCode = request.getStatusCode()
self.isConnected = request.getIsConnected()
}
.
.
.
}
Request.swift
import Cocoa
class Request: NSOperation {
.
.
.
func sendRequest() {
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: URL)
request.HTTPMethod = method
// send jsonParams or params
if jsonParams.count != 0 {
do {
let jsonData = try NSJSONSerialization.dataWithJSONObject(jsonParams, options: .PrettyPrinted)
request.setValue("aplication/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.HTTPBody = jsonData
} catch {
}
} else {
request.HTTPBody = self.params.dataUsingEncoding(NSUTF8StringEncoding)
}
let task = session.dataTaskWithRequest(request) {
(data, response, error) in
NSHTTPCookieStorage.sharedHTTPCookieStorage().setCookies(self.cookies, forURL: self.URL, mainDocumentURL: nil)
if data != nil {
self.data = data!
do {
let responseHeaders = response as! NSHTTPURLResponse
self.statusCode = responseHeaders.statusCode
switch self.statusCode {
case 200:
print("200: OK. getting contentsOfURL and cookies")
self.contentsOfURL = try NSString(contentsOfURL: self.URL, encoding: NSUTF8StringEncoding)
self.cookies = NSHTTPCookieStorage.sharedHTTPCookieStorage().cookiesForURL(self.URL)!
case 400:
print("400: page not found on web")
case 404:
print("404: page not found on server")
case 407:
print("407: failed authenticate proxy credentials")
default:
print("unable to get statusCode")
}
} catch {
}
} else {
print("\(self.statusCode): unable to get response ")
}
NSThread.sleepForTimeInterval(1.0)
}
task.resume()
}
The proper way to do this is to add a completion handler
func sendRequest(completion: () -> Void) {
// ...
let task = session.dataTaskWithRequest(request) {
// ...
completion()
}
task.resume()
}
Usage:
let r = Request()
r.sendRequest {
// It's done, do something
}
If you insist on blocking the thread (I hope it's not the main thread), use a semaphore. But remember to signal semaphore whether the request succeeded or failed. I've seen far too many code that forgot to signal the semaphore when the request fail so the app just hung up.
func sendRequest() {
let semaphore = dispatch_semaphore_create(0)
let task = session.dataTaskWithRequest(request) {
// ...
dispatch_semaphore_signal(semaphore)
}
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}

NSURL error for broken links in Swift

I coded a function for OSX 10.10 that is willing to open a text file from an URL and display its content.
Everything is working but if the URL cannot be reach then the App will crash. How could I handle this type of Error?
I guess it comes from the completionHandler closure but I am not sure.
here is my code
#IBAction func checkAdminMessage(sender: NSMenuItem) {
let messageURL = NSURL(string: "http://www.xxxxxx.com/text.txt")
// The Network stuff will be handled in a background thread
let sharedSession = NSURLSession.sharedSession()
let downloadTask: NSURLSessionDownloadTask = sharedSession.downloadTaskWithURL(messageURL!,
completionHandler: {
(location: NSURL!, response: NSURLResponse!, error: NSError!) -> Void in
var urlContents = NSString(contentsOfURL: location, encoding: NSUTF8StringEncoding, error: nil)
// Check if text.txt has NULL as content
if urlContents! == "NULL" {
// Have to use Grand Central Dispatch to put NSAlert in the main thread
let noMessage = NSLocalizedString("Nothing there", comment: "Text to dislay when the file is empty" )
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.displayAlertNotification(notification: noMessage)
}
} else {
// If the file is not empty then we display the content of this file
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.displayAlertNotification(notification: urlContents!)
}
}
})
downloadTask.resume()
}
Thank you
EDIT: Here is the updated code but the App still crashed
#IBAction func checkAdminMessage(sender: NSMenuItem) {
if let messageURL = NSURL(string: "http://www.xxxxxx.com/text.txt") {
let sharedSession = NSURLSession.sharedSession()
let downloadTask: NSURLSessionDownloadTask = sharedSession.downloadTaskWithURL(messageURL,
completionHandler: {
(location: NSURL!, response: NSURLResponse!, error: NSError!) -> Void in
var urlContents = NSString(contentsOfURL: location, encoding: NSUTF8StringEncoding, error: nil )
if urlContents == "NULL" {
println(urlContents)
// Have to use Grand Central Dispatch to put NSAlert in the main thread
let noMessage = NSLocalizedString("Nothing there", comment: "Text to dislay when the file is empty" )
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.displayAlertNotification(notification: noMessage)
}
}
else {
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.displayAlertNotification(notification: urlContents!)
}
}
})
downloadTask.resume()
}
else {
println("Error")
}
}
NSURL(string: ...) returns an optional, so the result may be nil due to several reasons.
Wrap your code in a conditional unwrap:
if let messageURL = NSURL(string: "http://www.xxxxxx.com/text.txt") {
// success
...
}
else {
// error
}
I figured it out with the helps of the people that commented my question.
I was getting a nil from 'location' in downloadTaskWithUrl, then the var urlContents was receiving a nil as well.
The solution is to check if 'location' is nil :
let downloadTask: NSURLSessionDownloadTask = sharedSession.downloadTaskWithURL(messageURL,
completionHandler: {
(location: NSURL!, response: NSURLResponse!, error: NSError!) -> Void in
**if (location != nil) {**
var urlContents = NSString(contentsOfURL: location, encoding: NSUTF8StringEncoding, error: nil ) ...

NSURLSessionDataTask: func receives but returns no data

I've tried to create a simple function which receives URL and simply returns the HTML of the webpage. the NSURLSessionDataTask itself seems to work, I can see the whole html when I println(data). But the func only returns the initial value "#!?". I suspect that the DataTask works asynchronous? How can I handle this?
func loadHTML(targetURL: String) -> String {
var theTargetURL = NSURL(string:targetURL)
var theResult = "#!?"
var request: NSURLRequest = NSURLRequest(URL:theTargetURL)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
println(NSString(data: data, encoding: NSASCIIStringEncoding))
if error != nil {
println(error.localizedDescription)
theResult = "error"
}
if data != nil {
theResult = NSString(data: data, encoding: NSASCIIStringEncoding)
println("RECEIVED\t\t\(countElements(theResult)) CHARS")
}
});
task.resume()
return theResult
}
Here is an example how you would do it using a completion handler.
typealias CompletionBlock = (NSData!, NSURLResponse!, NSError!) -> Void
func loadHtml(targetUrlString: String, completion: CompletionBlock){
let targetUrl = NSURL(string: targetUrlString)
let request = NSURLRequest(URL: targetUrl)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: completion)
task.resume()
}
Then, call it from your method where it is needed,
func viewDidLoad(){
super.viewDidLoad()
loadHtml("http://google.com", completion: { (responseData: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
if responseData != nil{
let resultString = NSString(data: responseData, encoding: NSASCIIStringEncoding)
println(resultString)
/*
do some task or update on the main thread
dispatch_async(dispatch_get_main_queue){
myActivityIndicator.stopAnimating()
}
*/
}else if error != nil{
println("Error occurred: \(error.localizedDescription)")
}
})

Resources