How to send HTTPS POST request in swift using NSURLSession - ios

I've been developing using HTTP. The code below works great when connecting with the development server using HTTP. However, when I change the scheme to https, it doesn't send a successful https post to the server.
What else do I need to do to switch from HTTP POST to HTTPS POST?
class func loginRemote(successHandler:()->(), errorHandler:(String)->()) {
let user = User.sharedInstance
// this is where I've been changing the scheme to https
url = NSURL(String: "http://url.to/login.page")
let request = NSMutableURLRequest(URL: url)
let bodyData = "email=\(user.email)&password=\(user.password)"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
request.HTTPMethod = "POST"
let session = NSURLSession.sharedSession()
// posting login request
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
if let httpResponse = response as? NSHTTPURLResponse {
if httpResponse.statusCode == 200 {
// email+password were good
successHandler()
} else {
// email+password were bad
errorHandler("Status: \(httpResponse.statusCode) and Response: \(httpResponse)")
}
} else {
NSLog("Unwrapping NSHTTPResponse failed")
}
})
task.resume()
}

You will have to implement one of the NSURLSessionDelegate methods so that it will accept the SSL certificate.
class YourClass: Superclass, NSURLSessionDelegate {
class func loginRemote(successHandler:()->(), errorHandler:(String)->()) {
// ...
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(),
delegate: self,
delegateQueue: nil)
// ...
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let credential = NSURLCredential(trust: challenge.protectionSpace.serverTrust)
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, credential)
}
}
}
WARNING: This will blindly accept any SSL certificate/connection that you attempt. This is not a safe practice, but it will allow you to test your server using HTTPS.
UPDATE: Swift 4+
class YourClass: Superclass, URLSessionDelegate {
class func loginRemote(successHandler: ()->(), errorHandler:(String)->()) {
// ...
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
// ...
}
func urlSession(_ session: URLSession, didReceiveChallenge challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
if let trust = challenge.protectionSpace.serverTrust {
completionHandler(.useCredential, URLCredential(trust: trust))
}
}
}
}

Related

URLSession.datatask with request block not called in background

