【问题标题】:JavaFX WebEngine wait for ajax to completeJavaFX WebEngine 等待 ajax 完成
【发布时间】:2014-02-16 16:44:33
【问题描述】:

我正在使用 JavaFX 开发一个数据挖掘应用程序,它依赖于 WebView(因此也依赖于 WebEngine)。挖掘分两步进行:首先,用户使用 UI 导航到 WebView 中的网站,以配置可以搜索感兴趣数据的位置。其次,使用定期运行的后台任务,WebEngine 加载相同的文档并尝试从加载的文档中提取数据。

这适用于大多数情况,但最近我遇到了一些使用 AJAX 呈现内容的页面的问题。为了检查 WebEngine 是否加载了文档,我听了loadWorkerstateProperty。如果状态转换为成功,我知道文档已加载(连同可能在 document.ready() 或等效项上运行的任何 javascript)。这是因为如果我没记错的话,javascript 是在 JavaFX 线程上执行的(来源:https://blogs.oracle.com/javafx/entry/communicating_between_javascript_and_javafx)。但是,如果启动 AJAX 调用,则 javascript 执行完成,并且引擎让我知道文档已准备就绪,尽管它显然还没有准备好,因为由于未完成的 AJAX 调用,内容可能仍会发生变化。

有没有办法解决这个问题,注入一个钩子,以便在 AJAX 调用完成时通知我?我已经尝试在$.ajaxSetup() 中安装一个默认的完整处理程序,但这很狡猾,因为如果 ajax 调用覆盖了完整的处理程序,则不会调用默认值。另外,我只能在第一次加载文档后注入它(到那时一些 AJAX 调用可能已经在运行)。我已经使用 upcall 测试了这种注入,它适用于在不提供自己的完整处理程序的命令上(在注入默认处理程序之后)启动的 AJAX 调用。

我正在寻找两件事:首先:一种挂接到 AJAX 调用的完成处理程序的通用方法,其次:一种等待 WebEngine 完成所有 AJAX 调用并随后通知我的方法。

【问题讨论】:

  • 我也面临同样的问题。你找到解决方案了吗?
  • @wib:不幸的是,我尝试了一些技巧,但问题在于构建 Web 引擎的库更深层次。最适合我的 hack 只是播放暂停转换以使 javafx 线程“休眠”一段时间,并希望 js 到那时完成......
  • 我想过这样做,但它在很大程度上依赖于可靠的互联网连接。看起来这是目前最好的解决方案

标签: jquery ajax javafx javafx-webengine


【解决方案1】:

说明

我也遇到了这个问题,并通过提供我自己的sun.net.www.protocol.http.HttpURLConnection 实现来解决它,我用它来处理任何 AJAX 请求。我的类,方便地称为AjaxHttpURLConnection,挂钩到getInputStream() 函数,但不返回其原始输入流。相反,我将PipedInputStream 的实例返回给WebEngine。然后我读取来自原始输入流的所有数据并将其传递给我的管道流。 这样,我获得了 2 个好处:

  1. 我知道何时收到最后一个字节,因此 AJAX 请求已被完全处理。
  2. 我什至可以抓取所有传入的数据并已经使用它(如果我愿意的话)。


示例

首先,你必须告诉 Java 使用你的 URLConnection 实现而不是默认的。为此,您必须为其提供您自己的URLStreamHandlerFactory 版本。您可以在 SO(例如 this one)或通过 Google 找到有关此主题的许多线程。为了设置您的工厂实例,请将以下内容放在您的 main 方法的早期位置。这就是我的样子。

import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;

public class MyApplication extends Application {

    // ...

    public static void main(String[] args) {
        URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
            public URLStreamHandler createURLStreamHandler(String protocol) {
                if ("http".equals(protocol)) {
                    return new MyUrlConnectionHandler();    
                }
                return null; // Let the default handlers deal with whatever comes here (e.g. https, jar, ...)
            }
        });
        launch(args);
    }
}

其次,我们必须提出自己的Handler,它告诉程序何时使用哪种类型的URLConnection

import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;

import sun.net.www.protocol.http.Handler;
import sun.net.www.protocol.http.HttpURLConnection;

public class MyUrlConnectionHandler extends Handler {

