I am trying to move over from NSURLConnection over to NSURLSession for a SOAP post, but seem to have issues with the NSURLSessionDataDelegate.
Here is the old code in NSURLConnection that works fine:
let soapMessage = "<?xml version='1.0' encoding='UTF-8'?><SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' xmlns:ns1='http://tempuri.org/'><SOAP-ENV:Body><ns1:get_Countries/></SOAP-ENV:Body></SOAP-ENV:Envelope>"
print("Soap Packet is \(soapMessage)")
let urlString = "https://example.com/Service.svc"
let url = NSURL(string: urlString)
let theRequest = NSMutableURLRequest(URL: url!)
let msgLength = String(soapMessage.characters.count)
theRequest.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
theRequest.addValue(msgLength, forHTTPHeaderField: "Content-Length")
theRequest.addValue("http://tempuri.org/IService/get_Countries", forHTTPHeaderField: "SoapAction")
theRequest.HTTPMethod = "POST"
theRequest.HTTPBody = soapMessage.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
print("Request is \(theRequest.allHTTPHeaderFields!)")
let connection = NSURLConnection(request: theRequest, delegate: self, startImmediately: false)
connection?.start()
This code then uses NSURLConnectionDelegate, and works fine as follows:
func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
MutableData.length = 0;
let httpresponse = response as? NSHTTPURLResponse
print("status \(httpresponse?.statusCode)")
//print("headers \(httpresponse?.allHeaderFields)")
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
MutableData.appendData(data)
}
func connection(connection: NSURLConnection, didFailWithError error: NSError) {
NSLog("Error with Soap call: %#", error)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
let xmlParser = NSXMLParser(data: MutableData)
xmlParser.delegate = self
xmlParser.parse()
xmlParser.shouldResolveExternalEntities = true
}
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust && challenge.protectionSpace.host == "example.com" {
NSLog("yep")
let credential = NSURLCredential(trust: challenge.protectionSpace.serverTrust!)
challenge.sender!.useCredential(credential, forAuthenticationChallenge: challenge)
} else {
NSLog("nope")
challenge.sender!.performDefaultHandlingForAuthenticationChallenge!(challenge)
}
}
So that code all works fine, and is just for reference so you can see what I have done in the past, and the fact that the API actually does work! However, if I move over to using NSURLSession and NSURLSessionDataDelegate instead then I cannot get it working correctly.
So here is the new code:
let soapMessage = "<?xml version='1.0' encoding='UTF-8'?><SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' xmlns:ns1='http://tempuri.org/'><SOAP-ENV:Body><ns1:get_Countries/></SOAP-ENV:Body></SOAP-ENV:Envelope>"
print("Soap Packet is \(soapMessage)")
let urlString = "https://example.com/Service.svc"
let url = NSURL(string: urlString)
let theRequest = NSMutableURLRequest(URL: url!)
let msgLength = String(soapMessage.characters.count)
theRequest.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
theRequest.addValue(msgLength, forHTTPHeaderField: "Content-Length")
theRequest.addValue("http://tempuri.org/IService/get_Countries", forHTTPHeaderField: "SoapAction")
theRequest.HTTPMethod = "POST"
theRequest.HTTPBody = soapMessage.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
print("Request is \(theRequest.allHTTPHeaderFields!)")
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration:config, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
let task = session.dataTaskWithRequest(theRequest)
task.resume()
My delegates I am using are NSURLSessionDelegate, NSURLSessionDataDelegate:
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
print("Am in NSURLSessionDelegate didReceiveChallenge")
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust && challenge.protectionSpace.host == "example.com" {
NSLog("yep authorised")
let credential = NSURLCredential(trust: challenge.protectionSpace.serverTrust!)
challenge.sender!.useCredential(credential, forAuthenticationChallenge: challenge)
} else {
NSLog("nope")
challenge.sender!.performDefaultHandlingForAuthenticationChallenge!(challenge)
}
}
func URLSessionDidFinishEventsForBackgroundURLSession(session: NSURLSession) {
print("Am in URLSessionDidFinishEventsForBackgroundURLSession")
let xmlParser = NSXMLParser(data: MutableData)
xmlParser.delegate = self
xmlParser.parse()
xmlParser.shouldResolveExternalEntities = true
}
func URLSession(session: NSURLSession, didBecomeInvalidWithError error: NSError?) {
print("error of \(error)")
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
print("Am in didReceiveResponse")
MutableData.length = 0
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
print("Am in didReceiveData")
MutableData.appendData(data)
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
print("error of \(error)")
}
So, when I run the code, I get output:
"Am in NSURLSessionDelegate didReceiveChallenge"
"yep authorised"
So it's getting to didReceiveChallenge fine, and it appears to be authorising the HTTPS secure certificate fine, but then nothing further happens, it doesn't do anything else, I'd expect it to go into didReceiveResponse then didReceiveData, but nothing further happens at all.
So I am stuck, I could of course continue and use NSURLConnection as it all works fine, but I'd like to understand the NSURLSession, and particulary where I am going wrong. So if anyone can help that would be great.
Thanks
In case anyone else has the same issue, I sorted this out. The issue was I was not using the completionHandler in the didReceiveChallenge and didReceiveResponse delegates
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
completionHandler(NSURLSessionResponseDisposition.Allow) //.Cancel,If you want to stop the download
}
Related
I have implemented a background upload task using URLSession as follow:
let boundary = UUID().uuidString
let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.upload")
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: .main)
var urlRequest = URLRequest(url: URL(string: "https://server/file.php")!)
urlRequest.httpMethod = "POST"
urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var data = Data()
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"video\"; filename=\"rawvideo.mp4\"\r\n".data(using: .utf8)!)
data.append("Content-Type: video/mp4\r\n\r\n".data(using: .utf8)!)
data.append(videoData!)
data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
let tempDir = FileManager.default.temporaryDirectory
let localURL = tempDir.appendingPathComponent("upload")
try? data.write(to: localURL)
session.uploadTask(with: urlRequest, fromFile: localURL).resume()
To handle the server response, I have used the following code [But I am not getting any print output. Seems this method is never called]
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
print("anoop")
guard let response = response as? HTTPURLResponse, (200...209).contains(response.statusCode) else {
DispatchQueue.main.async {
self.uploadComplete("Error", "Upload Server Down!")
}
print("error")
completionHandler(.cancel)
return
}
completionHandler(.allow)
}
To handle the server Data, I am using following code:
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if let toDictionary = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary {
print(toDictionary)
}
}
To handle the completion, I am using following code:
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print(error.localizedDescription)
}
}
Additionally, I have a progress bar which I am updating as follows:
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
let uploadProgress:Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
let percent = Int(uploadProgress * 100)
DispatchQueue.main.async {
self.progressLabel.text = "\(percent)% completed"
self.progressView.setProgress(Float(uploadProgress), animated: true)
}
}
This code works well if the server is working fine [Response: 200].
But if I change the request URL to a dummy url like https://server/dummyfile.php, the progress bar still increments and I don't get any error since
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void)
doesn't work.
Is it possible that in case of server errors [404, 500] the progress bar doesn't increment?
Notice that i've tried so many solution like this and none worked.
here is screenshot and code;
func downloadItems() {
let urlE: String = "http://alicelik.me/forios/service.php"
guard let url = URL(string: urlE) else {
print("Error: cannot")
return
}
let configuration = URLSessionConfiguration.default
var session = URLSession(configuration: configuration)
let task = session.dataTask(with: url as URL){
(data, response, error) in
guard error == nil else {
print("error calling GET on /todos/1")
print(error)
}
return()
}
task.resume()
}
Here is URLSession func;
#nonobjc func URLSession(session: URLSessionConfiguration, dataTask: URLSessionDataTask, didReceiveData data: NSData) {
self.data.append(data as Data);
}
#nonobjc func URLSession(session: URLSessionConfiguration, task: URLSessionTask, didCompleteWithError error: NSError?) {
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON()
}
}
note: *URLSession and *URLSessionConfiguration in func URLSession(session *) giving same error.
I had the same problem, solved it by:
var session = Foundation.URLSession(configuration: configuration)
Changing URLSession func will fix issue.
func downloadItems() {
let url: NSURL = NSURL(string: urlPath)!
var session = URLSession()
let configuration = URLSessionConfiguration.default
session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let task = session.dataTask(with: url as URL)
task.resume()
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
self.data.append(data as Data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if error != nil {
print("Failed to download data")
} else {
print("Data downloaded")
self.parseJSON()
}
}
public func dataTaskWithRequest(request: NSURLRequest, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDataTask
I know the usage method of above.
public func dataTaskWithRequest(request: NSURLRequest) -> NSURLSessionDataTask
But what is the usage of this method? I'm confused about how it is used.All examples I see are using the first method. The method returns no data, no error to handle, nor any response? Or is that you somehow envelope this as a task and later run in a queue?
You use this latter method if you've specified a delegate for a custom NSURLSession. The data is not returned to the closure, but rather the session calls your delegate's didReceiveData, which you have to implement separately.
It takes more work to implement the delegate methods, though, so you generally only do that where you absolutely have to (e.g. you want to process data as it comes in rather than waiting for all of the data to come in; you need delegate methods for custom handling of redirects and challenges; you are doing background NSURLSession with download or upload tasks rather than data tasks; etc.).
For example, to issue a simple GET request, expecting JSON response, you might define your class to conform to URLSessionDelegate, URLSessionDataDelegate, and, optionally, URLSessionTaskDelegate (called NSURLSessionDelegate, NSURLSessionDataDelegate and NSURLSessionTaskDelegate, respectively, in Objective-C and Swift 2), and then do something like the following in Swift 3:
var responseData: Data?
func performRequest() {
let url = URL(string: "http://ip.jsontest.com/")!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Accept")
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
responseData = Data()
let task = session.dataTask(with: request)
task.resume()
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
responseData!.append(data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard error == nil else {
print(error!)
return
}
do {
guard let jsonObject = try JSONSerialization.jsonObject(with: responseData!) as? [String: AnyObject] else {
print("response was not JSON dictionary")
print("responseString: \(String(data: responseData!, encoding: .utf8))")
return
}
print(jsonObject)
} catch let parseError {
print("JSON Error: \(parseError)")
}
}
Or, in Swift 2:
var responseData: NSMutableData?
func performRequest() {
let url = NSURL(string: "http://ip.jsontest.com/")!
let request = NSMutableURLRequest(URL: url)
request.setValue("application/json", forHTTPHeaderField: "Accept")
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
responseData = NSMutableData()
let task = session.dataTaskWithRequest(request)
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
responseData!.appendData(data)
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
guard error == nil else {
print(error!)
return
}
do {
guard let jsonObject = try NSJSONSerialization.JSONObjectWithData(responseData!, options: []) as? [String: AnyObject] else {
print("response was not JSON dictionary")
print("responseString: \(String(data: responseData!, encoding: NSUTF8StringEncoding))")
return
}
print(jsonObject)
} catch let parseError {
print("JSON Error: \(parseError)")
}
}
Clearly, you wouldn't do this unless you needed some of the more complicated delegate methods, too, but I wanted to show a minimalist implementation (which helps us appreciate the completion handler rendition).
public func dataTaskWithRequest(request: NSURLRequest, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDataTask
returns data, response and error directly in a completion handler.
public func dataTaskWithRequest(request: NSURLRequest) -> NSURLSessionDataTask
is used in conjunction with the URLSessionDataDelegate protocol. A couple of methods are required to be implemented. During the several stages the delegate methods are called to process the data.
The protocol method provides finer control of the loading process, for example to handle credential requests and redirection control.
I'm using URLSession to retrieve data from an url
func scheduleURLSession() {
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: "myIdentifier...")
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
let retrieveTask = backgroundSession.downloadTask(with: URL(string: "https://api.wedsblog.net/v2/index.php?bitcoin&simple")!)
retrieveTask.resume()
}
When download finished
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { }
Will be called. How can I read the content of downloaded file? Is ".downloadTask" the right function or shall I use another one?
Found following solution:
Implemented URLSessionDataDelegate
func scheduleURLSession() {
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: "myIdentifier")
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
let retrieveTask = backgroundSession.dataTask(with: URL(string: "https://api.wedsblog.net/v2/index.php?bitcoin&simple")!)
retrieveTask.resume()
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
print(String(data: data, encoding: String.Encoding.utf8)!)
}
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) {(data, response, error) -> Void in { // First check if error is not nil
// Second check what is status code of your http response
// Third do with data what you want(parse to objects)
}
I think it helpful.
I want to call web service for Swift 2. But it never works. This is my code.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate, NSURLConnectionDelegate, NSXMLParserDelegate {
var mutableData:NSMutableData = NSMutableData.init()
var currentElementName:NSString = ""
#IBOutlet var txtCelsius : UITextField!
#IBOutlet var txtFahrenheit : UITextField!
#IBAction func actionConvert(sender : AnyObject) {
let celcius = txtCelsius.text
let soapMessage = "<?xml version='1.0' encoding='utf-8'?><soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'><soap:Body><CelsiusToFahrenheit xmlns='http://www.w3schools.com/xml/'><Celsius>\(celcius)</Celsius></CelsiusToFahrenheit></soap:Body></soap:Envelope>"
let urlString = "http://www.w3schools.com/xml/tempconvert.asmx"
let url = NSURL(string: urlString)
let theRequest = NSMutableURLRequest(URL: url!)
theRequest.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
theRequest.addValue((soapMessage), forHTTPHeaderField: "Content-Length")
theRequest.HTTPMethod = "POST"
theRequest.HTTPBody = soapMessage.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) // or false
let connection = NSURLConnection(request: theRequest, delegate: self, startImmediately: true)
connection!.start()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
mutableData.length = 0;
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
mutableData.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
let xmlParser = NSXMLParser(data: mutableData)
xmlParser.delegate = self
xmlParser.parse()
xmlParser.shouldResolveExternalEntities = true
}
func parser(parser: NSXMLParser, foundCharacters string: String) {
if currentElementName == "CelsiusToFahrenheit" {
txtFahrenheit.text = string
}
}
NSURLConnection is deprecated, use NSURLSession instead.
Here's an example of a function doing what you want with NSURLSession and a callback:
func getFarenheit(celsius celsius: Int, completion: (result: String) -> Void) {
let soapMessage = "<?xml version='1.0' encoding='utf-8'?><soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'><soap:Body><CelsiusToFahrenheit xmlns='http://www.w3schools.com/xml/'><Celsius>\(celsius)</Celsius></CelsiusToFahrenheit></soap:Body></soap:Envelope>"
let urlString = "http://www.w3schools.com/xml/tempconvert.asmx"
if let url = NSURL(string: urlString) {
let theRequest = NSMutableURLRequest(URL: url)
theRequest.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
theRequest.addValue((soapMessage), forHTTPHeaderField: "Content-Length")
theRequest.HTTPMethod = "POST"
theRequest.HTTPBody = soapMessage.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
NSURLSession.sharedSession().dataTaskWithRequest(theRequest) { (data, response, error) in
if error == nil {
if let data = data, result = String(data: data, encoding: NSUTF8StringEncoding) {
completion(result: result)
}
} else {
print(error!.debugDescription)
}
}.resume()
}
}
Use it like this with a "trailing closure":
getFarenheit(celsius: 42) { (result) in
print(result)
}
It prints the data containing the XML and the converted value:
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CelsiusToFahrenheitResponse xmlns="http://www.w3schools.com/xml/"><CelsiusToFahrenheitResult>107.6</CelsiusToFahrenheitResult></CelsiusToFahrenheitResponse></soap:Body></soap:Envelope>