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