Skip to content

Commit ba99582

Browse files
sjuddglide-copybara-robot
authored andcommitted
Internal change
PiperOrigin-RevId: 289164359
1 parent cbbb254 commit ba99582

14 files changed

+1160
-0
lines changed

integration/cronet/build.gradle

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.bumptech.glide.integration.cronet
2+
3+
apply plugin: 'com.android.library'
4+
5+
dependencies {
6+
implementation project(':library')
7+
annotationProcessor project(':annotation:compiler')
8+
9+
api "androidx.annotation:annotation:${ANDROID_X_VERSION}"
10+
}
11+
12+
android {
13+
compileSdkVersion COMPILE_SDK_VERSION as int
14+
15+
defaultConfig {
16+
minSdkVersion MIN_SDK_VERSION as int
17+
targetSdkVersion TARGET_SDK_VERSION as int
18+
19+
versionName VERSION_NAME as String
20+
}
21+
22+
compileOptions {
23+
sourceCompatibility JavaVersion.VERSION_1_7
24+
targetCompatibility JavaVersion.VERSION_1_7
25+
}
26+
}
27+
28+
apply from: "${rootProject.projectDir}/scripts/upload.gradle"

integration/cronet/gradle.properties

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
POM_NAME=Glide Cronet Integration
2+
POM_ARTIFACT_ID=cronet-integration
3+
POM_PACKAGING=aar
4+
POM_DESCRIPTION=An integration library to use Cronet to fetch data over http/https in Glide

