Networking specifics under Java Web Start
This writing will help you overcome several annoyances of Java Web Start in respect to networking.
During my development I hit two major problems doing polite networking when running application under Java Web Start: I couldn’t set correct HTTP “User-Agent” string and I couldn’t make application obey connection and reading timeout settings. According to the SUN’s bug reports database these problems are pretty common and that’s why I wished to share my experience with the rest of community, knowing how important to have networking layer working correctly.
HTTP User-Agent
At the time I was busy with development of networking component for BlogBridge I hit one very subtle problem. Our application should fetch remote resource through HTTP. Of course it should pass “User-Agent” string to name itself correctly. There’s standard networking run-time property which is questioned every time the connection is created and used to initialize correct field in HTTP header (also you may wish to see other properties available for you).
The property:
http.agent
Example initialization as VM runtime property:
java -Dhttp.agent=My\ Cool\ Application ... main.MyApplication
and from within the code:
System.setProperty("http.agent", "My Cool Application");
Side note: it’s better always make agent string look like this “Application_name version (product_URL)”. For example, “BlogBridge 1.12 (www.blogbridge.com)”.
As you could see, setting of “User-Agent” could be very simple if there were no any “but”. Unfortunately, it works only when you run your application from console or debugger. When it comes to desktop deployment you need assistance from Java Web Start and here’s where the problems begin. Java Web Start does nothing really outlaw, it simply ignores your setting. Yes, whatever you put in this well-known property Java Web Start uses its own value. Fortunately, there’s still the way out of this stupid situation.
In our application we have good isolation of modules. In particular, our networking code was grouped in the single class and all connections to the outer world were done through it. All I had to do is to insert one line of code to fix our first annoyance. Here’s how it looks (I removed all error handling for greater readability):
public InputStream getRemoteStream(URL link)
{
if (link == null) return null;
URLConnection con = link.openConnection();
if (con instanceof HttpURLConnection)
{
String agent = System.getProperty("http.agent");
if (agent != null)
{
((HttpURLConnection).setRequestProperty("User-Agent", agent);
}
}
}
This code allows you to have you “User-Agent” setting back. If you have no your personalized value then it will save the value set by Java Web Start. Connection and Reading Timeouts
Another interesting challenge is to make application running under Java Web Start respect the connection and reading timeouts. Again, when application runs in any way except from Java Web Start it correctly handles timeouts. Under Java Web Start it completely forgets them.
Some digging in the non-public part of API showed that there’s a class sun.net.NetworkClient
, which is a parent of all clients for all protocols (including our beloved HTTP). This class is public and has two protected properties defaultSoTimeout
(reading timeout) and defaultConnectTimeout
(connection timeout). I looked carefully into the decompiled code and found why the properties were ignored. Reading of these properties looks as follows:
Integer readingTimeout = (Integer)AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return Integer.getInteger("sun.net.client.defaultReadTimeout");
}});
defaultSoTimeout = (readingTimeout == null) ? -1 : readingTimeout.intValue();
Integer connectTimeout= (Integer)AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return Integer.getInteger("sun.net.client.defaultConnectTimeout");
}});
defaultConnectTimeout = (connectTimeout == null) ? -1 : connectTimeout.intValue();
It means that in order to read these properties the application needs some privileges. No wonder that it has no these privileges when running in highly restricted sandbox of Java Web Start’s. So what can we do then? It’s easy, though not very “honest”. I mean that it will work until the non-public API changes. Then it will start to throw complains about not found class or something. But first, the solution:
private void networkInit() {
new NetworkClient()
{
{
Integer readTimeoutInt = Integer.getInteger("sun.net.client.defaultReadTimeout");
defaultSoTimeout = (readTimeoutInt == null) ? -1 : readTimeoutInt.intValue();
Integer connectTimeoutInt = Integer.getInteger("sun.net.client.defaultConnectTimeout");
defaultConnectTimeout = (connectTimeoutInt == null) ? -1 : connectTimeoutInt.intValue();
}
};
}
Was that clear? I just anonymously subclass NetworkClient
and initialize its protected properties exactly the same way it does, but without these nasty privileged calls. As I told above, the only thing one should be afraid of is missing NetworkClient
class or its changed signature in the future versions of JRE. To be sure you can enclose the code above in additional run-time check for class and properties presence.
Hope it was helpful. Keep coding!