URLSession data task block is not calling when the app is in background and it stuck at dataTask with request.
When I open the app the block gets called. By the way I'm using https request.
This is my code:
let request = NSMutableURLRequest(url: URL(string: url as String)!,
cachePolicy: .reloadIgnoringCacheData,
timeoutInterval:20)
request.httpMethod = method as String
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let session = URLSession.shared
let data = params.data(using: String.Encoding.utf8.rawValue)
request.httpBody = data
session.dataTask(with: request as URLRequest,completionHandler:
{(data, response, error) -> Void in
if error == nil
{
do {
let result = try JSONSerialization.jsonObject(with: data!, options:
JSONSerialization.ReadingOptions.mutableContainers)
print(result)
completionHandler(result as AnyObject?,nil)
}
catch let JSONError as NSError{
completionHandler(nil,JSONError.localizedDescription as NSString?)
}
}
else{
completionHandler(nil,error!.localizedDescription as NSString?)
}
}).resume()
Working perfectly when the app is in active state. Is there anything wrong in my code. please point me
If you want downloads to progress after your app is no longer in foreground, you have to use background session. The basic constraints of background sessions are outlined in Downloading Files in Background, and are essentially:
Use delegate-based URLSession with background URLSessionConfiguration.
Use upload and download tasks only, with no completion handlers.
In iOS, Implement application(_:handleEventsForBackgroundURLSession:completionHandler:) app delegate, saving the completion handler and starting your background session.
Implement urlSessionDidFinishEvents(forBackgroundURLSession:) in your URLSessionDelegate, calling that saved completion handler to let OS know you're done processing the background request completion.
So, pulling that together:
func startRequest(for urlString: String, method: String, parameters: String) {
let url = URL(string: urlString)!
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 20)
request.httpMethod = method
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpBody = parameters.data(using: .utf8)
BackgroundSession.shared.start(request)
}
Where
class BackgroundSession: NSObject {
static let shared = BackgroundSession()
static let identifier = "com.domain.app.bg"
private var session: URLSession!
#if !os(macOS)
var savedCompletionHandler: (() -> Void)?
#endif
private override init() {
super.init()
let configuration = URLSessionConfiguration.background(withIdentifier: BackgroundSession.identifier)
session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
func start(_ request: URLRequest) {
session.downloadTask(with: request).resume()
}
}
extension BackgroundSession: URLSessionDelegate {
#if !os(macOS)
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
self.savedCompletionHandler?()
self.savedCompletionHandler = nil
}
}
#endif
}
extension BackgroundSession: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
// handle failure here
print("\(error.localizedDescription)")
}
}
}
extension BackgroundSession: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
do {
let data = try Data(contentsOf: location)
let json = try JSONSerialization.jsonObject(with: data)
print("\(json)")
// do something with json
} catch {
print("\(error.localizedDescription)")
}
}
}
And the iOS app delegate does:
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
BackgroundSession.shared.savedCompletionHandler = completionHandler
}
Or simply start a BackgroundTask
func send(...) {
let backgroundTaskID = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
let session = URLSession(configuration: .default)
// ... after all the main logic, when you finish:
DispatchQueue.main.async {
completion(result)
UIApplication.shared.endBackgroundTask(backgroundTaskID)
}
}
You need a background session. The URLSessionDataTask which as per Apple's documentation doesn't support background downloads.
Create a URLSessionDownloadTask and use its delegate method it should work.
Follow this link
[URLSessionDownloadTask setDownloadTaskDidWriteDataBlock:^(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
CGFloat percentDone = (double)(totalBytesWritten)/(double)totalBytesExpectedToWrite;
[SVProgressHUD showWithStatus:[NSString stringWithFormat:#"%.2f%%",percentDone*100]];
}];
[downloadTask resume];
// Apply as shown in picture
/*********************/

How to Make synchronous request using NSOperationQueue?

I newbie for developing the ios application, I am trying to connect https web server, I had faced issue I am not able to connect the self signed certficate, so I have searched net In that I have followed below given code its working fine. but its working asynchronous mode.so I need until complete the task wait and return the data.Please gave any idea?/
func requestAsynChronousData(request: NSURLRequest)->NSData? {
let data: NSData? = nil
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue:NSOperationQueue.mainQueue())
let task = session.dataTaskWithRequest(request){ (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
if error == nil {
print("RESULTTTT\(data)")
}
}
task.resume()
return data;
}
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
print("WAITING!!!!!!!!!!!!!COMPLETIONHANDLER")
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!))
}
You need add dispatch_semaphore_create to you code like below
func requestAsynChronousData(request: NSURLRequest)->NSData? {
let data: NSData? = nil
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let semaphore = dispatch_semaphore_create(0)
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue:NSOperationQueue.mainQueue())
let task = session.dataTaskWithRequest(request){ (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
if error == nil {
print("RESULTTTT\(data)")
}
dispatch_semaphore_signal(semaphore)
}
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return data;
}
but this way will block your main thread while your completion handler will done
I have found the solution.
func requestAsynChronousData(request: NSURLRequest)->NSData? {
let data: NSData? = nil
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue:nil)
let task = session.dataTaskWithRequest(request){ (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
if error == nil {
data=taskData
print("RESULTTTT\(data)")
}
dispatch_semaphore_signal(semaphore)
}
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return data;
}
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
print("WAITING!!!!!!!!!!!!!COMPLETIONHANDLER")
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!))
}
Alternatively, you could set the maximum number of operations on your queue to 1.
let operationQueue = NSOperationQueue()
operationQueue.maxConcurrentOperationCount = 1
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue:operationQueue)
let task = session.dataTaskWithRequest(request){ (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
if error == nil {
print("RESULTTTT\(data)")
}
}
operationQueue.addOperation(task)
This will force the operations in this queue to act synchronously without using semaphores which will allow NSOperationQueue to clean up itself

Custom NSURLProtocol with NSURLSession

I'm trying to implement this tutorial which implements a custom NSURLProtocol with NSURLConnection.
https://www.raywenderlich.com/76735/using-nsurlprotocol-swift
It works as expected, but now that NSURLConnection is deprecated in iOS9, I'm trying to convert it to NSURLSession.
Unfortunatly it didn't work.
I'm loading a website in uiwebview, if I use NSURLConnection it loads and everything work as expected, all http requests from the webview is captured, but not when using NSURLSession.
Any help is appreciated.
here is my code
import UIKit
class MyProtocol: NSURLProtocol, NSURLSessionDataDelegate, NSURLSessionTaskDelegate, NSURLSessionDelegate {
//var connection: NSURLConnection!
var mutableData: NSMutableData!
var response: NSURLResponse!
var dataSession: NSURLSessionDataTask!
override class func canInitWithRequest(request: NSURLRequest) -> Bool {
if NSURLProtocol.propertyForKey("MyURLProtocolHandledKey", inRequest: request) != nil {
return false
}
return true
}
override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
return request
}
override class func requestIsCacheEquivalent(aRequest: NSURLRequest,
toRequest bRequest: NSURLRequest) -> Bool {
return super.requestIsCacheEquivalent(aRequest, toRequest:bRequest)
}
override func startLoading() {
let newRequest = self.request.mutableCopy() as! NSMutableURLRequest
NSURLProtocol.setProperty(true, forKey: "MyURLProtocolHandledKey", inRequest: newRequest)
self.dataSession = NSURLSession.sharedSession().dataTaskWithRequest(newRequest)
dataSession.resume()
self.mutableData = NSMutableData()
}
override func stopLoading() {
print("Data task stop")
self.dataSession.cancel()
self.mutableData = nil
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
self.response = response
self.mutableData = NSMutableData()
print(mutableData)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.client?.URLProtocol(self, didLoadData: data)
self.mutableData.appendData(data)
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if (error == nil)
{
self.client!.URLProtocolDidFinishLoading(self)
self.saveCachedResponse()
}
else
{
self.client?.URLProtocol(self, didFailWithError: error!)
}
}
func saveCachedResponse () {
let timeStamp = NSDate()
let urlString = self.request.URL?.absoluteString
let dataString = NSString(data: self.mutableData, encoding: NSUTF8StringEncoding) as NSString?
print("TiemStamp:\(timeStamp)\nURL: \(urlString)\n\nDATA:\(dataString)\n\n")
}
}
I've solved it.
Here is the code if anyone needs it.
import Foundation
class MyProtocol1: NSURLProtocol, NSURLSessionDataDelegate, NSURLSessionTaskDelegate
{
private var dataTask:NSURLSessionDataTask?
private var urlResponse:NSURLResponse?
private var receivedData:NSMutableData?
class var CustomKey:String {
return "myCustomKey"
}
// MARK: NSURLProtocol
override class func canInitWithRequest(request: NSURLRequest) -> Bool {
if (NSURLProtocol.propertyForKey(MyProtocol1.CustomKey, inRequest: request) != nil) {
return false
}
return true
}
override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
return request
}
override func startLoading() {
let newRequest = self.request.mutableCopy() as! NSMutableURLRequest
NSURLProtocol.setProperty("true", forKey: MyProtocol1.CustomKey, inRequest: newRequest)
let defaultConfigObj = NSURLSessionConfiguration.defaultSessionConfiguration()
let defaultSession = NSURLSession(configuration: defaultConfigObj, delegate: self, delegateQueue: nil)
self.dataTask = defaultSession.dataTaskWithRequest(newRequest)
self.dataTask!.resume()
}
override func stopLoading() {
self.dataTask?.cancel()
self.dataTask = nil
self.receivedData = nil
self.urlResponse = nil
}
// MARK: NSURLSessionDataDelegate
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask,
didReceiveResponse response: NSURLResponse,
completionHandler: (NSURLSessionResponseDisposition) -> Void) {
self.client?.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
self.urlResponse = response
self.receivedData = NSMutableData()
completionHandler(.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.client?.URLProtocol(self, didLoadData: data)
self.receivedData?.appendData(data)
}
// MARK: NSURLSessionTaskDelegate
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error != nil && error!.code != NSURLErrorCancelled {
self.client?.URLProtocol(self, didFailWithError: error!)
} else {
saveCachedResponse()
self.client?.URLProtocolDidFinishLoading(self)
}
}
// MARK: Private methods
/**
Do whatever with the data here
*/
func saveCachedResponse () {
let timeStamp = NSDate()
let urlString = self.request.URL?.absoluteString
let dataString = NSString(data: self.receivedData!, encoding: NSUTF8StringEncoding) as NSString?
print("TimeStamp:\(timeStamp)\nURL: \(urlString)\n\nDATA:\(dataString)\n\n")
}
}
Swift 3 version:
// CustomURLProtocol.swift
class CustomURLProtocol: URLProtocol, URLSessionDataDelegate, URLSessionTaskDelegate {
private var dataTask: URLSessionDataTask?
private var urlResponse: URLResponse?
private var receivedData: NSMutableData?
class var CustomHeaderSet: String {
return "CustomHeaderSet"
}
// MARK: NSURLProtocol
override class func canInit(with request: URLRequest) -> Bool {
guard let host = request.url?.host, host == "your domain.com" else {
return false
}
if (URLProtocol.property(forKey: CustomURLProtocol.CustomHeaderSet, in: request as URLRequest) != nil) {
return false
}
return true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
return request
}
override func startLoading() {
let mutableRequest = NSMutableURLRequest.init(url: self.request.url!, cachePolicy: NSURLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval: 240.0)//self.request as! NSMutableURLRequest
//Add User Agent
var userAgentValueString = "myApp"
mutableRequest.setValue(userAgentValueString, forHTTPHeaderField: "User-Agent")
print(mutableRequest.allHTTPHeaderFields ?? "")
URLProtocol.setProperty("true", forKey: CustomURLProtocol.CustomHeaderSet, in: mutableRequest)
let defaultConfigObj = URLSessionConfiguration.default
let defaultSession = URLSession(configuration: defaultConfigObj, delegate: self, delegateQueue: nil)
self.dataTask = defaultSession.dataTask(with: mutableRequest as URLRequest)
self.dataTask!.resume()
}
override func stopLoading() {
self.dataTask?.cancel()
self.dataTask = nil
self.receivedData = nil
self.urlResponse = nil
}
// MARK: NSURLSessionDataDelegate
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
self.urlResponse = response
self.receivedData = NSMutableData()
completionHandler(.allow)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
self.client?.urlProtocol(self, didLoad: data as Data)
self.receivedData?.append(data as Data)
}
// MARK: NSURLSessionTaskDelegate
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if error != nil { //&& error.code != NSURLErrorCancelled {
self.client?.urlProtocol(self, didFailWithError: error!)
} else {
//saveCachedResponse()
self.client?.urlProtocolDidFinishLoading(self)
}
}
}
The problem you are having with your code is that you are using the the NSURLSession.sharedSession to contain your data task. By using the shared session, you are not able to change the session delegate so none of your delegate routines are going to be invoked.
You will need to create a custom session with your protocol established as the delegate for the session. Then, when asked to start loading you can create a data task in that session.
From the documentation of URLSession:
Important
The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you don’t invalidate the session, your app leaks memory until it exits.
Also:
Note
Be careful to not create more sessions than you need. For example, if you have several parts of your app that need a similarly configured session, create one session and share it among them.
So I would move the creation of the URLSession from the startLoading method to the URLProtocol subclass initializer:
class MyURLProtocol: URLProtocol, URLSessionDataDelegate,URLSessionTaskDelegate {
override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
super.init(request: request, cachedResponse: cachedResponse, client: client)
defaultSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
}
private var defaultSession: URLSession?

