• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #19136

01 Apr 2024 03:39PM UTC coverage: 88.246% (-0.01%) from 88.257%
#19136

push

github

web-flow
OkHttpServer: support maxConcurrentCallsPerConnection (Fixes #11062). (#11063)

* Add option in OkHttpServerBuilder
* Add value as MAX_CONCURRENT_STREAM setting in settings frame sent by the server to the client per connection
* Enforce limit by sending a RST frame with REFUSED_STREAM error

31210 of 35367 relevant lines covered (88.25%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.2
/../okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java
1
/*
2
 * Copyright 2022 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.checkArgument;
20

21
import com.google.common.base.Preconditions;
22
import com.google.errorprone.annotations.CanIgnoreReturnValue;
23
import com.google.errorprone.annotations.DoNotCall;
24
import io.grpc.ChoiceServerCredentials;
25
import io.grpc.ExperimentalApi;
26
import io.grpc.ForwardingServerBuilder;
27
import io.grpc.InsecureServerCredentials;
28
import io.grpc.Internal;
29
import io.grpc.ServerBuilder;
30
import io.grpc.ServerCredentials;
31
import io.grpc.ServerStreamTracer;
32
import io.grpc.TlsServerCredentials;
33
import io.grpc.internal.FixedObjectPool;
34
import io.grpc.internal.GrpcUtil;
35
import io.grpc.internal.InternalServer;
36
import io.grpc.internal.KeepAliveManager;
37
import io.grpc.internal.ObjectPool;
38
import io.grpc.internal.ServerImplBuilder;
39
import io.grpc.internal.SharedResourcePool;
40
import io.grpc.internal.TransportTracer;
41
import io.grpc.okhttp.internal.Platform;
42
import java.io.IOException;
43
import java.net.InetAddress;
44
import java.net.InetSocketAddress;
45
import java.net.Socket;
46
import java.net.SocketAddress;
47
import java.security.GeneralSecurityException;
48
import java.util.EnumSet;
49
import java.util.List;
50
import java.util.Set;
51
import java.util.concurrent.Executor;
52
import java.util.concurrent.ScheduledExecutorService;
53
import java.util.concurrent.TimeUnit;
54
import java.util.logging.Level;
55
import java.util.logging.Logger;
56
import javax.net.ServerSocketFactory;
57
import javax.net.ssl.KeyManager;
58
import javax.net.ssl.SSLContext;
59
import javax.net.ssl.SSLSocket;
60
import javax.net.ssl.SSLSocketFactory;
61
import javax.net.ssl.TrustManager;
62

63
/**
64
 * Build servers with the OkHttp transport.
65
 *
66
 * @since 1.49.0
67
 */
68
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1785")
69
public final class OkHttpServerBuilder extends ForwardingServerBuilder<OkHttpServerBuilder> {
70
  private static final Logger log = Logger.getLogger(OkHttpServerBuilder.class.getName());
1✔
71
  private static final int DEFAULT_FLOW_CONTROL_WINDOW = 65535;
72

73
  static final long MAX_CONNECTION_IDLE_NANOS_DISABLED = Long.MAX_VALUE;
74
  private static final long MIN_MAX_CONNECTION_IDLE_NANO = TimeUnit.SECONDS.toNanos(1L);
1✔
75
  static final long MAX_CONNECTION_AGE_NANOS_DISABLED = Long.MAX_VALUE;
76
  static final long MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE = Long.MAX_VALUE;
77
  static final int MAX_CONCURRENT_STREAMS = Integer.MAX_VALUE;
78
  private static final long MIN_MAX_CONNECTION_AGE_NANO = TimeUnit.SECONDS.toNanos(1L);
1✔
79

80
  private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L);
1✔
81
  private static final ObjectPool<Executor> DEFAULT_TRANSPORT_EXECUTOR_POOL =
1✔
82
      OkHttpChannelBuilder.DEFAULT_TRANSPORT_EXECUTOR_POOL;
83

84
  /**
85
   * Always throws, to shadow {@code ServerBuilder.forPort()}.
86
   *
87
   * @deprecated Use {@link #forPort(int, ServerCredentials)} instead
88
   */
89
  @DoNotCall("Always throws. Use forPort(int, ServerCredentials) instead")
90
  @Deprecated
91
  public static OkHttpServerBuilder forPort(int port) {
92
    throw new UnsupportedOperationException("Use forPort(int, ServerCredentials) instead");
×
93
  }
94

95
  /**
96
   * Creates a builder for a server listening on {@code port}.
97
   */
98
  public static OkHttpServerBuilder forPort(int port, ServerCredentials creds) {
99
    return forPort(new InetSocketAddress(port), creds);
1✔
100
  }
101

102
  /**
103
   * Creates a builder for a server listening on {@code address}.
104
   */
105
  public static OkHttpServerBuilder forPort(SocketAddress address, ServerCredentials creds) {
106
    HandshakerSocketFactoryResult result = handshakerSocketFactoryFrom(creds);
1✔
107
    if (result.error != null) {
1✔
108
      throw new IllegalArgumentException(result.error);
×
109
    }
110
    return new OkHttpServerBuilder(address, result.factory);
1✔
111
  }
112

113
  final ServerImplBuilder serverImplBuilder = new ServerImplBuilder(this::buildTransportServers);
1✔
114
  final SocketAddress listenAddress;
115
  final HandshakerSocketFactory handshakerSocketFactory;
116
  TransportTracer.Factory transportTracerFactory = TransportTracer.getDefaultFactory();
1✔
117

118
  ObjectPool<Executor> transportExecutorPool = DEFAULT_TRANSPORT_EXECUTOR_POOL;
1✔
119
  ObjectPool<ScheduledExecutorService> scheduledExecutorServicePool =
1✔
120
      SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE);
1✔
121

122
  ServerSocketFactory socketFactory = ServerSocketFactory.getDefault();
1✔
123
  long keepAliveTimeNanos = GrpcUtil.DEFAULT_SERVER_KEEPALIVE_TIME_NANOS;
1✔
124
  long keepAliveTimeoutNanos = GrpcUtil.DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS;
1✔
125
  int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW;
1✔
126
  int maxInboundMetadataSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
1✔
127
  int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
1✔
128
  long maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED;
1✔
129
  boolean permitKeepAliveWithoutCalls;
130
  long permitKeepAliveTimeInNanos = TimeUnit.MINUTES.toNanos(5);
1✔
131
  long maxConnectionAgeInNanos = MAX_CONNECTION_AGE_NANOS_DISABLED;
1✔
132
  long maxConnectionAgeGraceInNanos = MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE;
1✔
133
  int maxConcurrentCallsPerConnection = MAX_CONCURRENT_STREAMS;
1✔
134

135
  OkHttpServerBuilder(
136
      SocketAddress address, HandshakerSocketFactory handshakerSocketFactory) {
1✔
137
    this.listenAddress = Preconditions.checkNotNull(address, "address");
1✔
138
    this.handshakerSocketFactory =
1✔
139
        Preconditions.checkNotNull(handshakerSocketFactory, "handshakerSocketFactory");
1✔
140
  }
1✔
141

142
  @Internal
143
  @Override
144
  protected ServerBuilder<?> delegate() {
145
    return serverImplBuilder;
1✔
146
  }
147

148
  // @VisibleForTesting
149
  OkHttpServerBuilder setTransportTracerFactory(TransportTracer.Factory transportTracerFactory) {
150
    this.transportTracerFactory = transportTracerFactory;
1✔
151
    return this;
1✔
152
  }
153

154
  /**
155
   * Override the default executor necessary for internal transport use.
156
   *
157
   * <p>The channel does not take ownership of the given executor. It is the caller' responsibility
158
   * to shutdown the executor when appropriate.
159
   */
160
  public OkHttpServerBuilder transportExecutor(Executor transportExecutor) {
161
    if (transportExecutor == null) {
1✔
162
      this.transportExecutorPool = DEFAULT_TRANSPORT_EXECUTOR_POOL;
×
163
    } else {
164
      this.transportExecutorPool = new FixedObjectPool<>(transportExecutor);
1✔
165
    }
166
    return this;
1✔
167
  }
168

169
  /**
170
   * Override the default {@link ServerSocketFactory} used to listen. If the socket factory is not
171
   * set or set to null, a default one will be used.
172
   */
173
  public OkHttpServerBuilder socketFactory(ServerSocketFactory socketFactory) {
174
    if (socketFactory == null) {
×
175
      this.socketFactory = ServerSocketFactory.getDefault();
×
176
    } else {
177
      this.socketFactory = socketFactory;
×
178
    }
179
    return this;
×
180
  }
181

182
  /**
183
   * Sets the time without read activity before sending a keepalive ping. An unreasonably small
184
   * value might be increased, and {@code Long.MAX_VALUE} nano seconds or an unreasonably large
185
   * value will disable keepalive. Defaults to two hours.
186
   *
187
   * @throws IllegalArgumentException if time is not positive
188
   */
189
  @Override
190
  public OkHttpServerBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
191
    Preconditions.checkArgument(keepAliveTime > 0L, "keepalive time must be positive");
×
192
    keepAliveTimeNanos = timeUnit.toNanos(keepAliveTime);
×
193
    keepAliveTimeNanos = KeepAliveManager.clampKeepAliveTimeInNanos(keepAliveTimeNanos);
×
194
    if (keepAliveTimeNanos >= AS_LARGE_AS_INFINITE) {
×
195
      // Bump keepalive time to infinite. This disables keepalive.
196
      keepAliveTimeNanos = GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED;
×
197
    }
198
    return this;
×
199
  }
