我正在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调用并在事后通知我的方法。
答案 0 :(得分:5)
我也有这个问题并通过提供我自己的sun.net.www.protocol.http.HttpURLConnection
实现来解决它,我用它来处理任何AJAX请求。我的类,方便地称为AjaxHttpURLConnection
,挂钩到getInputStream()
函数,但不返回其原始输入流。相反,我将PipedInputStream
的实例返回WebEngine
。然后,我读取来自原始输入流的所有数据,并将其传递给我的管道流。
这样,我获得了2个好处:
<小时/>
首先,您必须告诉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
以及哪个浏览器已完成加载可能会很棘手。AjaxHttpURLConnection
的方法是相应的网址包含ajax=1
。就我而言,这已经足够了。但是,由于我对html和http不太好,我不知道WebEngine
是否可以以任何不同的方式发出AJAX请求(例如标题字段?)。如果有疑问,您可以简单地返回我们修改的url连接的实例,但这当然意味着一些开销。WebEngine
以类似方式发送的请求数据。只需包装getOutputStream()
函数并放置另一个中间流来抓取正在发送的内容,然后将其传递给原始输出流。答案 1 :(得分:0)
这是@dadoosh答案的延伸......
为https执行此操作是一个代表团的噩梦,因为HttpsURLConnection
(Impl
)不能像[{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()