2

问题

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

我试过什么

我试过使用NSURLConnection sendSynchronousRequest,不幸的是不能轻易被告知不要遵循重定向。

背景

在告诉我不应该使用同步请求之前,请记住,这段代码是为了模拟 Java 的HttpUrlConnection,它在行为上本质上是同步的,用于j2objc项目。IosHttpUrlConnections' nativemakeSynchronousRequest当前的实现总是遵循重定向。它应该尊重HttpUrlConnection.instanceFollowRedirects领域

进行了进一步的研究

  • 在异步模式下使用 NSUrlConnection 时,会调用一个委托方法,该方法允许启用/禁用重定向。但是,我需要同步操作。
  • NSUrlconnection 上的这个答案:如何等待完成显示了如何sendSynchronousRequest使用异步请求来实现。但是,我无法修改它以使用委托,因此无法不遵循重定向。

我希望你能帮帮我

4

2 回答 2

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
于 2015-01-15T12:47:43.400 回答
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)就足够了,并且文档支持这一点。我发现这仅在您有ephemeralURLSessionConfiguration 时才足够。如果您有默认配置,则不会调用数据任务的完成闭包,因此信号量永远不会收到信号,因此代码永远不会向前移动。这就是导致评论者/提问者出现冻结 UI 问题的原因。

于 2017-07-12T02:21:09.170 回答