    @Override
    protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {

        if (url.toString().contains("ajax=1")) {
            return new AjaxHttpURLConnection(url, proxy, this);
        }

        // Return a default HttpURLConnection instance.
        return new HttpURLConnection(url, proxy);
    }
}

最后但同样重要的是,AjaxHttpURLConnection 来了。

import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.Proxy;
import java.net.URL;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.io.IOUtils;

import sun.net.www.protocol.http.Handler;
import sun.net.www.protocol.http.HttpURLConnection;

public class AjaxHttpURLConnection extends HttpURLConnection {

    private PipedInputStream pipedIn;
    private ReentrantLock lock;

    protected AjaxHttpURLConnection(URL url, Proxy proxy, Handler handler) {
        super(url, proxy, handler);
        this.pipedIn = null;
        this.lock = new ReentrantLock(true);
    }

    @Override
    public InputStream getInputStream() throws IOException {

        lock.lock();
        try {

            // Do we have to set up our own input stream?
            if (pipedIn == null) {

                PipedOutputStream pipedOut = new PipedOutputStream();
                pipedIn = new PipedInputStream(pipedOut);

                InputStream in = super.getInputStream();
                /*
                 * Careful here! for some reason, the getInputStream method seems
                 * to be calling itself (no idea why). Therefore, if we haven't set
                 * pipedIn before calling super.getInputStream(), we will run into
                 * a loop or into EOFExceptions!
                 */

                // TODO: timeout?
                new Thread(new Runnable() {
                    public void run() {
                        try {

                            // Pass the original data on to the browser.
                            byte[] data = IOUtils.toByteArray(in);
                            pipedOut.write(data);
                            pipedOut.flush();
                            pipedOut.close();

                            // Do something with the data? Decompress it if it was
                            // gzipped, for example.

                            // Signal that the browser has finished.

                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        } finally {
            lock.unlock();
        }
        return pipedIn;
    }
}


进一步考虑

  • 如果您使用多个 WebEngine 对象,可能很难判断哪个对象实际打开了 URLConnection 以及哪个浏览器已完成加载。
  • 您可能已经注意到我只处理 http 连接。我还没有测试我的方法可以在多大程度上转移到 https 等(这里不是专家:O)。
  • 如您所见,我唯一知道何时真正使用我的AjaxHttpURLConnection 的方法是相应的url 包含ajax=1。就我而言,这就足够了。但是,由于我不太擅长 html 和 http,所以我不知道 WebEngine 是否可以以任何不同的方式发出 AJAX 请求(例如,标头字段?)。如果有疑问,您总是可以简单地返回我们修改后的 url 连接的实例,但这当然意味着一些开销。
  • 如开头所述,如果您愿意,可以在从输入流中检索到数据后立即处理数据。您可以获取您的WebEngine 以类似方式发送的请求数据。只需包装 getOutputStream() 函数并放置另一个中间流来抓取正在发送的任何内容,然后将其传递给原始输出流。

【讨论】:

    【解决方案2】:

    这是@dadoosh 答案的延伸……

    为 https 执行此操作是委托的噩梦,因为 HttpsURLConnection(Impl) 不能像 HttpURLConnection 那样被实例化

    import sun.net.www.protocol.https.Handler;
    
    public class MyStreamHandler extends Handler {
    
        @Override
        protected URLConnection openConnection(URL url) throws IOException {
            URLConnection connection = super.openConnection(url);
            if (url.toString().contains("ajax=1")) {
                return new MyConnection((HttpsURLConnection) connection);
            } else {
                return connection;
            }
        }
    }
    

    所以我得到了本应返回的连接,如有必要,将其提供给MyConnection,以便它可以委托所有调用并修改getInputStream() 方法。

    顺便说一句,我找到了另一种检测 ajax 请求结束的解决方案。我只是等待调用close() 方法:

    @Override
    public synchronized InputStream getInputStream() throws IOException {
        if (cachedInputStream != null) {
            return cachedInputStream;
        }
    
        System.out.println("Open " + getURL());
        InputStream inputStream = delegate.getInputStream();
    
        cachedInputStream = new FilterInputStream(inputStream) {
            @Override
            public void close() throws IOException {
                super.close();
                // Signal that the browser has finished.
            }
        };
    
        return cachedInputStream;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-20
      • 1970-01-01
      • 2013-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-27
      相关资源
      最近更新 更多