JavaFX WebEngine等待ajax完成

时间:2014-01-25 17:35:13

标签: jquery ajax javafx javafx-webengine

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

这适用于大多数情况,但最近我遇到了使用AJAX呈现内容的页面遇到的麻烦。要检查WebEngine是否已加载文档,我会收听loadWorker的{​​{1}}。如果状态转换为successcesfull,我知道文档已加载(与可能在document.ready()上运行的任何javascript或等效文件一起)。这是因为如果我没弄错的话,javascript会在JavaFX线程上执行(来源:https://blogs.oracle.com/javafx/entry/communicating_between_javascript_and_javafx)。但是,如果启动了AJAX调用,则javascript执行完成,引擎会让我知道文档已准备就绪,但显然不是因为优秀的AJAX调用,内容可能仍然会发生变化。

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

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

2 个答案:

答案 0 :(得分:5)

解释

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

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

    实施例

    首先,您必须告诉Java使用您的URLConnection实现而不是默认实现。为此,您必须提供自己的URLStreamHandlerFactory版本。你可以在SO上找到很多线索(例如this one),或者通过谷歌在这个主题上找到。要设置工厂实例,请在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的方法是相应的网址包含ajax=1。就我而言,这已经足够了。但是,由于我对html和http不太好,我不知道WebEngine是否可以以任何不同的方式发出AJAX请求(例如标题字段?)。如果有疑问,您可以简单地返回我们修改的url连接的实例,但这当然意味着一些开销。
    • 如开头所述,如果您希望这样做,您可以在从输入流中检索数据后立即处理数据。您可以抓取WebEngine以类似方式发送的请求数据。只需包装getOutputStream()函数并放置另一个中间流来抓取正在发送的内容,然后将其传递给原始输出流。

答案 1 :(得分:0)

这是@dadoosh答案的延伸......

为https执行此操作是一个代表团的噩梦,因为HttpsURLConnectionImpl)不能像[{1}}

那样实例化
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方法。

BTW我找到了另一种检测ajax请求结束的解决方案。我只是等待调用getInputStream()方法:

close()
相关问题