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 com.google.common.annotations.VisibleForTesting; 24 import com.google.common.base.Preconditions; 25 import com.google.common.util.concurrent.MoreExecutors; 26 import io.grpc.Attributes; 27 import io.grpc.ExperimentalApi; 28 import io.grpc.NameResolver; 29 import io.grpc.internal.AbstractManagedChannelImplBuilder; 30 import io.grpc.internal.ClientTransportFactory; 31 import io.grpc.internal.ConnectionClientTransport; 32 import io.grpc.internal.GrpcUtil; 33 import io.grpc.internal.ProxyParameters; 34 import io.grpc.internal.SharedResourceHolder; 35 import io.grpc.internal.TransportTracer; 36 import java.net.InetSocketAddress; 37 import java.net.SocketAddress; 38 import java.util.concurrent.Executor; 39 import java.util.concurrent.ScheduledExecutorService; 40 import javax.annotation.Nullable; 41 import org.chromium.net.BidirectionalStream; 42 import org.chromium.net.CronetEngine; 43 import org.chromium.net.ExperimentalBidirectionalStream; 44 import org.chromium.net.ExperimentalCronetEngine; 45 46 /** Convenience class for building channels with the cronet transport. */ 47 @ExperimentalApi("There is no plan to make this API stable, given transport API instability") 48 public final class CronetChannelBuilder extends 49 AbstractManagedChannelImplBuilder<CronetChannelBuilder> { 50 51 /** BidirectionalStream.Builder factory used for getting the gRPC BidirectionalStream. */ 52 public static abstract class StreamBuilderFactory { newBidirectionalStreamBuilder( String url, BidirectionalStream.Callback callback, Executor executor)53 public abstract BidirectionalStream.Builder newBidirectionalStreamBuilder( 54 String url, BidirectionalStream.Callback callback, Executor executor); 55 } 56 57 /** Creates a new builder for the given server host, port and CronetEngine. */ forAddress(String host, int port, CronetEngine cronetEngine)58 public static CronetChannelBuilder forAddress(String host, int port, CronetEngine cronetEngine) { 59 Preconditions.checkNotNull(cronetEngine, "cronetEngine"); 60 return new CronetChannelBuilder(host, port, cronetEngine); 61 } 62 63 /** 64 * Always fails. Call {@link #forAddress(String, int, CronetEngine)} instead. 65 */ forTarget(String target)66 public static CronetChannelBuilder forTarget(String target) { 67 throw new UnsupportedOperationException("call forAddress() instead"); 68 } 69 70 /** 71 * Always fails. Call {@link #forAddress(String, int, CronetEngine)} instead. 72 */ forAddress(String name, int port)73 public static CronetChannelBuilder forAddress(String name, int port) { 74 throw new UnsupportedOperationException("call forAddress(String, int, CronetEngine) instead"); 75 } 76 77 @Nullable 78 private ScheduledExecutorService scheduledExecutorService; 79 80 private final CronetEngine cronetEngine; 81 82 private boolean alwaysUsePut = false; 83 84 private int maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE; 85 86 private boolean trafficStatsTagSet; 87 private int trafficStatsTag; 88 private boolean trafficStatsUidSet; 89 private int trafficStatsUid; 90 CronetChannelBuilder(String host, int port, CronetEngine cronetEngine)91 private CronetChannelBuilder(String host, int port, CronetEngine cronetEngine) { 92 super( 93 InetSocketAddress.createUnresolved(host, port), 94 GrpcUtil.authorityFromHostAndPort(host, port)); 95 this.cronetEngine = Preconditions.checkNotNull(cronetEngine, "cronetEngine"); 96 } 97 98 /** 99 * Sets the maximum message size allowed to be received on the channel. If not called, 100 * defaults to {@link io.grpc.internal.GrpcUtil#DEFAULT_MAX_MESSAGE_SIZE}. 101 */ maxMessageSize(int maxMessageSize)102 public final CronetChannelBuilder maxMessageSize(int maxMessageSize) { 103 checkArgument(maxMessageSize >= 0, "maxMessageSize must be >= 0"); 104 this.maxMessageSize = maxMessageSize; 105 return this; 106 } 107 108 /** 109 * Sets the Cronet channel to always use PUT instead of POST. Defaults to false. 110 */ alwaysUsePut(boolean enable)111 public final CronetChannelBuilder alwaysUsePut(boolean enable) { 112 this.alwaysUsePut = enable; 113 return this; 114 } 115 116 /** 117 * Not supported for building cronet channel. 118 */ 119 @Override usePlaintext(boolean skipNegotiation)120 public final CronetChannelBuilder usePlaintext(boolean skipNegotiation) { 121 throw new IllegalArgumentException("Plaintext not currently supported"); 122 } 123 124 /** 125 * Sets {@link android.net.TrafficStats} tag to use when accounting socket traffic caused by this 126 * channel. See {@link android.net.TrafficStats} for more information. If no tag is set (e.g. this 127 * method isn't called), then Android accounts for the socket traffic caused by this channel as if 128 * the tag value were set to 0. 129 * 130 * <p><b>NOTE:</b>Setting a tag disallows sharing of sockets with channels with other tags, which 131 * may adversely effect performance by prohibiting connection sharing. In other words use of 132 * multiplexed sockets (e.g. HTTP/2 and QUIC) will only be allowed if all channels have the same 133 * socket tag. 134 * 135 * @param tag the tag value used to when accounting for socket traffic caused by this channel. 136 * Tags between 0xFFFFFF00 and 0xFFFFFFFF are reserved and used internally by system services 137 * like {@link android.app.DownloadManager} when performing traffic on behalf of an 138 * application. 139 * @return the builder to facilitate chaining. 140 */ setTrafficStatsTag(int tag)141 public final CronetChannelBuilder setTrafficStatsTag(int tag) { 142 trafficStatsTagSet = true; 143 trafficStatsTag = tag; 144 return this; 145 } 146 147 /** 148 * Sets specific UID to use when accounting socket traffic caused by this channel. See {@link 149 * android.net.TrafficStats} for more information. Designed for use when performing an operation 150 * on behalf of another application. Caller must hold {@link 151 * android.Manifest.permission#MODIFY_NETWORK_ACCOUNTING} permission. By default traffic is 152 * attributed to UID of caller. 153 * 154 * <p><b>NOTE:</b>Setting a UID disallows sharing of sockets with channels with other UIDs, which 155 * may adversely effect performance by prohibiting connection sharing. In other words use of 156 * multiplexed sockets (e.g. HTTP/2 and QUIC) will only be allowed if all channels have the same 157 * UID set. 158 * 159 * @param uid the UID to attribute socket traffic caused by this channel. 160 * @return the builder to facilitate chaining. 161 */ setTrafficStatsUid(int uid)162 public final CronetChannelBuilder setTrafficStatsUid(int uid) { 163 trafficStatsUidSet = true; 164 trafficStatsUid = uid; 165 return this; 166 } 167 168 /** 169 * Provides a custom scheduled executor service. 170 * 171 * <p>It's an optional parameter. If the user has not provided a scheduled executor service when 172 * the channel is built, the builder will use a static cached thread pool. 173 * 174 * @return this 175 * 176 * @since 1.12.0 177 */ scheduledExecutorService( ScheduledExecutorService scheduledExecutorService)178 public final CronetChannelBuilder scheduledExecutorService( 179 ScheduledExecutorService scheduledExecutorService) { 180 this.scheduledExecutorService = 181 checkNotNull(scheduledExecutorService, "scheduledExecutorService"); 182 return this; 183 } 184 185 @Override buildTransportFactory()186 protected final ClientTransportFactory buildTransportFactory() { 187 return new CronetTransportFactory( 188 new TaggingStreamFactory( 189 cronetEngine, trafficStatsTagSet, trafficStatsTag, trafficStatsUidSet, trafficStatsUid), 190 MoreExecutors.directExecutor(), 191 scheduledExecutorService, 192 maxMessageSize, 193 alwaysUsePut, 194 transportTracerFactory.create()); 195 } 196 197 @Override getNameResolverParams()198 protected Attributes getNameResolverParams() { 199 return Attributes.newBuilder() 200 .set(NameResolver.Factory.PARAMS_DEFAULT_PORT, GrpcUtil.DEFAULT_PORT_SSL).build(); 201 } 202 203 @VisibleForTesting 204 static class CronetTransportFactory implements ClientTransportFactory { 205 private final ScheduledExecutorService timeoutService; 206 private final Executor executor; 207 private final int maxMessageSize; 208 private final boolean alwaysUsePut; 209 private final StreamBuilderFactory streamFactory; 210 private final TransportTracer transportTracer; 211 private final boolean usingSharedScheduler; 212 CronetTransportFactory( StreamBuilderFactory streamFactory, Executor executor, @Nullable ScheduledExecutorService timeoutService, int maxMessageSize, boolean alwaysUsePut, TransportTracer transportTracer)213 private CronetTransportFactory( 214 StreamBuilderFactory streamFactory, 215 Executor executor, 216 @Nullable ScheduledExecutorService timeoutService, 217 int maxMessageSize, 218 boolean alwaysUsePut, 219 TransportTracer transportTracer) { 220 usingSharedScheduler = timeoutService == null; 221 this.timeoutService = usingSharedScheduler 222 ? SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE) : timeoutService; 223 this.maxMessageSize = maxMessageSize; 224 this.alwaysUsePut = alwaysUsePut; 225 this.streamFactory = streamFactory; 226 this.executor = Preconditions.checkNotNull(executor, "executor"); 227 this.transportTracer = Preconditions.checkNotNull(transportTracer, "transportTracer"); 228 } 229 230 @Override newClientTransport( SocketAddress addr, ClientTransportOptions options)231 public ConnectionClientTransport newClientTransport( 232 SocketAddress addr, ClientTransportOptions options) { 233 InetSocketAddress inetSocketAddr = (InetSocketAddress) addr; 234 return new CronetClientTransport(streamFactory, inetSocketAddr, options.getAuthority(), 235 options.getUserAgent(), executor, maxMessageSize, alwaysUsePut, transportTracer); 236 } 237 238 @Override getScheduledExecutorService()239 public ScheduledExecutorService getScheduledExecutorService() { 240 return timeoutService; 241 } 242 243 @Override close()244 public void close() { 245 if (usingSharedScheduler) { 246 SharedResourceHolder.release(GrpcUtil.TIMER_SERVICE, timeoutService); 247 } 248 } 249 } 250 251 /** 252 * StreamBuilderFactory impl that applies TrafficStats tags to stream builders that are produced. 253 */ 254 private static class TaggingStreamFactory extends StreamBuilderFactory { 255 private final CronetEngine cronetEngine; 256 private final boolean trafficStatsTagSet; 257 private final int trafficStatsTag; 258 private final boolean trafficStatsUidSet; 259 private final int trafficStatsUid; 260 TaggingStreamFactory( CronetEngine cronetEngine, boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet, int trafficStatsUid)261 TaggingStreamFactory( 262 CronetEngine cronetEngine, 263 boolean trafficStatsTagSet, 264 int trafficStatsTag, 265 boolean trafficStatsUidSet, 266 int trafficStatsUid) { 267 this.cronetEngine = cronetEngine; 268 this.trafficStatsTagSet = trafficStatsTagSet; 269 this.trafficStatsTag = trafficStatsTag; 270 this.trafficStatsUidSet = trafficStatsUidSet; 271 this.trafficStatsUid = trafficStatsUid; 272 } 273 274 @Override newBidirectionalStreamBuilder( String url, BidirectionalStream.Callback callback, Executor executor)275 public BidirectionalStream.Builder newBidirectionalStreamBuilder( 276 String url, BidirectionalStream.Callback callback, Executor executor) { 277 ExperimentalBidirectionalStream.Builder builder = 278 ((ExperimentalCronetEngine) cronetEngine) 279 .newBidirectionalStreamBuilder(url, callback, executor); 280 if (trafficStatsTagSet) { 281 builder.setTrafficStatsTag(trafficStatsTag); 282 } 283 if (trafficStatsUidSet) { 284 builder.setTrafficStatsUid(trafficStatsUid); 285 } 286 return builder; 287 } 288 } 289 } 290