200

201
  /**
202
   * Sets a custom max connection idle time, connection being idle for longer than which will be
203
   * gracefully terminated. Idleness duration is defined since the most recent time the number of
204
   * outstanding RPCs became zero or the connection establishment. An unreasonably small value might
205
   * be increased. {@code Long.MAX_VALUE} nano seconds or an unreasonably large value will disable
206
   * max connection idle.
207
   */
208
  @Override
209
  public OkHttpServerBuilder maxConnectionIdle(long maxConnectionIdle, TimeUnit timeUnit) {
210
    checkArgument(maxConnectionIdle > 0L, "max connection idle must be positive: %s",
1✔
211
        maxConnectionIdle);
212
    maxConnectionIdleInNanos = timeUnit.toNanos(maxConnectionIdle);
1✔
213
    if (maxConnectionIdleInNanos >= AS_LARGE_AS_INFINITE) {
1✔
214
      maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED;
×
215
    }
216
    if (maxConnectionIdleInNanos < MIN_MAX_CONNECTION_IDLE_NANO) {
1✔
217
      maxConnectionIdleInNanos = MIN_MAX_CONNECTION_IDLE_NANO;
×
218
    }
219
    return this;
1✔
220
  }
221

222
  /**
223
   * Sets a custom max connection age, connection lasting longer than which will be gracefully
224
   * terminated. An unreasonably small value might be increased.  A random jitter of +/-10% will be
225
   * added to it. {@code Long.MAX_VALUE} nano seconds or an unreasonably large value will disable
226
   * max connection age.
227
   */
