Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ways to proxy an InputStream

I am using Android-Universal-Image-Loader to load images from remote server over HTTPS on my Android application. To have access to images the client should provide a valid token and sometimes server can return "expired crsf token" error. In order to handle this behavior a custom ImageDownloader should be defined. Below is the base implementation of method that should be overrrided in my implementation.

protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
    HttpURLConnection conn = createConnection(imageUri, extra);

    int redirectCount = 0;
    while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
         conn = createConnection(conn.getHeaderField("Location"), extra);
         redirectCount++;
    }

    InputStream imageStream;
    try {
         imageStream = conn.getInputStream();
    } catch (IOException e) {
         // Read all data to allow reuse connection (http://bit.ly/1ad35PY)
         IoUtils.readAndCloseStream(conn.getErrorStream());
         throw e;
    }
    if (!shouldBeProcessed(conn)) {
         IoUtils.closeSilently(imageStream);
         throw new IOException("Image request failed with response code " + conn.getResponseCode());
    }

    return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
}

I want to rewrite it to handle invalid token errors. For example, if the server returns such error it should be recognized, token should be regenerated and request repeated.

The only solution I come up with is like this (shortened code):

imageStream = conn.getInputStream();
byte[] body = org.apache.commons.io.IOUtils.toByteArray(imageStream);
if (body.length < 300  // high probability to contain err message
             && isInvalidToken(body)) {
              // handle error
}
return new ByteArrayInputStream(body);

Is is safe to use such kind of solution, considering I use it only for thumbnails of max 80kb size? Are there any other solutions?

like image 686
bvk256 Avatar asked Dec 14 '15 10:12

bvk256


People also ask

What are the methods of InputStream?

Methods of InputStream read() - reads one byte of data from the input stream. read(byte[] array) - reads bytes from the stream and stores in the specified array. available() - returns the number of bytes available in the input stream. mark() - marks the position in the input stream up to which data has been read.

How do I change InputStream to FileInputStream?

URL resource = classLoader. getResource("resource. ext"); File file = new File(resource. toURI()); FileInputStream input = new FileInputStream(file); // ...


2 Answers

Your solution is safe, although it's nicer if you create your ImageDownloaderInputStream class that implements InputStream and wraps the original InputStream. You can pre-load (buffer) some chunk from the underlying input stream to detect if the content is valid or not.

The only method you should override is read().

If the content is valid, you can serve the buffer content to the caller, when the buffer is empty, directly stream from the underlying InputStream.

If the content is invalid, just simply read another stream, or return a zero-length stream.

public class ImageDownloaderInputStream extends InputStream {
    private byte[] buffer = null;
    private int bufLen = 0;
    private int bufIndex = 0;
    private boolean isContentValid;
    private InputStream wrapped;

    public ImageDownloaderInputStream (InputStream wrapped) {
         this.wrapped = wrapped;
    }

    @Override
    public ind read() {
        if(buffer == null) {
            // check content and fill buffer
            this.isContentValid = checkContent();
        }
        if (this.isContentValid) {
            if(bufIndex < bufLen) {
                return buffer[bufIndex++] & 0xFF;
            } else {
                 return wrapped.read();
            }
        } else {
            // error handling: zero-length stream
            return -1;
        }
    }

    private boolean checkContent() {
        // fill the buffer
        this.buffer = new byte[1024];
        this.bufLen = wrapped.read(this.buffer); 
        // read more if not enough

        // check the content
        return true;
        // return false;      
    }
}
like image 139
gaborsch Avatar answered Oct 13 '22 14:10

gaborsch


You can check for a valid token after you checked that the response was 200 OK like so:

conn.getResponseCode() == HttpStatus.RESPONSE_OK && isValidToken(body)

If these conditions are not met then you handle it accordingly i.e repeat the request x times.

I would consider having a isValidToken(...) method instead of your isInvalidToken(...) so that you don't have to negate the response of the method.

like image 29
kstandell Avatar answered Oct 13 '22 14:10

kstandell