NSURLConnection to NSURLSession - swift

I'm new to Swift and NSURLConnection & NSURLSession. I have this code, to load a webpage and this works. But I got this warning that NSURLConnection was deprecated in iOS 9.0, and I have to use NSURLSession.
This is my code:
var authenticated:Bool = false
var urlConnection:NSURLConnection!
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if !authenticated {
self.authenticated = false
self.urlConnection = NSURLConnection(request: request, delegate: self)!
urlConnection.start()
return false
}
return true
}
// We use this method is to accept an untrusted site which unfortunately we need to do, as our PVM servers are self signed.
func connection(connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: NSURLProtectionSpace) -> Bool {
return (protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust)
}
func connection(connection: NSURLConnection, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
if challenge.previousFailureCount == 0 {
self.authenticated = true
let credential: NSURLCredential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!
)
challenge.sender!.useCredential(credential, forAuthenticationChallenge: challenge)
}
else {
challenge.sender!.cancelAuthenticationChallenge(challenge)
}
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
// remake a webview call now that authentication has passed ok.
self.authenticated = true
web.loadRequest(request)
// Cancel the URL connection otherwise we double up (webview + url connection, same url = no good!)
urlConnection.cancel()
}
This works. Now I want to 'convert' it to NSURLSession, but I can't seem to manage. Can somebody help me with this? I'm quite sure it's not so difficult for someone who can code very well.
I've tried several times to change to NSURLSession, but every time I've got this error: NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813). And with NSURLConnection the problem is solved.
This is one of my attempts while using NSURLSession:
var authenticated:Bool = false
var urlSession:NSURLSession!
var urlSessionConfig:NSURLSessionConfiguration!
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if !authenticated {
self.authenticated = false
self.urlSessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
self.urlSession = NSURLSession(configuration: self.urlSessionConfig, delegate: self, delegateQueue:NSOperationQueue.mainQueue())
let task = self.urlSession.dataTaskWithRequest(request){
(myData, myResponse, myError) -> Void in
if(myError == nil){
if(self.authenticated){
self.web.loadRequest(request)
}
}
}
task.resume()
return false
}
return true
}
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust){
if(challenge.protectionSpace.host == "mydomain.org"){
self.authenticated = true
let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential);
}
}
}

You don't have to use NSURLSession. Odds are, NSURLConnection will be in a semi-supported state until the end of time, given how broadly it is used.
With that said, I'm only going to say this once, so listen very carefully. Do not, under any circumstances, publicly ship either version of this code as written. Self-signed certificates are okay, as long as you check them properly. This code is not doing that, which makes them no better than HTTP.
If the data has to be kept secret, either use a real certificate or add code to validate the self-signed certificates properly.
If it isn't even slightly important to keep it secret, just use HTTP.
This document explains how to properly implement modified TLS chain validation by adding trust for a specific certificate:
https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
BTW, you're not doing anything with challenges in other protection spaces or for other hostnames. If you never call the completion handler, then tasks that request any other type of authentication will just hang around forever, in limbo, waiting for you to decide how to handle the authentication request. This is probably not what you want, though I doubt it is causing your problem unless you're behind a proxy. Your NSURLConnection code accepted challenges within a single protection space, so it probably didn't experience that problem (as much).
With that said, I don't see why this is failing with that error code unless there's something else wrong with the cert beyond being self-signed. Oh, and there's a missing underscore in the method declaration. I don't think that is important anymore, but I'm not certain. Be sure your delegate method is actually getting called.
You might also try setting the CFNETWORK_DIAGNOSTICS environment variable to 1 (or more) and see if that provides any further insight.

Related

URLProtocol doesn't get initialized

