JavaFX WebEngine等待ajax完成

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

这适用于大多数情况,但最近我遇到了一些使用AJAX呈现内容的页面的麻烦。 要检查WebEngine是否已加载文档,我会听取loadWorkerstateProperty 。 如果状态转换为succesfull,我知道文档已加载(与可能在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调用并在事后通知我的方法。

说明

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

  1. 我知道什么时候收到了最后一个字节,因此完全处理了AJAX请求。
  2. 我甚至可以获取所有传入的数据并且已经使用它(如果我想)。

首先, 您必须告诉Java使用URLConnection实现而不是默认实现 。 为此,您必须为其提供自己的URLStreamHandlerFactory版本。 你可以在这里找到很多线索(例如这一个 )或谷歌就这个话题。 要设置工厂实例,请在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 (eg 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()函数并放置另一个中间流来抓取正在发送的内容,然后将其传递给原始输出流。

这是@dadoosh答案的延伸……

为https执行此操作是委派的噩梦,因为HttpsURLConnectionImpl )不能像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; }