iOS first app, accept untrusted certificate for hello world

I am making a basic "hello world" iOS app. I have an ubuntu server in the cloud which I want to query from the iOS app. I understand that the server needs to be secure, ie, needs to be accessed via https request, and the certificate on the server needs to be "trusted".
Right now what I am trying to do is override this requirement. I have a self-signed certificate that is working for https on my server. When I make the request with the iOS app, it gives me some errors about NSURLErrorFailingURLPeerTrustErrorKey and even one line returned saying: NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?.
I know this is a common issue and there are many threads on this site about how to deal with it. I tried a piece of code from this post. I added this piece to my code:
func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust{
let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential);
}
}
My entire ViewController code is here:
import UIKit
class ViewController: UIViewController, UITextFieldDelegate, NSURLSessionDelegate {
// MARK: Properties
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var mealNameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Handle the text field’s user input through delegate callbacks.
nameTextField.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: UITextFieldDelegate
func textFieldShouldReturn(textField: UITextField) -> Bool {
// Hide the keyboard.
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField: UITextField) {
mealNameLabel.text = nameTextField.text
}
// MARK: Actions
#IBAction func setDefaultLabelText(sender: UIButton) {
mealNameLabel.text = "Default Text"
post_request()
}
func post_request(){
let request = NSMutableURLRequest(URL: NSURL(string: "https://54.164.XXX.XX/post_script.php")!)
request.HTTPMethod = "POST"
let postString = "id=13&name=Jack"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print("error=\(error)")
return
}
print("response = \(response)")
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("responseString = \(responseString)")
}
task.resume()
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust{
let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential);
}
}
}
It looks like the idea is to pick up the challenge event from the connection, and tell it that "yes I want to proceed". But that piece of code does not seem to be getting called. The post request gets sent, but I receive the error messages about the untrusted certificate. Can someone help me fix my code so that I can accept this certificate from my server?
You need to make your ViewController NSURLSessionDelegate to receive the callbacks and set the session's delegate to be your controller, like this:
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
...
let task = session.dataTaskWithRequest(request) { data, response, error in
}
task.resume()
You cannot use the shared session to get those delegate methods. You must instantiate a new NSURLSession and declare yourself as the delegate.
// Delegate methods won't be callend
let task = NSURLSession.sharedSession()...
// Use this to get delegate calls
let task = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(),
delegate: self,
delegateQueue: /* make a queue */)...
With iOS 9, you will also need to look into NSAppTransportSecurity and adding those keys to your plist. These are needed in iOS 9 to allow SSL connections.

