I am having trouble with a URLSession that creates and resumes a URLSessionDataTask in the sense that I expect subsequent calls to certain methods in the session's (and task`s) delegate but apparently no such calls occur.
How can I debug URLSession in a situation like this? For instance, can I request log output from it e.g. to find out when it sends or receives HTTP traffic or when it attempts to call a method in the delegate but cannot find the relevant implementation (e.g. because the method signature in the implementation may be slightly off).
Delegate methods are now (starting to be) called as expected. The solution consisted in copying method signatures from the very code base of URLSessionDataDelegate and URLSessionTaskDelegate (not their documentation) to ensure exact matches. Looking at device log output (lines with marked libsystem_network.dylib and <Debug>) was also helpful. The employed methods signatures are now as follows:
public func urlSession(_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
// ...
}
public func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive data: Data) {
// ...
}
public func urlSession(_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?) {
// ...
}
Related
I'm trying to realise basic-authentication using URLSessionDelegate and URLCredentials
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void)
Apple recommends not to write manually Authorization header. So I'm trying to use URLCredentials.
I'm doing very simple example in Swift Playground:
class Callback: NSObject {}
extension Callback: URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
print("received challenge")
print(challenge.protectionSpace.authenticationMethod)
completionHandler(.performDefaultHandling, nil)
}
}
let callback = Callback()
var configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: callback, delegateQueue: nil)
let string = "http://demo0230490.mockable.io/test"
var request = URLRequest(url: URL(string: string)!)
session.dataTask(with: request) { (data, response, error) in
print("response")
print(response)
}.resume()
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
The problem is – I don't receive auth challenge.
If you go to http://demo0230490.mockable.io/test in browser you will see the browser showing alert for basic auth.
From my mock http I return header like:
"Www-Authenticate" = (
"Basic realm=\"Access to MRM\""
);
Also I tried switching to https (I mean https://demo0230490.mockable.io/test) to check whether I'll receive auth challenge of type NSURLAuthenticationMethodServerTrust.
And that works. That means that my delegate is properly set and works in some circumstances.
So the question is:
Can anybody point the problem or provide the working example for basic auth using URLCredentials (for example using https://www.mockable.io)?
According to the documentation urlSession(_:didReceive:completionHandler:) is called only in two situations:
When a remote server asks for client certificates or Windows NT LAN Manager (NTLM) authentication, to allow your app to provide
appropriate credentials
When a session first establishes a connection to a remote server that uses SSL or TLS, to allow your app to verify the server’s
certificate chain
When you are switching to https the second condition is met and therefore method is called.
There is two types of authentication challenges: session-level and non-session-level.
For non-session-level challenges URLSession calls urlSession(_:task:didReceive:completionHandler:). For session-level challenges URLSession calls urlSession(_:didReceive:completionHandler:).
Difference between session-level challenges and non-session-level challenges is described in the discussion section of urlSession(_:task:didReceive:completionHandler:) documentation:
For session-level challenges—NSURLAuthenticationMethodNTLM, NSURLAuthenticationMethodNegotiate,
NSURLAuthenticationMethodClientCertificate, or
NSURLAuthenticationMethodServerTrust—the NSURLSession object calls the
session delegate’s urlSession(:didReceive:completionHandler:) method.
If your app does not provide a session delegate method, the
NSURLSession object calls the task delegate’s
urlSession(:task:didReceive:completionHandler:) method to handle the
challenge.
For non-session-level challenges (all others), the URLSession object calls the session delegate’s
urlSession(:task:didReceive:completionHandler:) method to handle the
challenge. If your app provides a session delegate and you need to
handle authentication, then you must either handle the authentication
at the task level or provide a task-level handler that calls the
per-session handler explicitly. The session delegate’s
urlSession(:didReceive:completionHandler:) method is not called for
non-session-level challenges.
In your case you should implement non-session-level challenge handler:
extension Callback: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
print("received task challenge")
}
}
Also there is working example on Apple-forum.
You should use urlSession(_:task:didReceive:completionHandler:) of URLSessionTaskDelegate. Method you use is for connection-level challenge, not for app-level.
Using either NSURLConnection or NSURLSession, how can I tell when data has started to arrive? My download may be large or my connection slow so I want to make an update to the UI once data starts to arrive.
NSUrlSession has a delegate that should help you, if you're interested in knowing the each block of data is received.
func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive data: Data)
And this delegate if just the headers received is useful.
optional func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: #escaping (URLSession.ResponseDisposition) -> Void)
I'm trying to use SocketIO to connect to a server running on my workstation from an iOS/Swift app. The certificate I'm using on the server is self-signed and I'm using the following code to connect:
import UIKit
import SocketIO
class ViewController: UIViewController, URLSessionDelegate {
let socketManager = SocketManager(socketURL: URL(string:"https://my_server_url")!,
config: [SocketIOClientOption.log(true),
SocketIOClientOption.forcePolling(true),
SocketIOClientOption.selfSigned(true),
SocketIOClientOption.sessionDelegate(self)])
func URLSession(_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?)
-> Void) {
print("didReceive challenge")
completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
}
Unfortunately I get the following error: Argument type '(ViewController) -> () -> (ViewController)' does not conform to expected type 'URLSessionDelegate' where I set the sessionDelegate to self.
I'm not sure why that is the case since all funcs in URLSessionDelegate are optional and I thought that my implementation of URLSession conformed to the protocol. I apologise if I'm doing something dumb but I'm new to iOS and Swift and I've been looking for an answer for hours with no success!
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
// Pass test server with self signed certificate
if challenge.protectionSpace.host == "self-signed-certificate.server.com" {
completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
} else {
completionHandler(.performDefaultHandling, nil)
}
}
I want to download a file from a certain url .
what I need is the data delegate ( the delegate which gives me the downloaded data) .
I implemented the :
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
delegate but it doesn't get called .
my code is :
func download(url: URL)
{
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
let task = session.downloadTask(with: url)
task.resume()
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
print("apending ")
self.dataaa.append(data)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
print("here")
}
the two delegate functions don't get called !
You're using the delegate methods for a data task, but you're creating a download task. Those two task types work differently and use completely different delegate methods.
I would like to process data as it comes in, so I've instiated a URL session like so:
let session = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: operationQueue);
I also set the class to be a URLSessionDataDelegate:
class ViewController: UITableViewController, URLSessionDataDelegate{
Lastly, I implement the didReceive data function like this:
func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive data: Data){
print(data)
}
However, the function is never being called.
I run my session like this:
let session1 = session.dataTask(with: url) { (data, response, error) in
print(data!);
}
It prints the data from the callback, but not from the delegate. Any help is greatly appreciated.
EDIT:
I also added the following:
func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: #escaping (URLSession.ResponseDisposition) -> Void){
completionHandler(URLSession.ResponseDisposition.allow);
}
However, the delegate method is still not being called.
The reason this is not working is that you're creating your data task with a completion handler. Instead you must use the dataTask(with:) function instead.
I also missed this in the documentation and spent hours wondering why my delegate methods weren't being called.
From the docs:
By using the completion handler, the task bypasses calls to delegate methods for response and data delivery, and instead provides any resulting
NSData, URLResponse, and NSError objects inside the completion handler. Delegate methods for handling authentication challenges, however, are still called.