NSUrlConnection同步请求,不遵循重定向

时间:2015-01-15 10:13:25

标签: ios nsurlconnection httpurlconnection nsurlconnectiondelegate j2objc

问题

我需要执行同步HTTP请求,而不是遵循重定向,最好不使用实例变量,因为这将被合并到j2objc项目中。

我尝试了什么

我尝试过使用NSURLConnection sendSynchronousRequest,遗憾的是不能轻易告诉他们不要关注重定向。

背景

在告诉我不应该使用同步请求之前,请记住,此代码用于模拟HttpUrlConnection项目的Java j2objc,这在行为上本质上是同步的。 IosHttpUrlConnections' native makeSynchronousRequest的实施目前始终遵循重定向。它应该尊重HttpUrlConnection.instanceFollowRedirects field

进行了进一步的研究

  • 在异步模式下使用NSUrlConnection时,会调用委托方法,该方法允许启用/禁用重定向。但是,我需要同步操作。
  • This answer on NSUrlconnection: How to wait for completion显示了如何使用异步请求实现sendSynchronousRequest。但是,我无法修改它以使用委托,因此无法遵循重定向。

我希望你能帮助我

2 个答案:

答案 0 :(得分:1)

您可以将NSURLSession与信号量一起使用,创建如下:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
NSURLSessionTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

    if (data)
    {
        // do whatever you want with the data here
    }
    else
    {
        NSLog(@"error = %@", error);
    }

    dispatch_semaphore_signal(semaphore);
}];
[task resume];

// but have the thread wait until the task is done

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

您必须实现NSURLSessionTaskDelegate的以下方法,并调用completionHandler块传递null以停止重定向。

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
        newRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLRequest *))completionHandler

答案 1 :(得分:1)

我想我会离开他们离开的地方,但是在Swift中这么多年以后。

class ViewController: UIViewController {

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let semaphore = DispatchSemaphore(value: 0)
        let configuration = URLSessionConfiguration.ephemeral
        configuration.timeoutIntervalForRequest = 10
        let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)

        // Redirects to google.com
        guard let url = URL(string: "https://bit(dot)ly/19BiSHW") else {
            return
        }

        var data: Data?
        var response: URLResponse?
        var error: Error?
        let task = session.dataTask(with: url) { (innerData, innerResponse, innerError) in
            // For clarity, we'll call this the data task's completion closure

            // Pass the data back to the calling scope
            data = innerData
            response = innerResponse
            error = innerError

            semaphore.signal()
        }

        task.resume()

        if semaphore.wait(timeout: .now() + .seconds(15)) == .timedOut {
            // The task's completion closure wasn't called within the time out, handle appropriately
        } else {
            if let e = error as NSError? {
                if e.domain == NSURLErrorDomain && e.code == NSURLErrorTimedOut {
                    print("The request timed out")
                }

                return
            }

            if let d = data {
                // do whatever you want with the data here, such as print it
                // (the data is the HTTP response body)
                print(String.init(data: d, encoding: .utf8) ?? "Response data could not be turned into a string")

                return
            }

            if let r = response {
                print("No data and no error, but we received a response, we'll inspect the headers")

                if let httpResponse = r as? HTTPURLResponse {
                    print(httpResponse.allHeaderFields)
                }
            }
        }
    }
}

extension ViewController: URLSessionTaskDelegate {
    func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Swift.Void) {

        // Inside the delegate method, we will call the delegate's completion handler

        // completionHandler: A block that your handler should call with
        // either the value of the request parameter, a modified URL
        // request object, or NULL to refuse the redirect and return
        // the body of the redirect response.

        // I found that calling the block with nil only triggers the
        // return of the body of the redirect response if the session is ephemeral

        // Calling this will trigger the data task's completion closure
        // which signals the semaphore and allows execution to continue
        completionHandler(nil)
    }
}

代码正在做什么:

它正在创建一个固有的异步任务(URLSessionTask),通过调用resume()告诉它执行,然后通过等待DispatchSemaphore来暂停当前的执行上下文。这是我已经习惯使用的技巧,并且在很多场合亲自使用以使异步行为以同步方式运行。

要点是代码在当前上下文中停止执行。在这个例子中,该上下文是主线程(因为它在UIViewController方法中),这通常是不好的做法。因此,如果您的同步代码永远不会继续执行(因为信号量永远不会发出信号),那么您的UI线程将永远停止,从而导致UI被冻结。

最后一部分是委托方法的实现。评论表明,调用completionHandler(nil)应该足够了,文档支持这一点。我发现只有ephemeral URLSessionConfiguration才足够。如果您具有默认配置,则不会调用数据任务的完成闭包,因此信号量永远不会发出信号,因此代码永远不会向前移动。这就是导致评论者/提问者冻结用户界面问题的原因。