2014-01-25 93 views
5

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

这对大多数情况下完美的作品,但最近我遇到了使用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调用并在事后通知我的方法。

+0

我面临同样的问题。你能找到解决方案吗? – wib

+0

@wib:不幸的是,我尝试了一些黑客,但是问题在于Web引擎构建在库中的更深层次。最适合我的黑客只是暂停转换,让javafx线程“睡眠”一段时间,并希望js在那时完成... – Warkst

+0

我想过这样做,但它严重依赖于可靠的Internet连接。看起来这是目前最好的解决方案 – wib

回答

4

说明

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

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


首先,你将不得不告诉Java使用而不是默认的你的URLConnection实现。为此,您必须提供您自己的URLStreamHandlerFactory版本。您可以在此处找到许多线索(例如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()函数,并放置另一个中间流来抓取正在发送的任何内容,然后将其传递到原始输出流。
0

这是@ 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; 
}