5

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

这适用于大多数情况,但最近我在使用 AJAX 呈现内容的页面上遇到了一些问题。要检查 WebEngine 是否已加载文档,我会听loadWorker's stateProperty. 如果状态转换为成功,我知道文档已加载(连同可能在 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 调用并在之后通知我的方法。

4

2 回答 2

5

解释

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

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


例子

首先,您必须告诉 Java 使用您的 URLConnection 实现而不是默认实现。为此,您必须为其提供您自己的URLStreamHandlerFactory. 您可以在 SO(例如这个)或通过 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)。
  • 如您所见,我知道何时实际使用 my 的唯一方法AjaxHttpURLConnection是相应的 url 何时包含ajax=1. 就我而言,这就足够了。但是,由于我不太擅长 html 和 http,因此我不知道它们是否WebEngine可以以任何不同的方式发出 AJAX 请求(例如,标头字段?)。如果有疑问,您总是可以简单地返回我们修改后的 url 连接的实例,但这当然意味着一些开销。
  • 如开头所述,如果您愿意,可以在从输入流中检索到数据后立即处理数据。WebEngine您可以以类似的方式获取您发送的请求数据。只需包装getOutputStream()函数并放置另一个中间流来抓取正在发送的任何内容,然后将其传递给原始输出流。
于 2015-10-21T13:36:59.270 回答
0

这是@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;
}
于 2017-06-12T07:37:13.877 回答