1 /*
2  * Copyright 2014 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.okhttp;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
21 import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIME_NANOS;
22 import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED;
23 
24 import com.google.common.annotations.VisibleForTesting;
25 import com.google.common.base.Preconditions;
26 import io.grpc.Attributes;
27 import io.grpc.ExperimentalApi;
28 import io.grpc.Internal;
29 import io.grpc.NameResolver;
30 import io.grpc.internal.AbstractManagedChannelImplBuilder;
31 import io.grpc.internal.AtomicBackoff;
32 import io.grpc.internal.ClientTransportFactory;
33 import io.grpc.internal.ConnectionClientTransport;
34 import io.grpc.internal.GrpcUtil;
35 import io.grpc.internal.KeepAliveManager;
36 import io.grpc.internal.SharedResourceHolder;
37 import io.grpc.internal.SharedResourceHolder.Resource;
38 import io.grpc.internal.TransportTracer;
39 import io.grpc.okhttp.internal.CipherSuite;
40 import io.grpc.okhttp.internal.ConnectionSpec;
41 import io.grpc.okhttp.internal.Platform;
42 import io.grpc.okhttp.internal.TlsVersion;
43 import java.net.InetSocketAddress;
44 import java.net.SocketAddress;
45 import java.security.GeneralSecurityException;
46 import java.security.KeyStore;
47 import java.security.SecureRandom;
48 import java.util.concurrent.Executor;
49 import java.util.concurrent.ExecutorService;
50 import java.util.concurrent.Executors;
51 import java.util.concurrent.ScheduledExecutorService;
52 import java.util.concurrent.TimeUnit;
53 import javax.annotation.Nullable;
54 import javax.net.ssl.HostnameVerifier;
55 import javax.net.ssl.SSLContext;
56 import javax.net.ssl.SSLSocketFactory;
57 import javax.net.ssl.TrustManagerFactory;
58 
59 /** Convenience class for building channels with the OkHttp transport. */
60 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1785")
61 public class OkHttpChannelBuilder extends
62         AbstractManagedChannelImplBuilder<OkHttpChannelBuilder> {
63 
64   /** Identifies the negotiation used for starting up HTTP/2. */
65   private enum NegotiationType {
66     /** Uses TLS ALPN/NPN negotiation, assumes an SSL connection. */
67     TLS,
68 
69     /**
70      * Just assume the connection is plaintext (non-SSL) and the remote endpoint supports HTTP/2
71      * directly without an upgrade.
72      */
73     PLAINTEXT
74   }
75 
76   /**
77    * ConnectionSpec closely matching the default configuration that could be used as a basis for
78    * modification.
79    *
80    * <p>Since this field is the only reference in gRPC to ConnectionSpec that may not be ProGuarded,
81    * we are removing the field to reduce method count. We've been unable to find any existing users
82    * of the field, and any such user would highly likely at least be changing the cipher suites,
83    * which is sort of the only part that's non-obvious. Any existing user should instead create
84    * their own spec from scratch or base it off ConnectionSpec.MODERN_TLS if believed to be
85    * necessary. If this was providing you with value and don't want to see it removed, open a GitHub
86    * issue to discuss keeping it.
87    *
88    * @deprecated Deemed of little benefit and users weren't using it. Just define one yourself
89    */
90   @Deprecated
91   public static final com.squareup.okhttp.ConnectionSpec DEFAULT_CONNECTION_SPEC =
92       new com.squareup.okhttp.ConnectionSpec.Builder(com.squareup.okhttp.ConnectionSpec.MODERN_TLS)
93           .cipherSuites(
94               // The following items should be sync with Netty's Http2SecurityUtil.CIPHERS.
95               com.squareup.okhttp.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
96               com.squareup.okhttp.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
97               com.squareup.okhttp.CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
98               com.squareup.okhttp.CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
99               com.squareup.okhttp.CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
100               com.squareup.okhttp.CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
101               com.squareup.okhttp.CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
102               com.squareup.okhttp.CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384)
103           .tlsVersions(com.squareup.okhttp.TlsVersion.TLS_1_2)
104           .supportsTlsExtensions(true)
105           .build();
106 
107   @VisibleForTesting
108   static final ConnectionSpec INTERNAL_DEFAULT_CONNECTION_SPEC =
109       new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
110           .cipherSuites(
111               // The following items should be sync with Netty's Http2SecurityUtil.CIPHERS.
112               CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
113               CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
114               CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
115               CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
116               CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
117               CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
118               CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
119               CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384)
120           .tlsVersions(TlsVersion.TLS_1_2)
121           .supportsTlsExtensions(true)
122           .build();
123 
124   private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L);
125   private static final Resource<ExecutorService> SHARED_EXECUTOR =
126       new Resource<ExecutorService>() {
127         @Override
128         public ExecutorService create() {
129           return Executors.newCachedThreadPool(GrpcUtil.getThreadFactory("grpc-okhttp-%d", true));
130         }
131 
132         @Override
133         public void close(ExecutorService executor) {
134           executor.shutdown();
135         }
136       };
137 
138   /** Creates a new builder for the given server host and port. */
forAddress(String host, int port)139   public static OkHttpChannelBuilder forAddress(String host, int port) {
140     return new OkHttpChannelBuilder(host, port);
141   }
142 
143   /**
144    * Creates a new builder for the given target that will be resolved by
145    * {@link io.grpc.NameResolver}.
146    */
forTarget(String target)147   public static OkHttpChannelBuilder forTarget(String target) {
148     return new OkHttpChannelBuilder(target);
149   }
150 
151   private Executor transportExecutor;
152   private ScheduledExecutorService scheduledExecutorService;
153 
154   private SSLSocketFactory sslSocketFactory;
155   private HostnameVerifier hostnameVerifier;
156   private ConnectionSpec connectionSpec = INTERNAL_DEFAULT_CONNECTION_SPEC;
157   private NegotiationType negotiationType = NegotiationType.TLS;
158   private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
159   private long keepAliveTimeoutNanos = DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
160   private boolean keepAliveWithoutCalls;
161 
OkHttpChannelBuilder(String host, int port)162   protected OkHttpChannelBuilder(String host, int port) {
163     this(GrpcUtil.authorityFromHostAndPort(host, port));
164   }
165 
OkHttpChannelBuilder(String target)166   private OkHttpChannelBuilder(String target) {
167     super(target);
168   }
169 
170   @VisibleForTesting
setTransportTracerFactory( TransportTracer.Factory transportTracerFactory)171   final OkHttpChannelBuilder setTransportTracerFactory(
172       TransportTracer.Factory transportTracerFactory) {
173     this.transportTracerFactory = transportTracerFactory;
174     return this;
175   }
176 
177   /**
178    * Override the default executor necessary for internal transport use.
179    *
180    * <p>The channel does not take ownership of the given executor. It is the caller' responsibility
181    * to shutdown the executor when appropriate.
182    */
transportExecutor(@ullable Executor transportExecutor)183   public final OkHttpChannelBuilder transportExecutor(@Nullable Executor transportExecutor) {
184     this.transportExecutor = transportExecutor;
185     return this;
186   }
187 
188   /**
189    * Sets the negotiation type for the HTTP/2 connection.
190    *
191    * <p>If TLS is enabled a default {@link SSLSocketFactory} is created using the best
192    * {@link java.security.Provider} available and is NOT based on
193    * {@link SSLSocketFactory#getDefault}. To more precisely control the TLS configuration call
194    * {@link #sslSocketFactory} to override the socket factory used.
195    *
196    * <p>Default: <code>TLS</code>
197    *
198    * @deprecated use {@link #usePlaintext()} or {@link #useTransportSecurity()} instead.
199    */
200   @Deprecated
negotiationType(io.grpc.okhttp.NegotiationType type)201   public final OkHttpChannelBuilder negotiationType(io.grpc.okhttp.NegotiationType type) {
202     Preconditions.checkNotNull(type, "type");
203     switch (type) {
204       case TLS:
205         negotiationType = NegotiationType.TLS;
206         break;
207       case PLAINTEXT:
208         negotiationType = NegotiationType.PLAINTEXT;
209         break;
210       default:
211         throw new AssertionError("Unknown negotiation type: " + type);
212     }
213     return this;
214   }
215 
216   /**
217    * Enable keepalive with default delay and timeout.
218    *
219    * @deprecated Use {@link #keepAliveTime} instead
220    */
221   @Deprecated
enableKeepAlive(boolean enable)222   public final OkHttpChannelBuilder enableKeepAlive(boolean enable) {
223     if (enable) {
224       return keepAliveTime(DEFAULT_KEEPALIVE_TIME_NANOS, TimeUnit.NANOSECONDS);
225     } else {
226       return keepAliveTime(KEEPALIVE_TIME_NANOS_DISABLED, TimeUnit.NANOSECONDS);
227     }
228   }
229 
230   /**
231    * Enable keepalive with custom delay and timeout.
232    *
233    * @deprecated Use {@link #keepAliveTime} and {@link #keepAliveTimeout} instead
234    */
235   @Deprecated
enableKeepAlive(boolean enable, long keepAliveTime, TimeUnit delayUnit, long keepAliveTimeout, TimeUnit timeoutUnit)236   public final OkHttpChannelBuilder enableKeepAlive(boolean enable, long keepAliveTime,
237       TimeUnit delayUnit, long keepAliveTimeout, TimeUnit timeoutUnit) {
238     if (enable) {
239       return keepAliveTime(keepAliveTime, delayUnit)
240           .keepAliveTimeout(keepAliveTimeout, timeoutUnit);
241     } else {
242       return keepAliveTime(KEEPALIVE_TIME_NANOS_DISABLED, TimeUnit.NANOSECONDS);
243     }
244   }
245 
246   /**
247    * {@inheritDoc}
248    *
249    * @since 1.3.0
250    */
251   @Override
keepAliveTime(long keepAliveTime, TimeUnit timeUnit)252   public OkHttpChannelBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
253     Preconditions.checkArgument(keepAliveTime > 0L, "keepalive time must be positive");
254     keepAliveTimeNanos = timeUnit.toNanos(keepAliveTime);
255     keepAliveTimeNanos = KeepAliveManager.clampKeepAliveTimeInNanos(keepAliveTimeNanos);
256     if (keepAliveTimeNanos >= AS_LARGE_AS_INFINITE) {
257       // Bump keepalive time to infinite. This disables keepalive.
258       keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
259     }
260     return this;
261   }
262 
263   /**
264    * {@inheritDoc}
265    *
266    * @since 1.3.0
267    */
268   @Override
keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit)269   public OkHttpChannelBuilder keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
270     Preconditions.checkArgument(keepAliveTimeout > 0L, "keepalive timeout must be positive");
271     keepAliveTimeoutNanos = timeUnit.toNanos(keepAliveTimeout);
272     keepAliveTimeoutNanos = KeepAliveManager.clampKeepAliveTimeoutInNanos(keepAliveTimeoutNanos);
273     return this;
274   }
275 
276   /**
277    * {@inheritDoc}
278    *
279    * @since 1.3.0
280    * @see #keepAliveTime(long, TimeUnit)
281    */
282   @Override
keepAliveWithoutCalls(boolean enable)283   public OkHttpChannelBuilder keepAliveWithoutCalls(boolean enable) {
284     keepAliveWithoutCalls = enable;
285     return this;
286   }
287 
288   /**
289    * Override the default {@link SSLSocketFactory} and enable TLS negotiation.
290    */
sslSocketFactory(SSLSocketFactory factory)291   public final OkHttpChannelBuilder sslSocketFactory(SSLSocketFactory factory) {
292     this.sslSocketFactory = factory;
293     negotiationType = NegotiationType.TLS;
294     return this;
295   }
296 
297   /**
298    * Set the hostname verifier to use when using TLS negotiation. The hostnameVerifier is only used
299    * if using TLS negotiation. If the hostname verifier is not set, a default hostname verifier is
300    * used.
301    *
302    * <p>Be careful when setting a custom hostname verifier! By setting a non-null value, you are
303    * replacing all default verification behavior. If the hostname verifier you supply does not
304    * effectively supply the same checks, you may be removing the security assurances that TLS aims
305    * to provide.</p>
306    *
307    * <p>This method should not be used to avoid hostname verification, even during testing, since
308    * {@link #overrideAuthority} is a safer alternative as it does not disable any security checks.
309    * </p>
310    *
311    * @see io.grpc.okhttp.internal.OkHostnameVerifier
312    *
313    * @since 1.6.0
314    * @return this
315    *
316    */
hostnameVerifier(@ullable HostnameVerifier hostnameVerifier)317   public final OkHttpChannelBuilder hostnameVerifier(@Nullable HostnameVerifier hostnameVerifier) {
318     this.hostnameVerifier = hostnameVerifier;
319     return this;
320   }
321 
322   /**
323    * For secure connection, provides a ConnectionSpec to specify Cipher suite and
324    * TLS versions.
325    *
326    * <p>By default a modern, HTTP/2-compatible spec will be used.
327    *
328    * <p>This method is only used when building a secure connection. For plaintext
329    * connection, use {@link #usePlaintext()} instead.
330    *
331    * @throws IllegalArgumentException
332    *         If {@code connectionSpec} is not with TLS
333    */
connectionSpec( com.squareup.okhttp.ConnectionSpec connectionSpec)334   public final OkHttpChannelBuilder connectionSpec(
335       com.squareup.okhttp.ConnectionSpec connectionSpec) {
336     Preconditions.checkArgument(connectionSpec.isTls(), "plaintext ConnectionSpec is not accepted");
337     this.connectionSpec = Utils.convertSpec(connectionSpec);
338     return this;
339   }
340 
341   /**
342    * Equivalent to using {@link #negotiationType} with {@code PLAINTEXT}.
343    *
344    * @deprecated use {@link #usePlaintext()} instead.
345    */
346   @Override
347   @Deprecated
usePlaintext(boolean skipNegotiation)348   public final OkHttpChannelBuilder usePlaintext(boolean skipNegotiation) {
349     if (skipNegotiation) {
350       negotiationType(io.grpc.okhttp.NegotiationType.PLAINTEXT);
351     } else {
352       throw new IllegalArgumentException("Plaintext negotiation not currently supported");
353     }
354     return this;
355   }
356 
357   /** Sets the negotiation type for the HTTP/2 connection to plaintext. */
358   @Override
usePlaintext()359   public final OkHttpChannelBuilder usePlaintext() {
360     negotiationType = NegotiationType.PLAINTEXT;
361     return this;
362   }
363 
364   /**
365    * Sets the negotiation type for the HTTP/2 connection to TLS (this is the default).
366    *
367    * <p>With TLS enabled, a default {@link SSLSocketFactory} is created using the best {@link
368    * java.security.Provider} available and is NOT based on {@link SSLSocketFactory#getDefault}. To
369    * more precisely control the TLS configuration call {@link #sslSocketFactory} to override the
370    * socket factory used.
371    */
372   @Override
useTransportSecurity()373   public final OkHttpChannelBuilder useTransportSecurity() {
374     negotiationType = NegotiationType.TLS;
375     return this;
376   }
377 
378   /**
379    * Provides a custom scheduled executor service.
380    *
381    * <p>It's an optional parameter. If the user has not provided a scheduled executor service when
382    * the channel is built, the builder will use a static cached thread pool.
383    *
384    * @return this
385    *
386    * @since 1.11.0
387    */
scheduledExecutorService( ScheduledExecutorService scheduledExecutorService)388   public final OkHttpChannelBuilder scheduledExecutorService(
389       ScheduledExecutorService scheduledExecutorService) {
390     this.scheduledExecutorService =
391         checkNotNull(scheduledExecutorService, "scheduledExecutorService");
392     return this;
393   }
394 
395   @Override
396   @Internal
buildTransportFactory()397   protected final ClientTransportFactory buildTransportFactory() {
398     boolean enableKeepAlive = keepAliveTimeNanos != KEEPALIVE_TIME_NANOS_DISABLED;
399     return new OkHttpTransportFactory(transportExecutor, scheduledExecutorService,
400         createSocketFactory(), hostnameVerifier, connectionSpec, maxInboundMessageSize(),
401         enableKeepAlive, keepAliveTimeNanos, keepAliveTimeoutNanos, keepAliveWithoutCalls,
402         transportTracerFactory);
403   }
404 
405   @Override
getNameResolverParams()406   protected Attributes getNameResolverParams() {
407     int defaultPort;
408     switch (negotiationType) {
409       case PLAINTEXT:
410         defaultPort = GrpcUtil.DEFAULT_PORT_PLAINTEXT;
411         break;
412       case TLS:
413         defaultPort = GrpcUtil.DEFAULT_PORT_SSL;
414         break;
415       default:
416         throw new AssertionError(negotiationType + " not handled");
417     }
418     return Attributes.newBuilder()
419         .set(NameResolver.Factory.PARAMS_DEFAULT_PORT, defaultPort).build();
420   }
421 
422   @VisibleForTesting
423   @Nullable
createSocketFactory()424   SSLSocketFactory createSocketFactory() {
425     switch (negotiationType) {
426       case TLS:
427         try {
428           if (sslSocketFactory == null) {
429             SSLContext sslContext;
430             if (GrpcUtil.IS_RESTRICTED_APPENGINE) {
431               // The following auth code circumvents the following AccessControlException:
432               // access denied ("java.util.PropertyPermission" "javax.net.ssl.keyStore" "read")
433               // Conscrypt will attempt to load the default KeyStore if a trust manager is not
434               // provided, which is forbidden on AppEngine
435               sslContext = SSLContext.getInstance("TLS", Platform.get().getProvider());
436               TrustManagerFactory trustManagerFactory =
437                   TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
438               trustManagerFactory.init((KeyStore) null);
439               sslContext.init(
440                   null,
441                   trustManagerFactory.getTrustManagers(),
442                   // Use an algorithm that doesn't need /dev/urandom
443                   SecureRandom.getInstance("SHA1PRNG", Platform.get().getProvider()));
444 
445             } else {
446               sslContext = SSLContext.getInstance("Default", Platform.get().getProvider());
447             }
448             sslSocketFactory = sslContext.getSocketFactory();
449           }
450           return sslSocketFactory;
451         } catch (GeneralSecurityException gse) {
452           throw new RuntimeException("TLS Provider failure", gse);
453         }
454       case PLAINTEXT:
455         return null;
456       default:
457         throw new RuntimeException("Unknown negotiation type: " + negotiationType);
458     }
459   }
460 
461   /**
462    * Creates OkHttp transports. Exposed for internal use, as it should be private.
463    */
464   @Internal
465   static final class OkHttpTransportFactory implements ClientTransportFactory {
466     private final Executor executor;
467     private final boolean usingSharedExecutor;
468     private final boolean usingSharedScheduler;
469     private final TransportTracer.Factory transportTracerFactory;
470     @Nullable
471     private final SSLSocketFactory socketFactory;
472     @Nullable
473     private final HostnameVerifier hostnameVerifier;
474     private final ConnectionSpec connectionSpec;
475     private final int maxMessageSize;
476     private final boolean enableKeepAlive;
477     private final AtomicBackoff keepAliveTimeNanos;
478     private final long keepAliveTimeoutNanos;
479     private final boolean keepAliveWithoutCalls;
480     private final ScheduledExecutorService timeoutService;
481     private boolean closed;
482 
OkHttpTransportFactory(Executor executor, @Nullable ScheduledExecutorService timeoutService, @Nullable SSLSocketFactory socketFactory, @Nullable HostnameVerifier hostnameVerifier, ConnectionSpec connectionSpec, int maxMessageSize, boolean enableKeepAlive, long keepAliveTimeNanos, long keepAliveTimeoutNanos, boolean keepAliveWithoutCalls, TransportTracer.Factory transportTracerFactory)483     private OkHttpTransportFactory(Executor executor,
484         @Nullable ScheduledExecutorService timeoutService,
485         @Nullable SSLSocketFactory socketFactory,
486         @Nullable HostnameVerifier hostnameVerifier,
487         ConnectionSpec connectionSpec,
488         int maxMessageSize,
489         boolean enableKeepAlive,
490         long keepAliveTimeNanos,
491         long keepAliveTimeoutNanos,
492         boolean keepAliveWithoutCalls,
493         TransportTracer.Factory transportTracerFactory) {
494       usingSharedScheduler = timeoutService == null;
495       this.timeoutService = usingSharedScheduler
496           ? SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE) : timeoutService;
497       this.socketFactory = socketFactory;
498       this.hostnameVerifier = hostnameVerifier;
499       this.connectionSpec = connectionSpec;
500       this.maxMessageSize = maxMessageSize;
501       this.enableKeepAlive = enableKeepAlive;
502       this.keepAliveTimeNanos = new AtomicBackoff("keepalive time nanos", keepAliveTimeNanos);
503       this.keepAliveTimeoutNanos = keepAliveTimeoutNanos;
504       this.keepAliveWithoutCalls = keepAliveWithoutCalls;
505 
506       usingSharedExecutor = executor == null;
507       this.transportTracerFactory =
508           Preconditions.checkNotNull(transportTracerFactory, "transportTracerFactory");
509       if (usingSharedExecutor) {
510         // The executor was unspecified, using the shared executor.
511         this.executor = SharedResourceHolder.get(SHARED_EXECUTOR);
512       } else {
513         this.executor = executor;
514       }
515     }
516 
517     @Override
newClientTransport( SocketAddress addr, ClientTransportOptions options)518     public ConnectionClientTransport newClientTransport(
519         SocketAddress addr, ClientTransportOptions options) {
520       if (closed) {
521         throw new IllegalStateException("The transport factory is closed.");
522       }
523       final AtomicBackoff.State keepAliveTimeNanosState = keepAliveTimeNanos.getState();
524       Runnable tooManyPingsRunnable = new Runnable() {
525         @Override
526         public void run() {
527           keepAliveTimeNanosState.backoff();
528         }
529       };
530       InetSocketAddress inetSocketAddr = (InetSocketAddress) addr;
531       OkHttpClientTransport transport = new OkHttpClientTransport(
532           inetSocketAddr,
533           options.getAuthority(),
534           options.getUserAgent(),
535           executor,
536           socketFactory,
537           hostnameVerifier,
538           connectionSpec,
539           maxMessageSize,
540           options.getProxyParameters(),
541           tooManyPingsRunnable,
542           transportTracerFactory.create());
543       if (enableKeepAlive) {
544         transport.enableKeepAlive(
545             true, keepAliveTimeNanosState.get(), keepAliveTimeoutNanos, keepAliveWithoutCalls);
546       }
547       return transport;
548     }
549 
550     @Override
getScheduledExecutorService()551     public ScheduledExecutorService getScheduledExecutorService() {
552       return timeoutService;
553     }
554 
555     @Override
close()556     public void close() {
557       if (closed) {
558         return;
559       }
560       closed = true;
561 
562       if (usingSharedScheduler) {
563         SharedResourceHolder.release(GrpcUtil.TIMER_SERVICE, timeoutService);
564       }
565 
566       if (usingSharedExecutor) {
567         SharedResourceHolder.release(SHARED_EXECUTOR, (ExecutorService) executor);
568       }
569     }
570   }
571 }
572