228
  @Override
229
  public OkHttpServerBuilder maxConnectionAge(long maxConnectionAge, TimeUnit timeUnit) {
230
    checkArgument(maxConnectionAge > 0L, "max connection age must be positive: %s",
1✔
231
        maxConnectionAge);
232
    maxConnectionAgeInNanos = timeUnit.toNanos(maxConnectionAge);
1✔
233
    if (maxConnectionAgeInNanos >= AS_LARGE_AS_INFINITE) {
1✔
234
      maxConnectionAgeInNanos = MAX_CONNECTION_AGE_NANOS_DISABLED;
×
235
    }
236
    if (maxConnectionAgeInNanos < MIN_MAX_CONNECTION_AGE_NANO) {
1✔
237
      maxConnectionAgeInNanos = MIN_MAX_CONNECTION_AGE_NANO;
×
238
    }
239
    return this;
1✔
240
  }
241

242
  /**
243
   * Sets a custom grace time for the graceful connection termination. Once the max connection age
244
   * is reached, RPCs have the grace time to complete. RPCs that do not complete in time will be
245
   * cancelled, allowing the connection to terminate. {@code Long.MAX_VALUE} nano seconds or an
246
   * unreasonably large value are considered infinite.
247
   *
248
   * @see #maxConnectionAge(long, TimeUnit)
249
   */
250
  @Override
251
  public OkHttpServerBuilder maxConnectionAgeGrace(long maxConnectionAgeGrace, TimeUnit timeUnit) {
252
    checkArgument(maxConnectionAgeGrace >= 0L, "max connection age grace must be non-negative: %s",
1✔
253
        maxConnectionAgeGrace);
254
    maxConnectionAgeGraceInNanos = timeUnit.toNanos(maxConnectionAgeGrace);
1✔
255
    if (maxConnectionAgeGraceInNanos >= AS_LARGE_AS_INFINITE) {
1✔
256
      maxConnectionAgeGraceInNanos = MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE;
×
257
    }
258
    return this;
1✔
259
  }
260

261
  /**
262
   * Sets a time waiting for read activity after sending a keepalive ping. If the time expires
263
   * without any read activity on the connection, the connection is considered dead. An unreasonably
264
   * small value might be increased. Defaults to 20 seconds.
265
   *
266
   * <p>This value should be at least multiple times the RTT to allow for lost packets.
267
   *
268
   * @throws IllegalArgumentException if timeout is not positive
269
   */
270
  @Override
271
  public OkHttpServerBuilder keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
272
    Preconditions.checkArgument(keepAliveTimeout > 0L, "keepalive timeout must be positive");
×
273
    keepAliveTimeoutNanos = timeUnit.toNanos(keepAliveTimeout);
×
274
    keepAliveTimeoutNanos = KeepAliveManager.clampKeepAliveTimeoutInNanos(keepAliveTimeoutNanos);
×
275
    return this;
×
276
  }
277

278
  /**
279
   * Specify the most aggressive keep-alive time clients are permitted to configure. The server will
280
   * try to detect clients exceeding this rate and when detected will forcefully close the
281
   * connection. The default is 5 minutes.
282
   *
283
   * <p>Even though a default is defined that allows some keep-alives, clients must not use
284
   * keep-alive without approval from the service owner. Otherwise, they may experience failures in
285
   * the future if the service becomes more restrictive. When unthrottled, keep-alives can cause a
286
   * significant amount of traffic and CPU usage, so clients and servers should be conservative in
287
   * what they use and accept.
288
   *
289
   * @see #permitKeepAliveWithoutCalls(boolean)
290
   */
291
  @CanIgnoreReturnValue
292
  @Override
293
  public OkHttpServerBuilder permitKeepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
294
    checkArgument(keepAliveTime >= 0, "permit keepalive time must be non-negative: %s",
1✔
295
        keepAliveTime);
296
    permitKeepAliveTimeInNanos = timeUnit.toNanos(keepAliveTime);
1✔
297
    return this;
1✔
298
  }
299

300
  /**
301
   * Sets whether to allow clients to send keep-alive HTTP/2 PINGs even if there are no outstanding
302
   * RPCs on the connection. Defaults to {@code false}.
303
   *
304
   * @see #permitKeepAliveTime(long, TimeUnit)
305
   */
306
  @CanIgnoreReturnValue
307
  @Override
308
  public OkHttpServerBuilder permitKeepAliveWithoutCalls(boolean permit) {
309
    permitKeepAliveWithoutCalls = permit;
1✔
310
    return this;
1✔
311
  }
312

313
  /**
314
   * Sets the flow control window in bytes. If not called, the default value is 64 KiB.
315
   */
