Swift / iOS:发出 HTTP GET 请求而不遵循重定向

Swift / iOS: issue HTTP GET request without following redirects

我希望我的 Swift iOS 应用程序获取它通过 NSUserActivityTypeBrowsingWeb 请求接收的 URL。

我的电子邮件service-provider“包装”了链接(就像大多数链接一样),返回“302 重定向”消息。我想执行初始 GET,以获取“302 Found”和“Location”header,但我不需要 iOS 到 follow 的重定向我.

我添加了以下代码:

// Follow the link to trigger click tracking
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
    guard let data = data else { return }
    print(String(data: data, encoding: .utf8)!)
}
task.resume()

这有效,但我不需要遵循重定向并获取所有数据(因为它可能是一个非常大的网页)。

iOS 是否提供了一种无需遵循重定向即可获取初始 HTTP(S) 响应的方法? (有点像省略 CURL 上的 --location 标志)。

您应该使用 URLSessionTaskDelegate 扩展实现您的自定义会话,然后您可以在 willPerformHTTPRedirection 方法中中断重定向:

class Redirect : NSObject {
    var session: URLSession?
    
    override init() {
        super.init()
        session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
    }
    
    func makeRequest() {
        let url = URL(string: "http://gmail.com")!
        let task = session?.dataTask(with: url) {(data, response, error) in
            guard let data = data else {
                return
            }
            print(String(data: data, encoding: .utf8)!)
        }
        task?.resume()
    }
}

extension Redirect: URLSessionDelegate, URLSessionTaskDelegate {
    func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
        // Stops the redirection, and returns (internally) the response body.
        completionHandler(nil)
    }
}

let r = Redirect()
r.makeRequest()

输出:

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="https://www.google.com/gmail/">here</A>.
</BODY></HTML>

多亏了iUrii and kleids我才开始工作,有几点需要注意:

  • 需要回调才能从异步任务中获取数据
  • 只能从主队列更新 UILabels (!!) 所以需要 DispatchQueue.main.async {}
//
//  AppDelegate.swift
//  testlinks
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window: UIWindow?
    
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }
    
    func applicationWillResignActive(_ application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
    }
    
    func applicationDidEnterBackground(_ application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }
    
    func applicationWillEnterForeground(_ application: UIApplication) {
        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
    }
    
    func applicationDidBecomeActive(_ application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }
    
    func applicationWillTerminate(_ application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }
    
    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
        // First attempt at handling a universal link
        print("Continue User Activity called: ")
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
           let url = userActivity.webpageURL {
            //handle URL
            let r = Redirect()
            r.makeRequest(url: url, callback: { (location) in
                
                guard let locationURL = location else {return}
                
                print("locationURL", locationURL)
                
                // Show this on our simple example app
                DispatchQueue.main.async {
                    if let vc = self.window?.rootViewController as? ViewController {
                        vc.result.text = url.absoluteString
                        vc.originalURL.text = locationURL.absoluteString
                    }
                    
                }
            })
        }
        return true
    }
}

// More efficient click-tracking with HTTP GET to obtain the "302" response, but not follow the redirect through to the Location.
// The callback is used to return the Location header back from the async task = thanks @kleids
class Redirect : NSObject {
    var session: URLSession?
    
    override init() {
        super.init()
        session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
    }
    
    func makeRequest(url: URL, callback: @escaping (URL?) -> ()) {
        let task = self.session?.dataTask(with: url) {(data, response, error) in
            guard response != nil else {
                return
            }
            if let response = response as? HTTPURLResponse {
                if let l = response.value(forHTTPHeaderField: "Location") {
                    callback(URL(string: l))
                }
            }
        }
        task?.resume()
    }
}

extension Redirect: URLSessionDelegate, URLSessionTaskDelegate {
    func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
        // Stops the redirection, and returns (internally) the response body.
        completionHandler(nil)
    }
}