I am implementing an URLProtocol in an app.
import Cocoa
class MyURLProtocol: URLProtocol {
override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
super.init(request: request, cachedResponse: cachedResponse, client: client)
}
override class func canInit(with request: URLRequest) -> Bool {
return true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
return request
}
override func startLoading() {
print("loading")
}
}
Although canInit(with request: URLRequest) always returns true, neither init(…) nor canonicalRequest(…) nor startLoading() get called.
URLProtocol.registerClass for MyURLProtocol is called in willFinishLaunching in the AppDelegate
I don't know what to do. Yesterday, day code called at least the functions.
Thanks for your help.
Are you using URLSession? URLSession bypasses the normal protocol registration and instead has you explicitly configure the protocols in the URLSessionConfiguration. See URLSessionConfiguration.protocolClasses.
Actually I'am working on a macOS app and not an iOS one, but it has fixed the problem when I was changing from WKWebView to WebView.
Thanks to Kevin Ballard on his comment.

Check for Reachability in viewDidAppear or AppDelegate?

I am using AlamoreFire and checking for network reachability in two of the app's view controllers in the viewDidAppear method of both. But sometimes when the network is found the view controller collection view loads twice.
I am guessing that maybe the Reachability should be put in one place only in the entire app.
What is the best and cleanest way to implement Reachability when you have multiple view controllers to check?
I would like to use the NetworkReachabilityManager from AlamoFire.
Normally with Reachability you'd have some sort of error view on top of the screen, don't worry if the background is trying to load or not.
Create a container view and in viewDidLoad()
if Reachability.isConnectedToNetwork() == true {
self.errorView.isHidden = true
} else {
self.errorView.isHidden = false
}
That solves your problem and helps with UX.
I have my method below. To be more direct to your question though, I would not check for connection in either.
Example: I get all the way to the apps first network call and then turn my wifi on.
Problem: My app fails even though nothing that needed wifi in the App was used.
My Method: I can understand checking for a network connection on the login page, but besides that, I would put it into your network. If a network call fails, check the connection and then relate to the user whether the call failed because of the server or because of the network.
This is what I use for Reachability:
import Foundation
/// This class is designed to check if the network is connected.
open class Reachability {
/// This function will hit two urls that should never be totally down to see if the device is connected
/// If either url connects, this returns true
///
/// - Parameter resultHandler: returns the result of the connection existing as a Bool in a resultHandler
class func isConnectedToNetwork(_ resultHandler: #escaping (_ isTrue: Bool) -> Void) {
let urlA = URL(string: "https://google.com/")
let urlB = URL(string: "https://baidu.com/")
Reachability.fetchURL(urlA!) { isTrue in
if isTrue {
print("NETWORK STATUS: \(isTrue)")
resultHandler(isTrue)
} else {
Reachability.fetchURL(urlB!, resultHandler: resultHandler)
}
}
}
/// Hits a URL in order to see if the device can connect to it
///
/// - Parameters:
/// - url: the url to request
/// - resultHandler: returns whether the url was successfully retrieved or not
class func fetchURL(_ url:URL, resultHandler: #escaping (_ isTrue: Bool) -> Void) {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData
request.timeoutInterval = 10.0
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: request) { data, response, error in
if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode == 200 {
resultHandler(true)
} else {
resultHandler(false)
}
} else {
resultHandler(false)
}
}
dataTask.resume()
}
}
Then anywhere in code you can call it like so:
Reachability.isConnectedToNetwork() { isConnected in
if isConnected {
//Do something when connected
} else {
//Do something when not connected
}
}

NSURLConnection ignoring SSL Certificate?