316
  public OkHttpServerBuilder flowControlWindow(int flowControlWindow) {
317
    Preconditions.checkState(flowControlWindow > 0, "flowControlWindow must be positive");
1✔
318
    this.flowControlWindow = flowControlWindow;
1✔
319
    return this;
1✔
320
  }
321

322
  /**
323
   * Provides a custom scheduled executor service.
324
   *
325
   * <p>It's an optional parameter. If the user has not provided a scheduled executor service when
326
   * the channel is built, the builder will use a static thread pool.
327
   *
328
   * @return this
329
   */
330
  public OkHttpServerBuilder scheduledExecutorService(
331
      ScheduledExecutorService scheduledExecutorService) {
332
    this.scheduledExecutorServicePool = new FixedObjectPool<>(
1✔
333
        Preconditions.checkNotNull(scheduledExecutorService, "scheduledExecutorService"));
1✔
334
    return this;
1✔
335
  }
336

337
  /**
338
   * Sets the maximum size of metadata allowed to be received. Defaults to 8 KiB.
339
   *
340
   * <p>The implementation does not currently limit memory usage; this value is checked only after
341
   * the metadata is decoded from the wire. It does prevent large metadata from being passed to the
342
   * application.
343
   *
344
   * @param bytes the maximum size of received metadata
345
   * @return this
346
   * @throws IllegalArgumentException if bytes is non-positive
347
   */
348
  @Override
349
  public OkHttpServerBuilder maxInboundMetadataSize(int bytes) {
350
    Preconditions.checkArgument(bytes > 0, "maxInboundMetadataSize must be > 0");
×
351
    this.maxInboundMetadataSize = bytes;
×
352
    return this;
×
353
  }
354

355
  /**
356
   * The maximum number of concurrent calls permitted for each incoming connection. Defaults to no
357
   * limit.
358
   */
359
  @CanIgnoreReturnValue
360
  public OkHttpServerBuilder maxConcurrentCallsPerConnection(int maxConcurrentCallsPerConnection) {
361
    checkArgument(maxConcurrentCallsPerConnection > 0,
1✔
362
        "max must be positive: %s", maxConcurrentCallsPerConnection);
363
    this.maxConcurrentCallsPerConnection = maxConcurrentCallsPerConnection;
1✔
364
    return this;
1✔
365
  }
366

367
  /**
368
   * Sets the maximum message size allowed to be received on the server. If not called, defaults to
369
   * defaults to 4 MiB. The default provides protection to servers who haven't considered the
370
   * possibility of receiving large messages while trying to be large enough to not be hit in normal
371
   * usage.
372
   *
373
   * @param bytes the maximum number of bytes a single message can be.
374
   * @return this
375
   * @throws IllegalArgumentException if bytes is negative.
376
   */
377
  @Override
378
  public OkHttpServerBuilder maxInboundMessageSize(int bytes) {
379
    Preconditions.checkArgument(bytes >= 0, "negative max bytes");
1✔
380
    maxInboundMessageSize = bytes;
1✔
381
    return this;
1✔
382
  }
383

384
  void setStatsEnabled(boolean value) {
385
    this.serverImplBuilder.setStatsEnabled(value);
1✔
386
  }
1✔
387

388
  InternalServer buildTransportServers(
389
      List<? extends ServerStreamTracer.Factory> streamTracerFactories) {
390
    return new OkHttpServer(this, streamTracerFactories, serverImplBuilder.getChannelz());
1✔
391
  }
392

393
  private static final EnumSet<TlsServerCredentials.Feature> understoodTlsFeatures =
1✔
394
      EnumSet.of(
1✔
395
          TlsServerCredentials.Feature.MTLS, TlsServerCredentials.Feature.CUSTOM_MANAGERS);
396

