/
CronetChannelBuilder.java
361 lines (327 loc) · 14 KB
/
CronetChannelBuilder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
/*
* Copyright 2016 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.cronet;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
import android.util.Log;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.MoreExecutors;
import io.grpc.ChannelLogger;
import io.grpc.ExperimentalApi;
import io.grpc.internal.AbstractManagedChannelImplBuilder;
import io.grpc.internal.ClientTransportFactory;
import io.grpc.internal.ConnectionClientTransport;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.SharedResourceHolder;
import io.grpc.internal.TransportTracer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.Nullable;
import org.chromium.net.BidirectionalStream;
import org.chromium.net.CronetEngine;
import org.chromium.net.ExperimentalBidirectionalStream;
import org.chromium.net.ExperimentalCronetEngine;
/** Convenience class for building channels with the cronet transport. */
@ExperimentalApi("There is no plan to make this API stable, given transport API instability")
public final class CronetChannelBuilder extends
AbstractManagedChannelImplBuilder<CronetChannelBuilder> {
private static final String LOG_TAG = "CronetChannelBuilder";
/** BidirectionalStream.Builder factory used for getting the gRPC BidirectionalStream. */
public static abstract class StreamBuilderFactory {
public abstract BidirectionalStream.Builder newBidirectionalStreamBuilder(
String url, BidirectionalStream.Callback callback, Executor executor);
}
/** Creates a new builder for the given server host, port and CronetEngine. */
public static CronetChannelBuilder forAddress(String host, int port, CronetEngine cronetEngine) {
Preconditions.checkNotNull(cronetEngine, "cronetEngine");
return new CronetChannelBuilder(host, port, cronetEngine);
}
/**
* Always fails. Call {@link #forAddress(String, int, CronetEngine)} instead.
*/
public static CronetChannelBuilder forTarget(String target) {
throw new UnsupportedOperationException("call forAddress() instead");
}
/**
* Always fails. Call {@link #forAddress(String, int, CronetEngine)} instead.
*/
public static CronetChannelBuilder forAddress(String name, int port) {
throw new UnsupportedOperationException("call forAddress(String, int, CronetEngine) instead");
}
@Nullable
private ScheduledExecutorService scheduledExecutorService;
private final CronetEngine cronetEngine;
private boolean alwaysUsePut = false;
private int maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE;
/**
* If true, indicates that the transport may use the GET method for RPCs, and may include the
* request body in the query params.
*/
private final boolean useGetForSafeMethods = false;
/**
* If true, indicates that the transport may use the PUT method for RPCs.
*/
private final boolean usePutForIdempotentMethods = false;
private boolean trafficStatsTagSet;
private int trafficStatsTag;
private boolean trafficStatsUidSet;
private int trafficStatsUid;
private CronetChannelBuilder(String host, int port, CronetEngine cronetEngine) {
super(
InetSocketAddress.createUnresolved(host, port),
GrpcUtil.authorityFromHostAndPort(host, port));
this.cronetEngine = Preconditions.checkNotNull(cronetEngine, "cronetEngine");
}
/**
* Sets the maximum message size allowed to be received on the channel. If not called,
* defaults to {@link io.grpc.internal.GrpcUtil#DEFAULT_MAX_MESSAGE_SIZE}.
*/
public final CronetChannelBuilder maxMessageSize(int maxMessageSize) {
checkArgument(maxMessageSize >= 0, "maxMessageSize must be >= 0");
this.maxMessageSize = maxMessageSize;
return this;
}
/**
* Sets the Cronet channel to always use PUT instead of POST. Defaults to false.
*/
public final CronetChannelBuilder alwaysUsePut(boolean enable) {
this.alwaysUsePut = enable;
return this;
}
/**
* Sets {@link android.net.TrafficStats} tag to use when accounting socket traffic caused by this
* channel. See {@link android.net.TrafficStats} for more information. If no tag is set (e.g. this
* method isn't called), then Android accounts for the socket traffic caused by this channel as if
* the tag value were set to 0.
*
* <p><b>NOTE:</b>Setting a tag disallows sharing of sockets with channels with other tags, which
* may adversely effect performance by prohibiting connection sharing. In other words use of
* multiplexed sockets (e.g. HTTP/2 and QUIC) will only be allowed if all channels have the same
* socket tag.
*
* @param tag the tag value used to when accounting for socket traffic caused by this channel.
* Tags between 0xFFFFFF00 and 0xFFFFFFFF are reserved and used internally by system services
* like {@link android.app.DownloadManager} when performing traffic on behalf of an
* application.
* @return the builder to facilitate chaining.
*/
final CronetChannelBuilder setTrafficStatsTag(int tag) {
trafficStatsTagSet = true;
trafficStatsTag = tag;
return this;
}
/**
* Sets specific UID to use when accounting socket traffic caused by this channel. See {@link
* android.net.TrafficStats} for more information. Designed for use when performing an operation
* on behalf of another application. Caller must hold {@link
* android.Manifest.permission#MODIFY_NETWORK_ACCOUNTING} permission. By default traffic is
* attributed to UID of caller.
*
* <p><b>NOTE:</b>Setting a UID disallows sharing of sockets with channels with other UIDs, which
* may adversely effect performance by prohibiting connection sharing. In other words use of
* multiplexed sockets (e.g. HTTP/2 and QUIC) will only be allowed if all channels have the same
* UID set.
*
* @param uid the UID to attribute socket traffic caused by this channel.
* @return the builder to facilitate chaining.
*/
final CronetChannelBuilder setTrafficStatsUid(int uid) {
trafficStatsUidSet = true;
trafficStatsUid = uid;
return this;
}
/**
* Provides a custom scheduled executor service.
*
* <p>It's an optional parameter. If the user has not provided a scheduled executor service when
* the channel is built, the builder will use a static cached thread pool.
*
* @return this
*
* @since 1.12.0
*/
public final CronetChannelBuilder scheduledExecutorService(
ScheduledExecutorService scheduledExecutorService) {
this.scheduledExecutorService =
checkNotNull(scheduledExecutorService, "scheduledExecutorService");
return this;
}
@Override
protected final ClientTransportFactory buildTransportFactory() {
return new CronetTransportFactory(
new TaggingStreamFactory(
cronetEngine, trafficStatsTagSet, trafficStatsTag, trafficStatsUidSet, trafficStatsUid),
MoreExecutors.directExecutor(),
scheduledExecutorService,
maxMessageSize,
alwaysUsePut,
transportTracerFactory.create(),
useGetForSafeMethods,
usePutForIdempotentMethods);
}
@VisibleForTesting
static class CronetTransportFactory implements ClientTransportFactory {
private final ScheduledExecutorService timeoutService;
private final Executor executor;
private final int maxMessageSize;
private final boolean alwaysUsePut;
private final StreamBuilderFactory streamFactory;
private final TransportTracer transportTracer;
private final boolean usingSharedScheduler;
private final boolean useGetForSafeMethods;
private final boolean usePutForIdempotentMethods;
private CronetTransportFactory(
StreamBuilderFactory streamFactory,
Executor executor,
@Nullable ScheduledExecutorService timeoutService,
int maxMessageSize,
boolean alwaysUsePut,
TransportTracer transportTracer,
boolean useGetForSafeMethods,
boolean usePutForIdempotentMethods) {
usingSharedScheduler = timeoutService == null;
this.timeoutService = usingSharedScheduler
? SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE) : timeoutService;
this.maxMessageSize = maxMessageSize;
this.alwaysUsePut = alwaysUsePut;
this.streamFactory = streamFactory;
this.executor = Preconditions.checkNotNull(executor, "executor");
this.transportTracer = Preconditions.checkNotNull(transportTracer, "transportTracer");
this.useGetForSafeMethods = useGetForSafeMethods;
this.usePutForIdempotentMethods = usePutForIdempotentMethods;
}
@Override
public ConnectionClientTransport newClientTransport(
SocketAddress addr, ClientTransportOptions options, ChannelLogger channelLogger) {
InetSocketAddress inetSocketAddr = (InetSocketAddress) addr;
return new CronetClientTransport(streamFactory, inetSocketAddr, options.getAuthority(),
options.getUserAgent(), options.getEagAttributes(), executor, maxMessageSize,
alwaysUsePut, transportTracer, useGetForSafeMethods, usePutForIdempotentMethods);
}
@Override
public ScheduledExecutorService getScheduledExecutorService() {
return timeoutService;
}
@Override
public void close() {
if (usingSharedScheduler) {
SharedResourceHolder.release(GrpcUtil.TIMER_SERVICE, timeoutService);
}
}
}
/**
* StreamBuilderFactory impl that applies TrafficStats tags to stream builders that are produced.
*/
private static class TaggingStreamFactory extends StreamBuilderFactory {
private static volatile boolean loadSetTrafficStatsTagAttempted;
private static volatile boolean loadSetTrafficStatsUidAttempted;
private static volatile Method setTrafficStatsTagMethod;
private static volatile Method setTrafficStatsUidMethod;
private final CronetEngine cronetEngine;
private final boolean trafficStatsTagSet;
private final int trafficStatsTag;
private final boolean trafficStatsUidSet;
private final int trafficStatsUid;
TaggingStreamFactory(
CronetEngine cronetEngine,
boolean trafficStatsTagSet,
int trafficStatsTag,
boolean trafficStatsUidSet,
int trafficStatsUid) {
this.cronetEngine = cronetEngine;
this.trafficStatsTagSet = trafficStatsTagSet;
this.trafficStatsTag = trafficStatsTag;
this.trafficStatsUidSet = trafficStatsUidSet;
this.trafficStatsUid = trafficStatsUid;
}
@Override
public BidirectionalStream.Builder newBidirectionalStreamBuilder(
String url, BidirectionalStream.Callback callback, Executor executor) {
ExperimentalBidirectionalStream.Builder builder =
((ExperimentalCronetEngine) cronetEngine)
.newBidirectionalStreamBuilder(url, callback, executor);
if (trafficStatsTagSet) {
setTrafficStatsTag(builder, trafficStatsTag);
}
if (trafficStatsUidSet) {
setTrafficStatsUid(builder, trafficStatsUid);
}
return builder;
}
private static void setTrafficStatsTag(ExperimentalBidirectionalStream.Builder builder,
int tag) {
if (!loadSetTrafficStatsTagAttempted) {
synchronized (TaggingStreamFactory.class) {
if (!loadSetTrafficStatsTagAttempted) {
try {
setTrafficStatsTagMethod = ExperimentalBidirectionalStream.Builder.class
.getMethod("setTrafficStatsTag", int.class);
} catch (NoSuchMethodException e) {
Log.w(LOG_TAG,
"Failed to load method ExperimentalBidirectionalStream.Builder.setTrafficStatsTag",
e);
} finally {
loadSetTrafficStatsTagAttempted = true;
}
}
}
}
if (setTrafficStatsTagMethod != null) {
try {
setTrafficStatsTagMethod.invoke(builder, tag);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause() == null ? e.getTargetException() : e.getCause());
} catch (IllegalAccessException e) {
Log.w(LOG_TAG, "Failed to set traffic stats tag: " + tag, e);
}
}
}
private static void setTrafficStatsUid(ExperimentalBidirectionalStream.Builder builder,
int uid) {
if (!loadSetTrafficStatsUidAttempted) {
synchronized (TaggingStreamFactory.class) {
if (!loadSetTrafficStatsUidAttempted) {
try {
setTrafficStatsUidMethod = ExperimentalBidirectionalStream.Builder.class
.getMethod("setTrafficStatsUid", int.class);
} catch (NoSuchMethodException e) {
Log.w(LOG_TAG,
"Failed to load method ExperimentalBidirectionalStream.Builder.setTrafficStatsUid",
e);
} finally {
loadSetTrafficStatsUidAttempted = true;
}
}
}
}
if (setTrafficStatsUidMethod != null) {
try {
setTrafficStatsUidMethod.invoke(builder, uid);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause() == null ? e.getTargetException() : e.getCause());
} catch (IllegalAccessException e) {
Log.w(LOG_TAG, "Failed to set traffic stats uid: " + uid, e);
}
}
}
}
}