FULL PRODUCT VERSION :
1.5.0_03
ADDITIONAL OS VERSION INFORMATION :
2.6.8-24.11-default #1 Fri Jan 14 13:01:26 UTC 2005 i686 i686 i386 GNU/Linux
(SuSE 9.2), Mac OS X 10.4 and XP SP 2
A DESCRIPTION OF THE PROBLEM :
When opening a URL where the hostname maps to multiple IP addresses, java.net.URL only attempts to connect to the first IP address. In contrast, the following web browsers will attempt to connect to the second address when the first one fails:
Safari 2.0 (412), Mac OS X 10.4 (Darwin Kernel 8.0.0)
FireFox 1.0.2, SuSE Linux 9.2
IE 6.0.2900.2180, WinXP SP 2
In my particular situation, I am using this as part of a test framework and I don't have to worry about connecting to virtual servers. However, this prevents the use of java.net.URL classes for writing a robust, real-world web browser.
The problem is not structural to the format of the URL class, in
particular the host portion of the URL is always exposed as a string,
not a java.net.InetAddress. Thus the underlying IP address is never
made public.
This suggests a simple fix: rotate through all possibilites when
connection. When opening a URLConnection, there is presumably similar to:
Socket socket = new Socket(url.getHost(), url.getPort());
It can be replaced with code similar to:
for (int addrPos = 0; addrPos <
InetAddress.getAllByName(url.getHost()).length; addrPos++) {
InetAddress addr = InetAddress.getAllByName(url.getHost())[i];
try {
Socket socket = new Socket(url.getHost(), url.getPort());
}
catch (IOException ex) {
if (addrPos == 1-InetAddress.getAllByName(url.getHost()).length) {
throw ex;
}
}
I have reproduced it in Java 1.4 on Mac OS X 10.4 and I just tested it on XP SP 2 (Home Edition) and it works exactly as described.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Set up two IP addresses with a single name, e.g.:
vvv.vocalabs.com. 86400 IN A 192.168.11.120
vvv.vocalabs.com. 86400 IN A 192.168.11.23
2. Call InetAddress.getAllByName to see that both hosts show up.
InetAddress.getAllByName("vvv.vocalabs.com")
3. Open a URL stream
new URL("http://vvv.vocalabs.com:8080/panelist/login.jsp").openStream();
4. Shut down the server which answered the request. Note that on the OSes I have tested (OS X 10.4, SuSE Linux 9.2, WinXP SP2), addresses seem to have a preferential order, even though the DNS server is shuffling the order-- this may be a bug in my test environment.
4. Open the URL stream again; a "no route to host" or "cannot connect" error should result, depending on how thoroughly disconnected the server.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I tried two configurations, (a) unplugging the server's Ethernet cable; and (b) shutting the server's Tomcat instance down. In both cases, web browsers (IE, FF, Safari) switched to the secondary server. I expected Java to do the same.
ACTUAL -
I tried two configurations, (a) via HttpUnit 1.6 and unplugging the server's Ethernet cable; and (b) via Jython and shutting the server's Tomcat instance down.
In once case, I got a "no route to host"; in the second case I got a "Connection refused".
ERROR MESSAGES/STACK TRACES THAT OCCUR :
One stack trace for each configuration:
java.net.NoRouteToHostException: No route to host
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:494)
at sun.net.www.protocol.http.HttpURLConnection$6.run(HttpURLConnection.java:1202)
at java.security.AccessController.doPrivileged(Native Method)
at sun.net.www.protocol.http.HttpURLConnection.getChainedException(HttpURLConnection.java:1196)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:885)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:367)
at com.meterware.httpunit.HttpWebResponse.readResponseHeader(HttpWebResponse.java:162)
at com.meterware.httpunit.HttpWebResponse.readHeaders(HttpWebResponse.java:200)
at com.meterware.httpunit.HttpWebResponse.<init>(HttpWebResponse.java:56)
at com.meterware.httpunit.HttpWebResponse.<init>(HttpWebResponse.java:67)
at com.meterware.httpunit.WebConversation.newResponse(WebConversation.java:76)
at com.meterware.httpunit.WebWindow.getResource(WebWindow.java:164)
at com.meterware.httpunit.WebWindow.getSubframeResponse(WebWindow.java:128)
at com.meterware.httpunit.WebWindow.getResponse(WebWindow.java:121)
at com.meterware.httpunit.WebClient.getResponse(WebClient.java:113)
at com.vocalabs.functional.SurveyRunner.run(SurveyRunner.java:258)
at java.lang.Thread.run(Thread.java:595)
Caused by: java.net.NoRouteToHostException: No route to host
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
at java.net.Socket.connect(Socket.java:507)
at java.net.Socket.connect(Socket.java:457)
at sun.net.NetworkClient.doConnect(NetworkClient.java:157)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:365)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:477)
at sun.net.www.http.HttpClient.<init>(HttpClient.java:214)
at sun.net.www.http.HttpClient.New(HttpClient.java:287)
at sun.net.www.http.HttpClient.New(HttpClient.java:299)
at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:792)
at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:744)
at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:669)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:913)
at sun.net.www.protocol.http.HttpURLConnection.getHeaderFieldKey(HttpURLConnection.java:1919)
at com.meterware.httpunit.HttpWebResponse.loadHeaders(HttpWebResponse.java:216)
at com.meterware.httpunit.HttpWebResponse.readHeaders(HttpWebResponse.java:198)
... 9 more
java.net.ConnectException: Connection refused
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
at java.net.Socket.connect(Socket.java:507)
at java.net.Socket.connect(Socket.java:457)
at sun.net.NetworkClient.doConnect(NetworkClient.java:157)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:365)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:477)
at sun.net.www.http.HttpClient.<init>(HttpClient.java:214)
at sun.net.www.http.HttpClient.New(HttpClient.java:287)
at sun.net.www.http.HttpClient.New(HttpClient.java:299)
at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:792)
at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:744)
at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:669)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:913)
at java.net.URL.openStream(URL.java:1007)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java)
at org.python.core.PyMethod.__call__(PyMethod.java)
at org.python.core.PyObject.__call__(PyObject.java)
at org.python.core.PyInstance.invoke(PyInstance.java)
at org.python.pycode._pyx6.f$0(<console>:1)
at org.python.pycode._pyx6.call_function(<console>)
at org.python.core.PyTableCode.call(PyTableCode.java)
at org.python.core.PyCode.call(PyCode.java)
at org.python.core.Py.runCode(Py.java)
at org.python.core.Py.exec(Py.java)
at org.python.util.PythonInterpreter.exec(PythonInterpreter.java)
at org.python.util.InteractiveInterpreter.runcode(InteractiveInterpreter.java)
at org.python.util.InteractiveInterpreter.runsource(InteractiveInterpreter.java)
at org.python.util.InteractiveInterpreter.runsource(InteractiveInterpreter.java)
at org.python.util.InteractiveConsole.push(InteractiveConsole.java)
at org.python.util.InteractiveConsole.interact(InteractiveConsole.java)
at org.python.util.jython.main(jython.java)
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
new java.net.URL(MY_URL_STRING).openStream();
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Rewrite URL to use an IP address found via InetAddress.getAllByName(String). Note that this will not work for connecting to virtual servers.
###@###.### 2005-05-24 09:50:01 GMT