12

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")
4
  • 1
    I think you should check your url, rather than html. if(url=="redirected url").
    – iphonic
    Commented Mar 16, 2015 at 6:00
  • Thank you @iphonic but I need to access the body of the response that has the redirect (Location) header. In any case the example code is just for illustration purposes to check if redirection is happening or not.
    – Fil
    Commented Mar 16, 2015 at 6:13
  • Yes, that you can do the way you are doing, just check url, instead of html, as url is the correct way to handle redirection than content.
    – iphonic
    Commented Mar 16, 2015 at 6:17
  • 2
    I do not understand @iphonic, by the time I am checking the content, the redirection has already occurred. I want to prevent it, not detect it. Thanks.
    – Fil
    Commented Mar 16, 2015 at 6:47

3 Answers 3

7

You have two things standing in your way with your current implementation.

  1. 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.

  2. 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!

4
  • I am studying your kind and very helpful explanations @nate , will be back to confirm once I get it to work. Thanks!
    – Fil
    Commented Mar 16, 2015 at 16:23
  • Okay @nate, I got it to work with your help. Your #1 was the key point: rather than using NSURLSession.sharedSession(), one needs to pass the delegate to the session initializer. It turns out #2 is not correct, however; it is okay to use the completion handler in session.dataTaskWithURL and the delagate does not get ignored. So the minimal refactoring needed per #3 is to pass a flag to getDataFromServerWithSuccess() to indicate whether redirection must be avoided. To prevent redirection, one then passes the delegate to the session initializer. Otherwise pass nil as delegate.
    – Fil
    Commented Mar 16, 2015 at 17:24
  • I will update my question with the working code. @nate, I will mark your answer as correct if you kindly revise it to remove or qualify your point #2. Thank you so much!!!
    – Fil
    Commented Mar 16, 2015 at 17:26
  • Glad to help! Didn't realize the redirect handler on the delegate was still called—I've updated the answer.
    – Nate Cook
    Commented Mar 16, 2015 at 17:37
6

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)
1

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")

Not the answer you're looking for? Browse other questions tagged or ask your own question.