397
  static HandshakerSocketFactoryResult handshakerSocketFactoryFrom(ServerCredentials creds) {
398
    if (creds instanceof TlsServerCredentials) {
1✔
399
      TlsServerCredentials tlsCreds = (TlsServerCredentials) creds;
1✔
400
      Set<TlsServerCredentials.Feature> incomprehensible =
1✔
401
          tlsCreds.incomprehensible(understoodTlsFeatures);
1✔
402
      if (!incomprehensible.isEmpty()) {
1✔
403
        return HandshakerSocketFactoryResult.error(
×
404
            "TLS features not understood: " + incomprehensible);
405
      }
406
      KeyManager[] km = null;
1✔
407
      if (tlsCreds.getKeyManagers() != null) {
1✔
408
        km = tlsCreds.getKeyManagers().toArray(new KeyManager[0]);
×
409
      } else if (tlsCreds.getPrivateKey() != null) {
1✔
410
        if (tlsCreds.getPrivateKeyPassword() != null) {
1✔
411
          return HandshakerSocketFactoryResult.error("byte[]-based private key with password "
×
412
              + "unsupported. Use unencrypted file or KeyManager");
413
        }
414
        try {
415
          km = OkHttpChannelBuilder.createKeyManager(
1✔
416
              tlsCreds.getCertificateChain(), tlsCreds.getPrivateKey());
1✔
417
        } catch (GeneralSecurityException gse) {
×
418
          log.log(Level.FINE, "Exception loading private key from credential", gse);
×
419
          return HandshakerSocketFactoryResult.error(
×
420
              "Unable to load private key: " + gse.getMessage());
×
421
        }
1✔
422
      } // else don't have a client cert
423
      TrustManager[] tm = null;
1✔
424
      if (tlsCreds.getTrustManagers() != null) {
1✔
425
        tm = tlsCreds.getTrustManagers().toArray(new TrustManager[0]);
×
426
      } else if (tlsCreds.getRootCertificates() != null) {
1✔
427
        try {
428
          tm = OkHttpChannelBuilder.createTrustManager(tlsCreds.getRootCertificates());
1✔
429
        } catch (GeneralSecurityException gse) {
×
430
          log.log(Level.FINE, "Exception loading root certificates from credential", gse);
×
431
          return HandshakerSocketFactoryResult.error(
×
432
              "Unable to load root certificates: " + gse.getMessage());
×
433
        }
1✔
434
      } // else use system default
435
      SSLContext sslContext;
436
      try {
437
        sslContext = SSLContext.getInstance("TLS", Platform.get().getProvider());
1✔
438
        sslContext.init(km, tm, null);
1✔
439
      } catch (GeneralSecurityException gse) {
×
440
        throw new RuntimeException("TLS Provider failure", gse);
×
441
      }
1✔
442
      SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
1✔
443
      switch (tlsCreds.getClientAuth()) {
1✔
444
        case OPTIONAL:
445
          sslSocketFactory = new ClientCertRequestingSocketFactory(sslSocketFactory, false);
1✔
446
          break;
1✔
447

448
        case REQUIRE:
449
          sslSocketFactory = new ClientCertRequestingSocketFactory(sslSocketFactory, true);
1✔
450
          break;
1✔
451

452
        case NONE:
453
          // NOOP; this is the SSLContext default
454
          break;
1✔
455

456
        default:
457
          return HandshakerSocketFactoryResult.error(
×
458
              "Unknown TlsServerCredentials.ClientAuth value: " + tlsCreds.getClientAuth());
×
459
      }
460
      return HandshakerSocketFactoryResult.factory(new TlsServerHandshakerSocketFactory(
1✔
461
          new SslSocketFactoryServerCredentials.ServerCredentials(sslSocketFactory)));
462

463
    } else if (creds instanceof InsecureServerCredentials) {
1✔
464
      return HandshakerSocketFactoryResult.factory(new PlaintextHandshakerSocketFactory());
1✔
465

466
    } else if (creds instanceof SslSocketFactoryServerCredentials.ServerCredentials) {
1✔
467
      SslSocketFactoryServerCredentials.ServerCredentials factoryCreds =
×
468
          (SslSocketFactoryServerCredentials.ServerCredentials) creds;
469
      return HandshakerSocketFactoryResult.factory(
×
470
          new TlsServerHandshakerSocketFactory(factoryCreds));
471

472
    } else if (creds instanceof ChoiceServerCredentials) {
1✔
473
      ChoiceServerCredentials choiceCreds = (ChoiceServerCredentials) creds;
×
474
      StringBuilder error = new StringBuilder();
×
475
      for (ServerCredentials innerCreds : choiceCreds.getCredentialsList()) {
×
476
        HandshakerSocketFactoryResult result = handshakerSocketFactoryFrom(innerCreds);
×
477
        if (result.error == null) {
×
478
          return result;
×
479
        }
480
        error.append(", ");
×
481
        error.append(result.error);
×
482
      }
×
483
      return HandshakerSocketFactoryResult.error(error.substring(2));
×
484

485
    } else {
486
      return HandshakerSocketFactoryResult.error(
1✔
487
          "Unsupported credential type: " + creds.getClass().getName());
1✔
488
    }
489
  }