Preventing URLSession redirect in Swift

I need to fetch a redirecting URL but prevent redirection in Swift. From other posts and Apple docs I understand I must implement the delegate method URLSession(session:, task:, willPerformHTTPRedirection response:, request:, completionHandler:) and return nil via the completion closure. But I can't find examples in swift, nor figure out the right way to do it. The code below reproduces my issue in playground: the delegate does not seem to get executed.
import Foundation
import XCPlayground
XCPSetExecutionShouldContinueIndefinitely(continueIndefinitely: true)
class MySession: NSObject, NSURLSessionDelegate, NSURLSessionTaskDelegate {
// trying to follow instructions at https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSessionTaskDelegate_protocol/index.html#//apple_ref/occ/intfm/NSURLSessionTaskDelegate/URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:
// to prevent redirection -- DOES NOT SEEM TO GET CALLED
func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: (NSURLRequest!) -> Void) {
println("in URLSession delegate") // NEVER PRINTS
completionHandler(nil) // NO EFFECT
}
// fetch data from URL with NSURLSession
class func getDataFromServerWithSuccess(myURL: String, success: (response: String!) -> Void) {
var session = NSURLSession.sharedSession()
let loadDataTask = session.dataTaskWithURL(NSURL(string: myURL)!) { (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
// OMITTING ERROR CHECKING FOR BREVITY
success(response: NSString(data: data!, encoding: NSASCIIStringEncoding) as String)
}
loadDataTask.resume()
}
// extract data from redirect
class func getRedirectionInfo(url: String) {
getDataFromServerWithSuccess(url) {(data) -> Void in
if let html = data {
if html.rangeOfString("<html><head><title>Object moved</title>", options: .RegularExpressionSearch) != nil {
println("success: redirection was prevented") // SHOULD PRINT THIS
} else {
println("failure: redirection went through") // INSTEAD PRINTS THIS
}
}
}
}
}
MySession.getRedirectionInfo("http://bit.ly/filmenczer") // ex. redirecting link
Please be gentle, I am a newbie. Thank you in advance for any assistance!
UPDATE: With many thanks to #nate I got it to work. The key insight is that in order for the delegate to be called, one must pass the delegate class to the NSURLSession() initializer, rather than using NSURLSession.sharedSession(). Passing nil as the delegate yields the customary behavior (with redirection). Here is working version of the code:
import Foundation
import XCPlayground
XCPSetExecutionShouldContinueIndefinitely(continueIndefinitely: true)
class MySession: NSObject, NSURLSessionDelegate, NSURLSessionTaskDelegate {
// to prevent redirection
func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: (NSURLRequest!) -> Void) {
completionHandler(nil)
}
// fetch data from URL with NSURLSession
class func getDataFromServerWithSuccess(myURL: String, noRedirect: Bool, success: (response: String!) -> Void) {
var myDelegate: MySession? = nil
if noRedirect {
myDelegate = MySession()
}
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: myDelegate, delegateQueue: nil)
let loadDataTask = session.dataTaskWithURL(NSURL(string: myURL)!) { (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
// OMITTING ERROR CHECKING FOR BREVITY
success(response: NSString(data: data!, encoding: NSASCIIStringEncoding) as String)
}
loadDataTask.resume()
}
// extract data from redirect
class func getRedirectionInfo(url: String) {
getDataFromServerWithSuccess(url, noRedirect: true) {(data) -> Void in
if let html = data {
if html.rangeOfString("<html>\n<head><title>Bitly</title>", options: .RegularExpressionSearch) != nil {
println("success: redirection was prevented")
} else {
println("failure: redirection went through")
}
}
}
}
}
MySession.getRedirectionInfo("http://bit.ly/filmenczer")
You have two things standing in your way with your current implementation.
You never set the delegate property on the NSURLSession instance that you're using to make the request. Without the delegate property set, your delegate methods won't ever be called. Instead of getting NSURLSession.sharedSession(), look at the NSURLSession(configuration:delegate:delegateQueue:) initializer. The first and last parameters can be NSURLSessionConfiguration.defaultSessionConfiguration() and nil, respectively, see below for more about the delegate.
Note that when you use the variant of session.dataTaskWithURL that has a completion handler, delegate methods that handle response and data delivery will be ignored, but authentication and redirection handlers are still used.
You'll have to refactor somewhat to use MySession as a delegate, since you're using class methods to make the request. You need an instance to use as the session's delegate.
I took a short and incomplete route to having the delegate pick up on the redirect with this alternate code—you'll need to refactor as in #3 to make sure you can still call your callback:
class func getDataFromServerWithSuccess(myURL: String, success: (response: String!) -> Void) {
let delegate = MySession()
var session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: delegate, delegateQueue: nil)
let task = session.dataTaskWithURL(NSURL(string: myURL)!) {
// ...
}
task.resume()
}
Hope that helps!
I also found the solution helpful, but I am using Swift 4.2 in my current project.
So, here is an adapted shorter version of the solution above, that also works with Swift 4.2 and Xcode 10.
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
class MySession: NSObject, URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: #escaping (URLRequest?) -> Void) {
completionHandler(nil)
}
}
func getDataFromServerWithSuccess(myURL: String, noRedirect: Bool) {
let myDelegate: MySession? = noRedirect ? MySession() : nil
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: myDelegate, delegateQueue: nil)
let loadDataTask = session.dataTask(with: URL(string:myURL)!) { (data, response, error) in
// OMITTING ERROR CHECKING FOR BREVITY
if let data = data {
if let dataString = String(bytes: data, encoding: .utf8) {
print(dataString)
if dataString.contains("Bitly") == true {
print("success: redirection was prevented")
} else {
print("failure: redirection went through")
}
}
}
}
loadDataTask.resume()
}
getDataFromServerWithSuccess(myURL: "http://bitly.com/filmenczer", noRedirect: true)
Minor adjustments to the solutions proposed above. This works with Swift 2 in XCode 7.
import UIKit
import Foundation
import XCPlayground
XCPSetExecutionShouldContinueIndefinitely(true)
class MySession: NSObject, NSURLSessionDelegate {
// to prevent redirection
func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: (NSURLRequest!) -> Void) {
completionHandler(nil)
}
// fetch data from URL with NSURLSession
class func getDataFromServerWithSuccess(myURL: String, noRedirect: Bool, success: (response: String!) -> Void) {
var myDelegate: MySession? = nil
if noRedirect {
myDelegate = MySession()
}
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: myDelegate, delegateQueue: nil)
let loadDataTask = session.dataTaskWithURL(NSURL(string: myURL)!) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
// OMITTING ERROR CHECKING FOR BREVITY
success(response: NSString(data: data!, encoding: NSASCIIStringEncoding) as! String)
}
loadDataTask.resume()
}
// extract data from redirect
class func getRedirectionInfo(url: String) {
getDataFromServerWithSuccess(url, noRedirect: true) {(data) -> Void in
if let html = data {
if html.rangeOfString("<html>\n<head><title>Bitly</title>", options: .RegularExpressionSearch) != nil {
print("success: redirection was prevented")
} else {
print("failure: redirection went through")
}
}
}
}
}
MySession.getRedirectionInfo("http://bit.ly/filmenczer")

Resources