I know this question is asked often, but I have implemented the solution found in the answers and I'm not having any luck. I am not a Swift developer, so I'm guessing I'm missing something.
Here is my NSURLConnectionDelegate methods:
func connection(connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: NSURLProtectionSpace) -> Bool {
return protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
}
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
print("Second attempt resulted in authentication challenge")
challenge.sender!.useCredential(NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!), forAuthenticationChallenge: challenge)
challenge.sender!.continueWithoutCredentialForAuthenticationChallenge(challenge)
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
print("We got a response.....")
}
func connection(connection: NSURLConnection, didFailWithError error: NSError) {
print("============ second attempt failed ==============")
print("\(error)")
}
The connection goes through a VPN that doesn't allow for DNS. We have to use the IP, which results in an invalid cert error. The Cert is correct, just not using the IP. I am trying to ignore the cert error but I am still getting "An SSL error has occurred and a secure connection to the server cannot be made"
My understanding is that
challenge.sender!.useCredential(NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!), forAuthenticationChallenge: challenge)
challenge.sender!.continueWithoutCredentialForAuthenticationChallenge(challenge)
Should resolve this, but it doesn't seem to be working. The print line does run, so the delegate method is being called.
Thanks!
As Breek suggested, NSAppTransportSecurity in info.plist did the trick for iOS 9.

Is this specialized use of NSURLConnection to handle self signed certs convertible to NSURLSession?

I have a self signed certificate in the VM I use to test my service. Using answers found in UIWebView to view self signed websites (No private api, not NSURLConnection) - is it possible? I was able to write functioning swift 2.0 code. Xcode 7 tells me that NSURLConnection is deprecated and I should use NSURLSession instead. None of my attempts to migrate this code succeeded, and none of the usual conversion scenarios described in other answers seem to apply.
If I create a new NSURLSession in order to handle the authentication challenge with my delegate methods, the other loads still happen on the sharedSession, and therefore fail.
var authRequest : NSURLRequest? = nil
var authenticated = false
var trustedDomains = [:] // set up as necessary
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if !authenticated {
authRequest = request
let urlConnection: NSURLConnection = NSURLConnection(request: request, delegate: self)!
urlConnection.start()
return false
}
else if isWebContent(request.URL!) { // write your method for this
return true
}
return processData(request) // write your method for this
}
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let challengeHost = challenge.protectionSpace.host
if let _ = trustedDomains[challengeHost] {
challenge.sender!.useCredential(NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!), forAuthenticationChallenge: challenge)
}
}
challenge.sender!.continueWithoutCredentialForAuthenticationChallenge(challenge)
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
authenticated = true
connection.cancel()
webview!.loadRequest(authRequest!)
}
I was able to suppress the deprecation warning by adding this line before the first method. I would prefer a solution that replaces NSURLConnection but in the absence of that, this will have to do.
// hide NSURLConnection deprecation
#available(iOS, deprecated=9.0)

NSURLConnection The certificate for this server is invalid

Recently I've been trying to connect to a test server of mine using a self-signed SSL certificate using NSURLSession.sharedSession().dataTaskWithRequest()
Now I get this error:
The certificate for this server is invalid. You might be connecting to a server that is pretending to be “...” which could put your confidential information at risk.
I've been searching the web for how to solve it.
All of them advised for using one of these:
func connection(connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: NSURLProtectionSpace) -> Bool
func connection(connection: NSURLConnection, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge)
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge)
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void)
Now, when I ran my app I noticed that only
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge)
ran.
This is my code:
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge)
{
// Trusting and not trusting connection to host: Self-signed certificate
challenge.sender.useCredential(NSURLCredential(forTrust: challenge.protectionSpace.serverTrust), forAuthenticationChallenge: challenge)
challenge.sender.continueWithoutCredentialForAuthenticationChallenge(challenge)
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust)
{
let trust = challenge.protectionSpace.serverTrust
let cred = NSURLCredential(forTrust: trust)
challenge.sender.useCredential(cred, forAuthenticationChallenge: challenge)
}
}
Yet I keep getting NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813).
Aha! Apparently I've had a let urlConnection = NSURLConnection(request: request, delegate: self) a bit before that I haven't noticed...
And I changed NSURLSession.sharedSession().dataTaskWithRequest()
to
let task = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, delegateQueue: NSOperationQueue.mainQueue()).dataTaskWithRequest(request)
And left in place the delegate methods... that did it :)