All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bumptech.glide.integration.cronet.BufferQueue Maven / Gradle / Ivy

There is a newer version: 5.0.0-rc01
Show newest version
package com.bumptech.glide.integration.cronet;

import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.chromium.net.UrlResponseInfo;

/**
 * A utility for processing response bodies, as one contiguous buffer rather than an asynchronous
 * stream.
 */
final class BufferQueue {
  public static final String CONTENT_LENGTH = "content-length";
  public static final String CONTENT_ENCODING = "content-encoding";
  private final Queue mBuffers;
  private final AtomicBoolean mIsCoalesced = new AtomicBoolean(false);

  public static Builder builder() {
    return new Builder();
  }

  /**
   * Use this class during a request, to combine streamed buffers of a response into a single final
   * buffer.
   *
   * 

For example: {@code @Override public void onResponseStarted(UrlRequest request, * UrlResponseInfo info) { request.read(builder.getFirstBuffer(info)); } @Override public void * onReadCompleted(UrlRequest request, UrlResponseInfo info, ByteBuffer buffer) { * request.read(builder.getNextBuffer(buffer)); } } */ public static final class Builder { private ArrayDeque mBuffers = new ArrayDeque<>(); private RuntimeException whenClosed; private Builder() {} /** Returns the next buffer to write data into. */ public ByteBuffer getNextBuffer(ByteBuffer lastBuffer) { if (mBuffers == null) { throw new RuntimeException(whenClosed); } if (lastBuffer != mBuffers.peekLast()) { mBuffers.addLast(lastBuffer); } if (lastBuffer.hasRemaining()) { return lastBuffer; } else { return ByteBuffer.allocateDirect(8096); } } /** Returns a ByteBuffer heuristically sized to hold the whole response body. */ public ByteBuffer getFirstBuffer(UrlResponseInfo info) { // Security note - a malicious server could attempt to exhaust client memory by sending // down a Content-Length of a very large size, which we would eagerly allocate without // the server having to actually send those bytes. This isn't considered to be an // issue, because that same malicious server could use our transparent gzip to force us // to allocate 1032 bytes per byte sent by the server. return ByteBuffer.allocateDirect((int) Math.min(bufferSizeHeuristic(info), 524288)); } private static long bufferSizeHeuristic(UrlResponseInfo info) { final Map> headers = info.getAllHeaders(); if (headers.containsKey(CONTENT_LENGTH)) { long contentLength = Long.parseLong(headers.get(CONTENT_LENGTH).get(0)); boolean isCompressed = !headers.containsKey(CONTENT_ENCODING) || (headers.get(CONTENT_ENCODING).size() == 1 && "identity".equals(headers.get(CONTENT_ENCODING).get(0))); if (isCompressed) { // We have to guess at the uncompressed size. In the future, consider guessing a // compression ratio based on the content-type and content-encoding. For now, // assume 2. return 2 * contentLength; } else { // In this case, we know exactly how many bytes we're going to get, so we can // size our buffer perfectly. However, we still have to call read() for the last time, // even when we know there shouldn't be any more bytes coming. To avoid allocating another // buffer for that case, add one more byte than we really need. return contentLength + 1; } } else { // No content-length. This means we're either being sent a chunked response, or the // java stack stripped content length because of transparent gzip. In either case we really // have no idea, and so we fall back to a reasonable guess. return 8192; } } public BufferQueue build() { whenClosed = new RuntimeException(); final ArrayDeque buffers = mBuffers; mBuffers = null; return new BufferQueue(buffers); } } private BufferQueue(Queue buffers) { mBuffers = buffers; for (ByteBuffer buffer : mBuffers) { buffer.flip(); } } /** Returns the response body as a single contiguous buffer. */ public ByteBuffer coalesceToBuffer() { markCoalesced(); if (mBuffers.size() == 0) { return ByteBuffer.allocateDirect(0); } else if (mBuffers.size() == 1) { return mBuffers.remove(); } else { int size = 0; for (ByteBuffer buffer : mBuffers) { size += buffer.remaining(); } ByteBuffer result = ByteBuffer.allocateDirect(size); while (!mBuffers.isEmpty()) { result.put(mBuffers.remove()); } result.flip(); return result; } } private void markCoalesced() { if (!mIsCoalesced.compareAndSet(false, true)) { throw new IllegalStateException("This BufferQueue has already been consumed"); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy