Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpURLConnection.getRequestProperties() throws IllegalStateException: "Already connected"

After executing a request I would like to check the request headers, but it doesn't work.

I call getRequestProperties() on an instance of sun.net.www.protocol.http.HttpURLConnection and I always get an IllegalStateException with the message "Already connected". As if I wanted to set request properties. But I only want to read them.

The responsible code for this behaviour is in the HttpUrlConnection:
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/net/www/protocol/http/HttpURLConnection.java#HttpURLConnection.getRequestProperties%28%29

public synchronized Map<String, List<String>> getRequestProperties() {
    if (connected)
        throw new IllegalStateException("Already connected");
    // ...
}

Ok so maybe I should only read the request properties after disconnecting. But it turns out, disconnect() doesn't set connected to false. Although it should: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/net/www/protocol/http/HttpURLConnection.java#HttpURLConnection.disconnect%28%29

It also doesn't seem to make a difference if I read the stream to the end or not. Closing the InputStream before or after calling disconnect doesn't make a difference either.

I'm confused. Can you help me?

  1. Why doesn't disconnect() set connected to false?
  2. Why can't I read request properties while the urlConnection is connected?
  3. How do you properly read request headers after the request?

The code to reproduce this is a Unit test for Android (I use Robolectric), but I think you can use it in a Java project as well and call it from main() after removing the test annotation:

/**
 * Test if HttpUrlConnection works as expected, because in some cases it seems it doesn't
 *
 * @throws Exception
 */
@Test
public void testHttpUrlConnection() throws Exception
{
    final URL url = new URL("http://www.stackoverflow.com");
    final HttpURLConnection urlConnection = ( HttpURLConnection ) url.openConnection( );
    urlConnection.setRequestMethod("GET");
    InputStream is = null;
    try
    {
        is = urlConnection.getInputStream();
        assertEquals(200, urlConnection.getResponseCode());
    }
    catch (IOException ex)
    {
        is = urlConnection.getErrorStream( );
    }
    final String result = copyStreamToString(is); // some html response
    // Streams must be closed before disconnecting (according to http://stackoverflow.com/a/11056207/3596676)
    is.close();
    assertTrue((Boolean) getFieldViaRecursiveReflection(urlConnection, "connected"));
    // urlConnection should always be disconnected (according to http://developer.android.com/reference/java/net/HttpURLConnection.html)
    urlConnection.disconnect();
    assertFalse((Boolean) getFieldViaRecursiveReflection(urlConnection, "connected")); // AssertionError
    // getRequestProperties throws IllegalStateException ("already connected")
    Map<String, List<String>> requestProperties = urlConnection.getRequestProperties();
    // do stuff with the properties
    // return the result
}

private static String copyStreamToString( final InputStream is ) throws IOException
{
    if ( is == null )
    {
        return "";
    }
    BufferedReader reader = new BufferedReader( new InputStreamReader( is ) );
    String result = copyBufferedReaderToString( reader );
    reader.close( );
    return result;
}

private static String copyBufferedReaderToString( final BufferedReader bufferedReader ) throws IOException
{
    StringBuffer sb = new StringBuffer( );
    String line;
    while ( ( line = bufferedReader.readLine( ) ) != null )
    {
        sb.append( line );
    }
    return sb.toString( );
}

private static Object getFieldViaRecursiveReflection(final Object object, final String attribute) throws Exception
{
    return getFieldViaRecursiveReflection(object, object.getClass(), attribute);
}

private static Object getFieldViaRecursiveReflection(final Object object, final Class<?> c, final String attribute) throws Exception
{
    try
    {
        final Field field = c.getDeclaredField(attribute);
        field.setAccessible(true);
        return field.get(object);
    }
    catch (NoSuchFieldException ex)
    {
        /* end of type hierarchy? */
        Class<?> superClass = c.getSuperclass();
        if (superClass == null)
        {
            throw ex;
        }
        else
        {
            return getFieldViaRecursiveReflection(object, superClass, attribute);
        }
    }
}
like image 238
backendev Avatar asked Mar 17 '23 07:03

backendev


1 Answers

As no one posted an answer in the 2 months since I asked the question, but today I had to deal with the problem again and found a solution, I will answer my own question.

I can't answer all the questions in the answer (e.g. "why doesn't urlConnection.disconnect() set the connected attribute of urlConnection to false?"), but I found the solution for the main problem, which was that reading the headers of a request didn't work when the urlConnection was connected.

For some reason, which I can't remember, I wanted/needed to check the request headers after the request was done and the response was there. But I looked at the implementation of getRequestProperties() in sun.net.www.protocol.http.HttpURLConnection again (see the code here) and noticed that a method called filterAndAddHeaders gets called. So it seems that headers not only get read in that method, but set. I'm not sure why this is done in a getter method (the getRequestProperties() one), but it makes sense that when the request is already done, you should warn the user when he tries to add request headers - in this case with the IllegalStateException that bothered me so much.

To come to the solution:

I just moved the call to getRequestProperties() to before the request gets sent. And now everything works fine.

P.S.:

Please note that this is not all there is to it. One of my unit tests ran successfully even though I called getRequestProperties() after the request. In that case the urlConnection internal attribute connected was set to false. I haven't figured it all out, but it may have been related to the response status code 304 (not modified). Maybe this helps as a hint if you have to deal with this problem and for some reason can't move the getRequestProperties() call to before sending the request.

like image 93
backendev Avatar answered Apr 06 '23 23:04

backendev