代理InputStream的方法

9

我正在使用Android-Universal-Image-Loader在我的Android应用程序上从远程服务器加载图像,通过HTTPS。为了访问图像,客户端应提供有效的令牌,有时服务器会返回“过期crsf令牌”错误。为了处理这种行为,应定义自定义ImageDownloader。以下是应在我的实现中重写的方法的基本实现。

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());
}

我希望重新编写代码以处理无效的令牌错误。例如,如果服务器返回此类错误,则应识别该错误,重新生成令牌并重复请求。

我能想到的唯一解决方案是这样的(缩短后的代码):

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);

如果我只是用它来生成最大80kb的缩略图,那么使用这种解决方案是否安全?还有其他解决方案吗?

3个回答

3
您的解决方案是安全的,但最好创建一个实现InputStream并包装原始InputStreamImageDownloaderInputStream类。您可以预加载(缓冲)来自底层输入流的某些块以检测内容是否有效。
您应该覆盖的唯一方法是read()
如果内容有效,则可以向调用者提供缓冲区内容;当缓冲区为空时,直接从底层InputStream中流式传输。
如果内容无效,只需读取另一个流或返回零长度的流即可。
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;      
    }
}

1
谢谢,这似乎是实现所描述任务的“正确”方式之一。 - bvk256
你使用的是哪个InputStream?我遇到了这个错误: “The type InputStream cannot be a superinterface of ImageDownloaderInputStream; a superinterface must be an interface” - Jose1755
@Jose1755 哎呀,复制粘贴出了问题,应该是 java.io.InputStream,而且因为它是一个抽象类,我们必须要用 extend 来继承它。已经修正了上面的代码,希望你也能理解。 - gaborsch

1
在确认响应为200 OK后,您可以这样检查有效令牌:
conn.getResponseCode() == HttpStatus.RESPONSE_OK && isValidToken(body)

如果不满足这些条件,则需要相应地处理,例如重复x次请求。 我会考虑使用一个isValidToken(...)方法来代替你的isInvalidToken(...),这样你就不必否定该方法的响应。

0

你考虑过类似这样的东西吗?

if(conn.getResponseCode()==HttpStatus.RESPONSE_OK) else{ //repeat request...} 

问题在于当存在无效的令牌时,服务器将以200 OK作出响应,仅通过HTTP响应体来指示错误。 - bvk256

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接