2014-01-25 2 views
5

WebView (및 WebEngine)를 사용하는 JavaFX에서 데이터 마이닝 응용 프로그램을 개발 중입니다. 마이닝은 2 단계로 진행됩니다. 먼저 사용자가 UI를 사용하여 WebView의 웹 사이트로 이동하여 흥미로운 데이터를 검색 할 수있는 위치를 구성합니다. 둘째, 주기적으로 실행되는 백그라운드 작업을 사용하여 WebEngine은 동일한 문서를로드하고로드 된 문서에서 데이터 추출을 시도합니다.JavaFX WebEngine이 아약스가 완료 될 때까지 기다림

이것은 대부분의 경우에 완벽하게 작동하지만 최근에는 AJAX를 사용하여 콘텐츠를 렌더링하는 페이지에 약간의 문제가있었습니다. WebEngine이 문서를로드했는지 확인하기 위해 loadWorkerstateProperty을 듣습니다. 상태가 succesfull로 전환되면 document.ready() 또는 이에 상응하는 항목에서 실행될 수있는 javascript와 함께 문서가로드 된 것입니다. JavaFX 스레드에서 오류가 발생하지 않으면 javascript가 실행되기 때문에 (출처 : https://blogs.oracle.com/javafx/entry/communicating_between_javascript_and_javafx). 그러나 AJAX 호출이 시작되면 자바 스크립트 실행이 끝나고 엔진에서 문서 준비 상태를 알 수 있습니다.하지만 AJAX 호출로 인해 콘텐츠가 계속 변경 될 수는 없습니다.

AJAX 호출이 끝나면 알림을받을 수 있도록 후크를 삽입 할 수있는 방법이 있습니까? $.ajaxSetup()에 기본 전체 처리기를 설치하려고 시도했지만 아약스 호출이 전체 처리기를 재정의하면 기본값이 호출되지 않기 때문에 상당히 위험합니다. 또한 문서를 처음로드 한 후에 만 ​​주입 할 수 있으며 일부 AJAX 호출이 이미 실행 중일 수 있습니다. 나는이 호출을 상향 호출로 테스트했으며 자체 처리기를 제공하지 않는 명령 (기본 처리기 주입 후)에서 실행되는 AJAX 호출에 대해서는 정상적으로 작동합니다.

저는 AJAX 호출의 완료 핸들러에 연결하는 일반적인 방법과 둘째로 WebEngine이 모든 AJAX 호출을 완료하고 나중에 알릴 때까지 기다리는 두 가지 방법을 찾고 있습니다.

+0

동일한 문제가 발생합니다. 솔루션을 찾을 수 있었습니까? – wib

+0

@wib : 불행히도, 나는 해킹을 시도했지만 문제는 웹 엔진이 구축 된 라이브러리에 더 깊게 자리 잡고 있습니다. 나를 위해 가장 잘 작동하는 해킹은 javafx 스레드를 "잠자기"로 잠시 멈추게하고 잠시 js가 완료 될 때까지 기다리는 것이 었습니다 ... – Warkst

+0

나는 그 일을 생각했지만 신뢰할 수있는 인터넷 연결에 크게 의존합니다. . 그게 지금 제일 좋은 해결책 인 것 같아. – wib

답변

4

설명

나는이 문제를했고 내가 어떤 AJAX 요청을 처리하는 데 사용 sun.net.www.protocol.http.HttpURLConnection 내 자신의 구현을 제공하여 해결했습니다. 편리하게 AjaxHttpURLConnection이라고 불리는 내 클래스는 getInputStream() 함수에 후크하지만 원래 입력 스트림을 반환하지 않습니다. 대신 PipedInputStream의 인스턴스를 WebEngine에 반환합니다. 그런 다음 원래 입력 스트림에서 오는 모든 데이터를 읽고이를 파이프 된 스트림으로 전달합니다. 이 방법은, 내가 2 개 혜택을 얻을 :

마지막 바이트를받은 따라서 AJAX 요청이 완전히 처리되었을 때 내가 아는
  1. .
  2. 들어오는 모든 데이터를 가져 와서 이미 작업하고 있습니다 (원하는 경우).


    먼저

, 당신은 기본 일 대신 URLConnection의 구현를 사용하는 자바 말할해야합니다. 그렇게하려면 자신의 버전 인 URLStreamHandlerFactory을 제공해야합니다. 이 주제에 관해서는 SO (예 : 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); 
    } 
} 

둘째, 우리는 URLConnection의 유형을 사용할 수있는 프로그램을 알려줍니다 그 Handler 우리 자신을 마련한다.

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() 함수를 랩핑하고 다른 중간 스트림을 보내서 무엇이든지 보내고 원래의 출력 스트림으로 전달하십시오. HTTPS에 대해 이렇게이 @의 dadoosh의 대답의 확장
0

...

은 그래서 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; 
     } 
    } 
} 

처럼 인스턴스화 할 수없는 HttpsURLConnection (Impl) 이후 대표단의 악몽 I 반환 된 연결을 얻고 필요한 경우 MyConnection에 전달하면 모든 호출을 위임하고 getInputStream() 메서드를 수정할 수 있습니다.

BTW 아약스 요청의 끝을 감지하는 또 다른 해결책을 찾았습니다. 그냥 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; 
}