I've been doing some work sending HTTP requests from an applet to an application server using the Microsoft VM and have found some behaviors that should be documented somewhere. The following pertains to sending post requests using URLConnection. In order to minimize applet size, support multiple virtual machines (Netscape 4, Microsoft and the Java Plug-in) and SSL, you're pretty much stuck with URLConnection.

Connection Limits

The HTTP 1.1 spec says a client should not open more than two concurrent connections to a web server. If you're hitting a 1.0 server, the limit is four connections. A little background: the Microsoft VM uses the HTTP implementation found in WinInet. WinInet enforces these connection limits by blocking any calls that attempt to break the spec. If you try to open a third connection to a 1.1 server, for example, that call (either a direct call to WinInet or using Java) will block until one of the two active connections is closed.

There are ways to change the number of max connections by making a Win32 call or changing the registry. Do not do this. You might encounter a proxy or server that will enforce the limit.

Abstraction!

There is a lot happening behind the scenes. In order to make sure everything works the way it should, you need to use URLConnection exactly as described.

For example, check out the decompiled source for URLConnection.getInputStream (it's actually com.ms.net.wininet.WininetURLConnection)

    public InputStream getInputStream()
        throws IOException
    {
        if(!attemptedGetInput)
        {
            connect();
            attemptedGetInput = true;
            inputStream = createInputStream();
        }
        if(inputStream == null)
            throw new IOException();
        else
            return inputStream;
    }

As you can see, getInputStream does a lot. If you haven't already used this URLConnection to read, it does connect(), which just sets some state. The actual connection occurs when WininetURLConnection.createInputStream() creates a new WininetInputStream. In the constructor for that class, WininetInputStream.connect is called. This is a native method that probably uses WinInet to create a HTTP connection.

FAQ

What is the correct order for the calls?

Here is what is working for me:

URL url = new URL(baseURL, requestString);
URLConnection connection = url.openConnection();

// If you're sending info from an applet, you probably
// don't want to cache it.
connection.setUseCaches(false);

// We need to set these flags, as they default
// to the settings for get requests.
connection.setDoInput(true);
connection.setDoOutput(true);

// Write any headers.
connection.setRequestProperty("Content-Type",
                              "application/octet-stream");

// Get the output stream for writing the body of the post.
DataOutputStream dos =
    new DataOutputStream(connection.getOutputStream());

try
{
    // Write the contents.
    dos.write(contents, 0, contents.length);

    catch (IOException exc)
    {
        System.out.println("Failed to write message contents:");
        exc.printStackTrace();
        return;
    }
}

// Flush the output stream.  The underlying MSFT classes
// actually do this, but I haven't decompiled the NS stuff
// and you never know what's going to change.
dos.flush();
dos.close();

// I'll leave getting the status code as an exercise for the
// reader for the time being.  Just loop through all available
// header fields, check to see if it starts with HTTP/, and
// parse out the status code.
StringBuffer response = new StringBuffer;
String line;
if (statusCode == HttpURLConnection.HTTP_OK)
{
    BufferedReader rd =
             new BufferedReader(new InputStreamReader(in));
        
    while ((line = rd.readLine()) != null)
    {
        response.append(line);
    }

    rd.close();
}

Why does my applet hang when I call URLConnection.getInputStream?

You're probably hitting the connection limit. Make sure to call close() on the input stream that was returned by URLConnection.getInputStream(). You can't really have threads that just make URLConnection requests whenever they want - you should create a queue of requests that are processed and returned by a separate thread. This will make sure the UI of the applet won't be affected by network problems and creates a serial communications channel so your applet obeys the HTTP spec.

Why does my applet hang when I attempt to close the InputStream I got from URLConnection.getInputStream?

Probably because there's no content length set on the response. If you get a response from the server that doesn't include an entity body (204, 304, etc.) and the server does not set a content-length of 0 (according to the spec, it's optional), closing the input stream can hang.

Why do I get an IOException (InternetReadFile) when I attempt to close the connection on the server-side?

Well, that's a good question. Reading from O'Reilly's HTTP book, it looks like the Microsoft VM might be in the middle of a call to the Win32 call InternetReadFile when your server decides to reset the TCP connection. What worked for me is doing a "graceful" close: call Socket.shutdownOutput() on the server-side (my server-side is Java), followed by Socket.close() on the client connection.

Links