Explanation
I've also had this problem and solved it by providing my own implementation of sun.net.www.protocol.http.HttpURLConnection
which I use to process any AJAX requests. My class, conveniently called AjaxHttpURLConnection
, hooks into the getInputStream()
function, but does not return its original input stream. Instead, I give an instance of PipedInputStream
back to the WebEngine
. I then read all the data coming from the original input stream and pass it on to my piped stream.
This way, I gain 2 benefits:
- I know when the last byte has been received and thus the AJAX request has been processed completely.
- I even can grab all the incoming data and already work with it (if I wanted to).
Example
First, you will have to tell Java to use your URLConnection implementation instead of the default one. To do so, you must provide it with your own version of the URLStreamHandlerFactory
. You can find many threads here on SO (e.g. this one) or via Google on this topic. In order to set your factory instance, put the following somewhere early in your main
method. This is what mine looks like.
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);
}
}
Second, we have to come up with our own Handler
that tells the programme when to use which type of 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);
}
}
Last but not least, here comes the 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;
}
}
Further Considerations
- If you are using multiple
WebEngine
objects, it might be tricky to tell which one actually opened the URLConnection
and thus which browser has finished loading.
- You might have noticed that I only dealth with http connections. I have not tested to what extent my approach could be transferred to https etc. (not an expert here :O).
- As you have seen, my only means to know when to actually use my
AjaxHttpURLConnection
is when the corresponding url contains ajax=1
. In my case, this was sufficient. Since I am not too good with html and http, however, I don't know if the WebEngine
can make AJAX requests in any different way (e.g. the header fields?). If in doubt, you could simply always return an instance of our modified url connection, but that would of course mean some overhead.
- As stated in the beginning, you can immediately work with the data once it has been retrieved from the input stream if you wish to do so. You can grab the request data that your
WebEngine
sends in a similar way. Just wrap the getOutputStream()
function and place another intermediate stream to grab whatever is being sent and then pass it on to the original output stream.