Erklärung
Ich hatte auch dieses Problem und löste es, indem ich meine eigene Implementierung von sun.net.www.protocol.http.HttpURLConnection
bereitstellte, die ich zur Verarbeitung von AJAX-Anfragen verwende. Meine Klasse, praktischerweise AjaxHttpURLConnection
genannt, klinkt sich in die Funktion getInputStream()
ein, gibt jedoch nicht ihren originalen Eingabestrom zurück. Stattdessen gebe ich eine Instanz von PipedInputStream
an die WebEngine
zurück. Dann lese ich alle Daten aus dem originalen Eingabestrom und leite sie an meinen Piped-Stream weiter. Auf diese Weise erhalte ich 2 Vorteile:
- Ich weiß, wann das letzte Byte empfangen wurde und somit die AJAX-Anfrage vollständig verarbeitet wurde.
- Ich kann sogar alle eingehenden Daten abfangen und bereits damit arbeiten (wenn ich wollte).
Beispiel
Zunächst müssen Sie Java mitteilen, dass es Ihre URL-Verbindungsimplementierung verwenden soll, anstelle der Standardimplementierung. Hierzu müssen Sie ihm Ihre eigene Version von URLStreamHandlerFactory
bereitstellen. Sie können viele Threads hier auf SO (zum Beispiel dieser) oder über Google zu diesem Thema finden. Um Ihre Factory-Instanz festzulegen, platzieren Sie folgendes frühzeitig in Ihrer main
-Methode. So sieht meine aus.
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
public class MeineAnwendung extends Anwendung {
// ...
public static void main(String[] args) {
URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
public URLStreamHandler createURLStreamHandler(String protocol) {
if ("http".equals(protocol)) {
return new MyUrlConnectionHandler();
}
return null; // Lassen Sie die Standardhandler damit umgehen, was auch immer hier kommt (z. B. https, jar, ...)
}
});
launch(args);
}
}
Zweitens müssen wir einen eigenen Handler
erstellen, der dem Programm mitteilt, wann welche Art von URLConnection
verwendet werden soll.
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);
}
// Gibt eine Standard-HttpURLConnection-Instanz zurück.
return new HttpURLConnection(url, proxy);
}
}
Zu guter Letzt, hier kommt die 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 {
// Müssen wir unseren eigenen Eingabestream einrichten?
if (pipedIn == null) {
PipedOutputStream pipedOut = new PipedOutputStream();
pipedIn = new PipedInputStream(pipedOut);
InputStream in = super.getInputStream();
/*
* Vorsicht hier! Aus irgendeinem Grund ruft die getInputStream-Methode sich selbst auf (keine Ahnung warum). Daher, wenn wir pipedIn nicht vor dem Aufruf von super.getInputStream() gesetzt haben, geraten wir in eine Schleife oder in EOFExceptions!
*/
// TODO: Timeout?
new Thread(new Runnable() {
public void run() {
try {
// Leiten Sie die originalen Daten an den Browser weiter.
byte[] data = IOUtils.toByteArray(in);
pipedOut.write(data);
pipedOut.flush();
pipedOut.close();
// Etwas mit den Daten machen? Zum Beispiel, sie entschlüsseln, wenn sie gzipped waren.
// Signalisieren, dass der Browser fertig ist.
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
} finally {
lock.unlock();
}
return pipedIn;
}
}
Weitere Überlegungen
- Wenn Sie mehrere
WebEngine
-Objekte verwenden, könnte es schwierig sein zu erkennen, welches tatsächlich die URLConnection
geöffnet hat und somit welcher Browser fertig geladen hat.
- Sie haben vielleicht bemerkt, dass ich mich nur mit http-Verbindungen befasst habe. Ich habe nicht getestet, inwieweit mein Ansatz auf https usw. übertragen werden könnte (bin kein Experte hier :O).
- Wie Sie gesehen haben, ist mein einziger Anhaltspunkt, wann ich tatsächlich meinen
AjaxHttpURLConnection
verwenden soll, wenn die entsprechende URL ajax=1
enthält. In meinem Fall war das ausreichend. Da ich jedoch nicht so gut mit html und http umgehen kann, weiß ich nicht, ob die WebEngine
AJAX-Anfragen auf eine andere Weise stellen kann (z. B. die Headerfelder?). Wenn Sie unsicher sind, könnten Sie einfach immer eine Instanz unserer modifizierten URL-Verbindung zurückgeben, aber das würde natürlich einige Overhead bedeuten.
- Wie am Anfang erwähnt, können Sie sofort mit den Daten arbeiten, sobald sie aus dem Eingabestream abgerufen wurden, wenn Sie dies wünschen. Sie können die Anfragedaten, die Ihre
WebEngine
sendet, auf ähnliche Weise abfangen. Wickeln Sie einfach die Funktion getOutputStream()
ein und platzieren Sie einen weiteren Zwischenstrom, um zu erfassen, was gesendet wird, und leiten Sie es dann an den ursprünglichen Ausgabestrom weiter.