Thursday, August 8, 2013

Java: How to solve the InputStream waiting for data indeterminately

I've worked in a web project written in Java where I got a weird bug using a InputStream object. My project works as a client of another system what I do not have control and needed to download some data from that.

My problem was that somehow, when I was downloading some data using a InputStream the object was locked, waiting for data forever, even when the transmission had already finished.

To solve that problem, considering that I didn't have control about the server, what I did was implement a kind of timeout to the InputStream.

For that, I read all the streaming, counting the time since the last time that some data had come. When it completed TIME_OUT milliseconds (in this case 1000) since the last reading, I manually stop the reading and created a new InputStream object with the data alright read.

Let's see the code!

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;

public class InputStreamUtils {
 
 public static long TIME_OUT = 1000;
 
 public static InputStream autoFinish(InputStream is) throws IOException{
  if (is == null){
   return null;
  }
  
  long lastTimeDataWasRead = Calendar.getInstance().getTimeInMillis();
  
  ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  int nRead = 0;
  byte[] data = new byte[1024];

  while (nRead != -1) {
   if (is.available() == 0){
    long timeSinceLastReading = Calendar.getInstance().getTimeInMillis() - 
lastTimeDataWasRead;
    if (timeSinceLastReading >= TIME_OUT){
     nRead = -1;
     break;
    }
   }
   else{
    lastTimeDataWasRead = Calendar.getInstance().getTimeInMillis();
    nRead = is.read(data, 0, 1024);
    if (nRead != -1){
     buffer.write(data, 0 , nRead);
    }
   }
  }
  buffer.flush();
  return (InputStream) new ByteArrayInputStream(buffer.toByteArray());
 }
}

Yet, if you want to use the data as a byte array representation you can return buffer.toByteArray() instead of (InputStream) new ByteArrayInputStream(buffer.toByteArray()).