Experimental URLConnection timeout     author: Lars J. Nilsson  
     
 

    So, this little tip popped up as a side note on all the worries and problems with the - maybe overly - generic URLConnection design. A discussion on the Robots mail list made me remember it and a lifelong dedication to things I should let slide - after all: there are free third party solutions ready to use already - made me write this trick. But before we begin, two notes of warning:

    Warning: This trick subclasses some undocumented classes from Sun shipped with the JRE, and Sun might change them at any time. We will keep the changes as small as possible to allow us to fast adopt such a change, but I felt a note of warning was justified.

    Warning: This trick is not tested properly. It is merely a fast patch, so use it carefully, review it, and send me your comments.

These two notes said, I'd just like to stress the second again. For example: The use of fall over for proxy servers is crude and I have not tried it on keep-alive connections. Please mail me if you figure it out and I can add it up to this article. You'll find the entire source of this article here as a test class. Now, let's start:

The setup:
    The idea of this trick is to set a timeout to URL connections. By the way: I will use the HTTP classes for this article, but the trick should be applicable to all URLConnections. What we need to do is to find the class that actually makes the socket connection to the server, add the timeout to that same socket, modify the URLConnection class to use our new timed-out client, and provide a stream handler that can be passed to the URL constructor. First out is:

The sun.net.www.http.HttpClient class:
    This is the class that connects to a server, sets up the parameters for the HTTP call and handles keep-alive connections, and much more. (Actually, the socket to the remote server is created by the HttpClient parent class the NetworkClient, but we don't have to care about that). So, we'll subclass the HttpClient with our own timeout version and provide a reference to the socket the client is using and a constructor:

  private Socket socket;

  public TimeoutHttpClient(URL url,
      String proxy,
      int proxyPort) throws IOException {

    super(url, proxy, proxyPort);
  }


This is the constructor that actually does the work and for convenience we'll also provide an overloading of the constructor using the default values for the String and int parameters through this call:

  public TimeoutHttpClient(URL url)
            throws IOException {

    this(url, null, -1);
  }


Having done that, we need two methods. One method that overwrites the "doConnect" method from the NetworkClient and one static "New" method:

  protected Socket doConnect(String host, int port)
            throws IOException, UnknownHostException {

    socket = new Socket(host, port);

    socket.setSoTimeout(TimeoutHttpURLConnection.TIMEOUT);

    return socket;
  }


In this method "socket" is the reference to the instance member we created above. In this example I also use a static variable in the
"TimeoutHttpURLConnection" that we will create below to hold our default timeout value for simplicity, but it could easily be a private member of the TimeoutHttpClient class.

  public static HttpClient New(URL url_1)
             throws IOException {

    HttpClient httpc = (HttpClient)kac.get(url_1);

    if(httpc == null) {

      httpc = new TimeoutHttpClient(url_1, null, -1);

    } else httpc = HttpClient.New(url_1);

    return httpc;
  }


This is trickier. This is a static method used by the HttpURLConnection class to check for keep-alive connections stored in the "kac" member. So we'll remember this method for later. Note that we call the parent class static "New" method - this is because the HttpClient class wants to do a security check before assigning the new URL to the connection. Now we'll do two small "get" and "set" methods for the timeout and then the client is ready.

  public void setTimeout(int millis) throws SocketException {
    if(socket != null) socket.setSoTimeout(millis);
  }

  public int getTimeout() throws SocketException {
    if(socket != null) return socket.getSoTimeout();
    else return -1;
  }


The sun.net.www.protocol.http.HttpURLConnection class:
   Next in line is the actual URLConnection instance. Here we need to make sure it does not create anything but our new timeout client class as client instance. We will also provide the static variable for the actual length of the timeout in milliseconds:

  public static int TIMEOUT = 30000;

  public TimeoutHttpURLConnection(URL url,
      String proxy,
      int proxyPort) throws IOException {

    super(url, proxy, proxyPort);
  }

  public TimeoutHttpURLConnection(URL url)
            throws IOException {

    this(url, null, -1);
  }

  protected HttpClient getNewClient(URL url)
            throws IOException {

    return new TimeoutHttpClient(url, null, -1);
  }

  protected HttpClient getProxiedClient(URL url,
      String proxy,
      int proxyPort) throws IOException {

    return new TimeoutHttpClient(url, proxy, proxyPort);
  }


That's the easy bit. The hard bit comes now: We now need to implement a "connect" method that gets a HttpClient and an output stream for the connection. The catch is that the HttpClient uses a fall-over mechanism before using an eventual proxy host, and the boolean variable that determines that function is private. We'll work around that with the assumption that if the "proxySet" system property is set to "true" we're going to use a proxy if we can parse the system property value correctly. If you have better ideas on how to perform this action please mail me.

  public void connect()
            throws IOException {

    if(connected) return;

    Properties prop = System.getProperties();
    String set = (String)prop.get("proxySet");

    if(set != null && set.equalsIgnoreCase("true")) {

      String host = (String)prop.get("proxyHost");
      int port = -1;

      try {
        port = Integer.parseInt((String)prop.get("proxyPort"));
      } catch(Exception e) { }

      if(host != null && port != -1) {
        http = new TimeoutHttpClient(url, host, port);
      } else http = TimeoutHttpClient.New(url);

    } else http = TimeoutHttpClient.New(url);

    if(http instanceof TimeoutHttpClient) {
      ((TimeoutHttpClient)http).setTimeout(TIMEOUT);
    }

    ps = (PrintStream)http.getOutputStream();

    connected = true;
  }

The URLStreamHandler class:

  public TimeoutHttpStreamHandler() {
    super();
  }

  public URLConnection openConnection(URL url)
            throws IOException {

    return new TimeoutHttpURLConnection(url);
  }


The roundup:
    Now, having done the above lines of code and compiled them, it is time to test them. Personally I always have a dummy class - which you'll find here - about to copy code into and test. I'm sure you've figured it all out by know, but for the sake of the article I'll provide my main method:

public static void main(String[] args) {

 try {

  TimeoutHttpURLConnection.TIMEOUT = Integer.parseInt(args[1]);

  URL base = new URL(args[0]);

  URL url = new URL(base, "", new TimeoutHttpStreamHandler());

  URLConnection con = url.openConnection();

  InputStream in = con.getInputStream();

  int tmp = 0;

  while((tmp = in.read()) != -1) System.out.print((char)tmp);

  in.close();

 } catch(Exception e) {

  e.printStackTrace();

 }
}


The class is called with the URL you want to test it on as first parameter and the timeout in milliseconds as second:

    % java mydummyclass www.yahoo.com 30000

Try to set the timeout to 1 millisecond (which should be too fast for any setup) and watch your reward - the InterruptedIOException - appear on the screen.

Now please try this out and... I'll say it again: This is an experiment. Mail me your ideas and comments and we can make this article complete together.