Ways to proxy an InputStream
Asked Answered
S

3

9

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?

Surefire answered 14/12, 2015 at 10:14 Comment(0)
C
3

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;      
    }
}
Conklin answered 14/12, 2015 at 11:44 Comment(3)
Thanks, this seems to be one of the 'proper' ways to implement the described task.Surefire
Which InputStream are you using? I am getting this error: "The type InputStream cannot be a superinterface of ImageDownloaderInputStream; a superinterface must be an interface"Intratelluric
@Intratelluric oops, a copy-paste problem, it is java.io.InputStream, and because it is an abstract class, we have to extend it. Fixed the code above, hope you figured it out, too.Conklin
K
1

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.

Korean answered 14/12, 2015 at 11:5 Comment(0)
I
0

Have you considered something like this?

if(conn.getResponseCode()==HttpStatus.RESPONSE_OK) else{ //repeat request...} 
Insolvable answered 14/12, 2015 at 10:30 Comment(1)
The problem is when there are an invalid token server will respond with 200 OK, only the body of HTTP response will indicate the error.Surefire

© 2022 - 2024 — McMap. All rights reserved.