integration/cronet/lint.xml

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<lint>
3+
<issue id="AllowBackup" severity="ignore"/>
4+
</lint>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<manifest package="com.bumptech.glide.integration.cronet">
2+
<application />
3+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package com.bumptech.glide.integration.cronet;
2+
3+
import java.nio.ByteBuffer;
4+
import java.util.ArrayDeque;
5+
import java.util.List;
6+
import java.util.Map;
7+
import java.util.Queue;
8+
import java.util.concurrent.atomic.AtomicBoolean;
9+
import org.chromium.net.UrlResponseInfo;
10+
11+
/**
12+
* A utility for processing response bodies, as one contiguous buffer rather than an asynchronous
13+
* stream.
14+
*/
15+
final class BufferQueue {
16+
public static final String CONTENT_LENGTH = "content-length";
17+
public static final String CONTENT_ENCODING = "content-encoding";
18+
private final Queue<ByteBuffer> mBuffers;
19+
private final AtomicBoolean mIsCoalesced = new AtomicBoolean(false);
20+
21+
public static Builder builder() {
22+
return new Builder();
23+
}
24+
25+
/**
26+
* Use this class during a request, to combine streamed buffers of a response into a single final
27+
* buffer.
28+
*
29+
* <p>For example: {@code @Override public void onResponseStarted(UrlRequest request,
30+
* UrlResponseInfo info) { request.read(builder.getFirstBuffer(info)); } @Override public void
31+
* onReadCompleted(UrlRequest request, UrlResponseInfo info, ByteBuffer buffer) {
32+
* request.read(builder.getNextBuffer(buffer)); } }
33+
*/
34+
public static final class Builder {
35+
private ArrayDeque<ByteBuffer> mBuffers = new ArrayDeque<>();
36+
private RuntimeException whenClosed;
37+
38+
private Builder() {}
39+
40+
/** Returns the next buffer to write data into. */
41+
public ByteBuffer getNextBuffer(ByteBuffer lastBuffer) {
42+
if (mBuffers == null) {
43+
throw new RuntimeException(whenClosed);
44+
}
45+
if (lastBuffer != mBuffers.peekLast()) {
46+
mBuffers.addLast(lastBuffer);
47+
}
48+
if (lastBuffer.hasRemaining()) {
49+
return lastBuffer;
50+
} else {
51+
return ByteBuffer.allocateDirect(8096);
52+
}
53+
}
54+
55+
/** Returns a ByteBuffer heuristically sized to hold the whole response body. */
56+
public ByteBuffer getFirstBuffer(UrlResponseInfo info) {
57+
// Security note - a malicious server could attempt to exhaust client memory by sending
58+
// down a Content-Length of a very large size, which we would eagerly allocate without
59+
// the server having to actually send those bytes. This isn't considered to be an
60+
// issue, because that same malicious server could use our transparent gzip to force us
61+
// to allocate 1032 bytes per byte sent by the server.
62+
return ByteBuffer.allocateDirect((int) Math.min(bufferSizeHeuristic(info), 524288));
63+
}
64+
65+
private static long bufferSizeHeuristic(UrlResponseInfo info) {
66+
final Map<String, List<String>> headers = info.getAllHeaders();
67+
if (headers.containsKey(CONTENT_LENGTH)) {
68+
long contentLength = Long.parseLong(headers.get(CONTENT_LENGTH).get(0));
69+
boolean isCompressed =
70+
!headers.containsKey(CONTENT_ENCODING)
71+
|| (headers.get(CONTENT_ENCODING).size() == 1
72+
&& "identity".equals(headers.get(CONTENT_ENCODING).get(0)));
73+
if (isCompressed) {
74+
// We have to guess at the uncompressed size. In the future, consider guessing a
75+
// compression ratio based on the content-type and content-encoding. For now,
76+
// assume 2.
77+
return 2 * contentLength;
78+
} else {
79+
// In this case, we know exactly how many bytes we're going to get, so we can
80+
// size our buffer perfectly. However, we still have to call read() for the last time,
81+
// even when we know there shouldn't be any more bytes coming. To avoid allocating another
82+
// buffer for that case, add one more byte than we really need.
83+
return contentLength + 1;
84+
}
85+
} else {
86+
// No content-length. This means we're either being sent a chunked response, or the
87+
// java stack stripped content length because of transparent gzip. In either case we really
88+
// have no idea, and so we fall back to a reasonable guess.
89+
return 8192;
90+
}
91+
}
92+
93+
public BufferQueue build() {
94+
whenClosed = new RuntimeException();
95+
final ArrayDeque<ByteBuffer> buffers = mBuffers;
96+
mBuffers = null;
97+
return new BufferQueue(buffers);
98+
}
99+
}
100+
101+
private BufferQueue(Queue<ByteBuffer> buffers) {
102+
mBuffers = buffers;
103+
for (ByteBuffer buffer : mBuffers) {
104+
buffer.flip();
105+
}
106+
}
107+
108+
/** Returns the response body as a single contiguous buffer. */
109+
public ByteBuffer coalesceToBuffer() {
110+
markCoalesced();
111+
if (mBuffers.size() == 0) {
112+
return ByteBuffer.allocateDirect(0);
113+
} else if (mBuffers.size() == 1) {
114+
return mBuffers.remove();
115+
} else {
116+
int size = 0;
117+
for (ByteBuffer buffer : mBuffers) {
118+
size += buffer.remaining();
119+
}
120+
ByteBuffer result = ByteBuffer.allocateDirect(size);
121+
while (!mBuffers.isEmpty()) {
122+
result.put(mBuffers.remove());
123+
}
124+
result.flip();
125+
return result;
126+
}
127+
}
128+
129+
private void markCoalesced() {
130+
if (!mIsCoalesced.compareAndSet(false, true)) {
131+
throw new IllegalStateException("This BufferQueue has already been consumed");
132+
}
133+
}
134+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.bumptech.glide.integration.cronet;
2+
3+
import java.nio.ByteBuffer;
4+
5+
/**
6+
* Parses a {@link java.nio.ByteBuffer} to a particular data type.
7+
*
8+
* @param <T> The type of data to parse the buffer to.
9+
*/
10+
interface ByteBufferParser<T> {
11+
/** Returns the required type of data parsed from the given {@link ByteBuffer}. */
12+
T parse(ByteBuffer byteBuffer);
13+
/** Returns the {@link Class} of the data that will be parsed from {@link ByteBuffer}s. */
14+
Class<T> getDataClass();
15+
}

0 commit comments

Comments
 (0)