2014-01-25 94 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; 
}