9
votes

Is the following code safe:

try {
    URL url = new URL(urlRequest);
    conn = (HttpURLConnection)url.openConnection();
    conn.setConnectTimeout(30000);
    conn.setReadTimeout(30000);
    conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
    String encoding = conn.getContentEncoding();
    return Utils.wrapCompressedStream(conn.getInputStream(), encoding);
} catch (IOException e) {
    if(conn != null) {
        conn.getContentEncoding();
        conn.getErrorStream();
        conn.whateverOtherMethodThere();
        ...
    }
}

Particularly, is it safe in case of InterruptedIOException (say, read timeout) to call methods like getContentEncoding()? As far as I understand this method requires live connection to read HTTP(S) headers.

Update (additional info):

This question originates from a real-system experience. I believe, the system was run on Oracle/Sun JVM 1.6 then. The code is almost the same:

...    
} catch (IOException e) {
    if(conn != null) {
         try {
             String response = tryGetResponse(conn);
...

The problem happened in the tryGetResponse on HTTPS requests:

 private static String tryGetResponse(HttpURLConnection conn) {
    if(conn == null) return "(failed to get)";
    InputStream in = null;
    try {
        InputStream err = conn.getErrorStream();
        if (err != null) {
            in = Utils.wrapCompressedStream(err, conn.getContentEncoding());
        }
        return Utils.inputStreamToString(in);
    } catch (IOException e) {
        return "(failed to get)";
    } finally {
        Utils.closeQuitely(in);
    }
}

Spontaneously system hanged on the socket connect (or read) in the getContentEncoding() call:

in = Utils.wrapCompressedStream(err, conn.getContentEncoding());

exactly after SocketTimeoutException is thrown in the initial code.

So, it seems that getContentEncoding() tries (or tried in Java 6) to establish a new connection without timeouts being set.

4
No, it is not, if you try to get a header field like content encoding from an incomplete inputstream you will get another IOException, check the HttpURLConnection source code.vzamanillo

4 Answers

3
votes

No. It is not safe in general. The behavior might differ between JVM implementations (think IBM J9 vs. Oracle VM vs. Open JDK) and change between versions of the same VM without notice.

The reason for this is that the API specification makes no guarantees.

If you tell me which specific implementation in which particular version you are using, I could look in the sources and try to make some conclusions. But I would strongly advise against relying about the behavior you find there.

With regards to HTTPS: There seem still to be some bugs with SSL. For example OpenSSL has made a public announcement, that they will reveal a security bug end of this week. The bug fixes for that might change the behavior of a HTTPS connection in some error cases. It's possible that whatever we find in the sources will be moot at the end of this week. If not, it might change with the next security fix.

Update:

I've tried to find the sources that correspond to the version of Java you have referenced in your updated question. Finding the Java-Sources is not a big problem, but the code goes very quickly into native parts. This alone is a good hint, that the answer would not only be version specific, but also platform specfic (Linux, Windows, Mac, etc). You can check yourself on the OpenJDK sources for Java 1.6, e.g. for the network stack for Windows.

Beware: You have very likely used the Sun JDK 1.6. OpenJDK 1.6 is besed on the Sun/Oracle JDK, but on JDK 1.7. The Open JDK 1.6 is code form Sun/Oracle JDK 1.7 backportet to Java 1.6. So there still might be some small differences, that nevertheless can be significant regarding the usage of the connection after an error occured.

2
votes

Particularly, is it safe in case of InterruptedIOException (say, read timeout) to call methods like getContentEncoding()?

You can try. The worst that will happen is anotherIOException. In several cases of IOException, for example FileNotFoundException, it is perfectly safe to call getErrorStream() and read the content of an error page.

2
votes

For getErrorStream the spec says:

Returns the error stream if the connection failed but the server sent useful data nonetheless. The typical example is when an HTTP server responds with a 404, which will cause a FileNotFoundException to be thrown in connect, but the server sent an HTML help page with suggestions as to what to do. This method will not cause a connection to be initiated. If the connection was not connected, or if the server did not have an error while connecting or if the server had an error but no error data was sent, this method will return null. This is the default.

Returns: an error stream if any, null if there have been no errors, the connection is not connected or the server sent no useful data.

You can also inspect the source to clear up any doubts, so lets look at some of the methods:

public InputStream getErrorStream() {
    if (connected && responseCode >= 400) {
        // Client Error 4xx and Server Error 5xx
        if (errorStream != null) {
            return errorStream;
        } else if (inputStream != null) {
            return inputStream;
        }
    }
    return null;
}

It is safe to call this (no exception will be thrown) and the errorStream can be set in some IOException cases. The comment in the source is buffer the error stream if bytes < 4k and it can be buffered within 1 second.

getContentEncodings behaviour from the spec is:

Returns: the content encoding of the resource that the URL references, or null if not known.

But what about after an error? Lets look at code:

public String getContentEncoding() { //from the base class java.net.URLConnection
    return getHeaderField("content-encoding");
}

public String getHeaderField(String name) {
    try {
        getInputStream();
    } catch (IOException e) {} //ah exception is eaten

    if (cachedHeaders != null) {
        return cachedHeaders.findValue(name);
    }

    return responses.findValue(name);
}

So it caches headers and returns them if known, even potentially after an IOException, it doesn't propagate an exception but might return null if it has not previously got the headers successfully.

0
votes
catch (IOException e) {
    if(conn != null) {
        conn.getContentEncoding();
        conn.getErrorStream();
        conn.whateverOtherMethodThere();
        ...
    }

public class InterruptedIOException extends IOException

Signals that an I/O operation has been interrupted. An InterruptedIOException is thrown to indicate that an input or output transfer has been terminated because the thread performing it was interrupted. The field bytesTransferred indicates how many bytes were successfully transferred before the interruption occurred.

That means if an interrupted exception occurs you can not do further processing on the HttpUrlConnection.

Again Through IOException you can not catch other type of exception like IllegalArgumentException, illegalthreadstate exception and many more.

For more details http://download.java.net/jdk7/archive/b123/docs/api/java/net/HttpURLConnection.html#getRequestMethod%28%29