490

491
  static final class HandshakerSocketFactoryResult {
492
    public final HandshakerSocketFactory factory;
493
    public final String error;
494

495
    private HandshakerSocketFactoryResult(HandshakerSocketFactory factory, String error) {
1✔
496
      this.factory = factory;
1✔
497
      this.error = error;
1✔
498
    }
1✔
499

500
    public static HandshakerSocketFactoryResult error(String error) {
501
      return new HandshakerSocketFactoryResult(
1✔
502
          null, Preconditions.checkNotNull(error, "error"));
1✔
503
    }
504

505
    public static HandshakerSocketFactoryResult factory(HandshakerSocketFactory factory) {
506
      return new HandshakerSocketFactoryResult(
1✔
507
          Preconditions.checkNotNull(factory, "factory"), null);
1✔
508
    }
509
  }
510

511
  static final class ClientCertRequestingSocketFactory extends SSLSocketFactory {
512
    private final SSLSocketFactory socketFactory;
513
    private final boolean required;
514

515
    public ClientCertRequestingSocketFactory(SSLSocketFactory socketFactory, boolean required) {
1✔
516
      this.socketFactory = Preconditions.checkNotNull(socketFactory, "socketFactory");
1✔
517
      this.required = required;
1✔
518
    }
1✔
519

520
    private Socket apply(Socket s) throws IOException {
521
      if (!(s instanceof SSLSocket)) {
1✔
522
        throw new IOException(
×
523
            "SocketFactory " + socketFactory + " did not produce an SSLSocket: " + s.getClass());
×
524
      }
525
      SSLSocket sslSocket = (SSLSocket) s;
1✔
526
      if (required) {
1✔
527
        sslSocket.setNeedClientAuth(true);
1✔
528
      } else {
529
        sslSocket.setWantClientAuth(true);
1✔
530
      }
531
      return sslSocket;
1✔
532
    }
533

534
    @Override public Socket createSocket(Socket s, String host, int port, boolean autoClose)
535
        throws IOException {
536
      return apply(socketFactory.createSocket(s, host, port, autoClose));
1✔
537
    }
538

539
    @Override public Socket createSocket(String host, int port) throws IOException {
540
      return apply(socketFactory.createSocket(host, port));
×
541
    }
542

543
    @Override public Socket createSocket(
544
        String host, int port, InetAddress localHost, int localPort) throws IOException {
545
      return apply(socketFactory.createSocket(host, port, localHost, localPort));
×
546
    }
547

548
    @Override public Socket createSocket(InetAddress host, int port) throws IOException {
549
      return apply(socketFactory.createSocket(host, port));
×
550
    }
551

552
    @Override public Socket createSocket(
553
        InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException {
554
      return apply(socketFactory.createSocket(host, port, localAddress, localPort));
×
555
    }
556

557
    @Override public String[] getDefaultCipherSuites() {
558
      return socketFactory.getDefaultCipherSuites();
×
559
    }
560

561
    @Override public String[] getSupportedCipherSuites() {
562
      return socketFactory.getSupportedCipherSuites();
×
563
    }
564
  }
565
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc