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

grpc / grpc-java / #20068

12 Nov 2025 09:11PM UTC coverage: 88.558% (+0.04%) from 88.522%
#20068

push

github

ejona86
api,netty: Add custom header support for HTTP CONNECT proxy

Allow users to specify custom HTTP headers when connecting through
an HTTP CONNECT proxy. This extends HttpConnectProxiedSocketAddress
with an optional headers field (Map<String, String>), which is
converted to Netty's HttpHeaders in the protocol negotiator.

This change is fully backward-compatible. Existing code without
headers continues to work as before.

Fixes #9826

35101 of 39636 relevant lines covered (88.56%)

0.89 hits per line

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

91.41
/../netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java
1
/*
2
 * Copyright 2015 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.netty;
18

19
import static com.google.common.base.Preconditions.checkState;
20

21
import com.google.common.annotations.VisibleForTesting;
22
import com.google.common.base.Optional;
23
import com.google.common.base.Preconditions;
24
import com.google.errorprone.annotations.ForOverride;
25
import io.grpc.Attributes;
26
import io.grpc.CallCredentials;
27
import io.grpc.ChannelCredentials;
28
import io.grpc.ChannelLogger;
29
import io.grpc.ChannelLogger.ChannelLogLevel;
30
import io.grpc.ChoiceChannelCredentials;
31
import io.grpc.ChoiceServerCredentials;
32
import io.grpc.CompositeCallCredentials;
33
import io.grpc.CompositeChannelCredentials;
34
import io.grpc.Grpc;
35
import io.grpc.InsecureChannelCredentials;
36
import io.grpc.InsecureServerCredentials;
37
import io.grpc.InternalChannelz.Security;
38
import io.grpc.InternalChannelz.Tls;
39
import io.grpc.SecurityLevel;
40
import io.grpc.ServerCredentials;
41
import io.grpc.Status;
42
import io.grpc.TlsChannelCredentials;
43
import io.grpc.TlsServerCredentials;
44
import io.grpc.internal.CertificateUtils;
45
import io.grpc.internal.GrpcAttributes;
46
import io.grpc.internal.GrpcUtil;
47
import io.grpc.internal.NoopSslSession;
48
import io.grpc.internal.ObjectPool;
49
import io.netty.channel.ChannelDuplexHandler;
50
import io.netty.channel.ChannelFutureListener;
51
import io.netty.channel.ChannelHandler;
52
import io.netty.channel.ChannelHandlerContext;
53
import io.netty.channel.ChannelInboundHandlerAdapter;
54
import io.netty.handler.codec.http.DefaultHttpHeaders;
55
import io.netty.handler.codec.http.DefaultHttpRequest;
56
import io.netty.handler.codec.http.HttpClientCodec;
57
import io.netty.handler.codec.http.HttpClientUpgradeHandler;
58
import io.netty.handler.codec.http.HttpHeaderNames;
59
import io.netty.handler.codec.http.HttpHeaders;
60
import io.netty.handler.codec.http.HttpMethod;
61
import io.netty.handler.codec.http.HttpVersion;
62
import io.netty.handler.codec.http2.Http2ClientUpgradeCodec;
63
import io.netty.handler.proxy.HttpProxyHandler;
64
import io.netty.handler.proxy.ProxyConnectionEvent;
65
import io.netty.handler.ssl.OpenSsl;
66
import io.netty.handler.ssl.OpenSslEngine;
67
import io.netty.handler.ssl.SslContext;
68
import io.netty.handler.ssl.SslContextBuilder;
69
import io.netty.handler.ssl.SslHandler;
70
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
71
import io.netty.handler.ssl.SslProvider;
72
import io.netty.util.AsciiString;
73
import java.io.ByteArrayInputStream;
74
import java.net.SocketAddress;
75
import java.net.URI;
76
import java.nio.channels.ClosedChannelException;
77
import java.security.GeneralSecurityException;
78
import java.security.KeyStore;
79
import java.util.Arrays;
80
import java.util.EnumSet;
81
import java.util.List;
82
import java.util.Map;
83
import java.util.Set;
84
import java.util.concurrent.Executor;
85
import java.util.logging.Level;
86
import java.util.logging.Logger;
87
import javax.annotation.Nullable;
88
import javax.net.ssl.SSLEngine;
89
import javax.net.ssl.SSLException;
90
import javax.net.ssl.SSLParameters;
91
import javax.net.ssl.SSLSession;
92
import javax.net.ssl.TrustManager;
93
import javax.net.ssl.TrustManagerFactory;
94
import javax.net.ssl.X509TrustManager;
95
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
96

97
/**
98
 * Common {@link ProtocolNegotiator}s used by gRPC.
99
 */
100
final class ProtocolNegotiators {
101
  private static final Logger log = Logger.getLogger(ProtocolNegotiators.class.getName());
1✔
102
  private static final EnumSet<TlsChannelCredentials.Feature> understoodTlsFeatures =
1✔
103
      EnumSet.of(
1✔
104
          TlsChannelCredentials.Feature.MTLS, TlsChannelCredentials.Feature.CUSTOM_MANAGERS);
105
  private static final EnumSet<TlsServerCredentials.Feature> understoodServerTlsFeatures =
1✔
106
      EnumSet.of(
1✔
107
          TlsServerCredentials.Feature.MTLS, TlsServerCredentials.Feature.CUSTOM_MANAGERS);
108

109
  private ProtocolNegotiators() {
110
  }
111

112
  public static FromChannelCredentialsResult from(ChannelCredentials creds) {
113
    if (creds instanceof TlsChannelCredentials) {
1✔
114
      TlsChannelCredentials tlsCreds = (TlsChannelCredentials) creds;
1✔
115
      Set<TlsChannelCredentials.Feature> incomprehensible =
1✔
116
          tlsCreds.incomprehensible(understoodTlsFeatures);
1✔
117
      if (!incomprehensible.isEmpty()) {
1✔
118
        return FromChannelCredentialsResult.error(
1✔
119
            "TLS features not understood: " + incomprehensible);
120
      }
121
      SslContextBuilder builder = GrpcSslContexts.forClient();
1✔
122
      if (tlsCreds.getKeyManagers() != null) {
1✔
123
        builder.keyManager(new FixedKeyManagerFactory(tlsCreds.getKeyManagers()));
1✔
124
      } else if (tlsCreds.getPrivateKey() != null) {
1✔
125
        builder.keyManager(
1✔
126
            new ByteArrayInputStream(tlsCreds.getCertificateChain()),
1✔
127
            new ByteArrayInputStream(tlsCreds.getPrivateKey()),
1✔
128
            tlsCreds.getPrivateKeyPassword());
1✔
129
      }
130
      try {
131
        List<TrustManager> trustManagers;
132
        if (tlsCreds.getTrustManagers() != null) {
1✔
133
          trustManagers = tlsCreds.getTrustManagers();
1✔
134
        } else if (tlsCreds.getRootCertificates() != null) {
1✔
135
          trustManagers = Arrays.asList(CertificateUtils.createTrustManager(
1✔
136
                  new ByteArrayInputStream(tlsCreds.getRootCertificates())));
1✔
137
        } else { // else use system default
138
          TrustManagerFactory tmf = TrustManagerFactory.getInstance(
1✔
139
              TrustManagerFactory.getDefaultAlgorithm());
1✔
140
          tmf.init((KeyStore) null);
1✔
141
          trustManagers = Arrays.asList(tmf.getTrustManagers());
1✔
142
        }
143
        builder.trustManager(new FixedTrustManagerFactory(trustManagers));
1✔
144
        TrustManager x509ExtendedTrustManager =
1✔
145
            CertificateUtils.getX509ExtendedTrustManager(trustManagers);
1✔
146
        return FromChannelCredentialsResult.negotiator(tlsClientFactory(builder.build(),
1✔
147
                (X509TrustManager) x509ExtendedTrustManager));
148
      } catch (SSLException | GeneralSecurityException ex) {
×
149
        log.log(Level.FINE, "Exception building SslContext", ex);
×
150
        return FromChannelCredentialsResult.error(
×
151
            "Unable to create SslContext: " + ex.getMessage());
×
152
      }
153

154
    } else if (creds instanceof InsecureChannelCredentials) {
1✔
155
      return FromChannelCredentialsResult.negotiator(plaintextClientFactory());
1✔
156

157
    } else if (creds instanceof CompositeChannelCredentials) {
1✔
158
      CompositeChannelCredentials compCreds = (CompositeChannelCredentials) creds;
1✔
159
      return from(compCreds.getChannelCredentials())
1✔
160
          .withCallCredentials(compCreds.getCallCredentials());
1✔
161

162
    } else if (creds instanceof NettyChannelCredentials) {
1✔
163
      NettyChannelCredentials nettyCreds = (NettyChannelCredentials) creds;
1✔
164
      return FromChannelCredentialsResult.negotiator(nettyCreds.getNegotiator());
1✔
165

166
    } else if (creds instanceof ChoiceChannelCredentials) {
1✔
167
      ChoiceChannelCredentials choiceCreds = (ChoiceChannelCredentials) creds;
1✔
168
      StringBuilder error = new StringBuilder();
1✔
169
      for (ChannelCredentials innerCreds : choiceCreds.getCredentialsList()) {
1✔
170
        FromChannelCredentialsResult result = from(innerCreds);
1✔
171
        if (result.error == null) {
1✔
172
          return result;
1✔
173
        }
174
        error.append(", ");
1✔
175
        error.append(result.error);
1✔
176
      }
1✔
177
      return FromChannelCredentialsResult.error(error.substring(2));
1✔
178

179
    } else {
180
      return FromChannelCredentialsResult.error(
1✔
181
          "Unsupported credential type: " + creds.getClass().getName());
1✔
182
    }
183
  }
184

185
  public static FromServerCredentialsResult from(ServerCredentials creds) {
186
    if (creds instanceof TlsServerCredentials) {
1✔
187
      TlsServerCredentials tlsCreds = (TlsServerCredentials) creds;
1✔
188
      Set<TlsServerCredentials.Feature> incomprehensible =
1✔
189
          tlsCreds.incomprehensible(understoodServerTlsFeatures);
1✔
190
      if (!incomprehensible.isEmpty()) {
1✔
191
        return FromServerCredentialsResult.error(
1✔
192
            "TLS features not understood: " + incomprehensible);
193
      }
194
      SslContextBuilder builder;
195
      if (tlsCreds.getKeyManagers() != null) {
1✔
196
        builder = GrpcSslContexts.configure(SslContextBuilder.forServer(
1✔
197
            new FixedKeyManagerFactory(tlsCreds.getKeyManagers())));
1✔
198
      } else if (tlsCreds.getPrivateKey() != null) {
1✔
199
        builder = GrpcSslContexts.forServer(
1✔
200
            new ByteArrayInputStream(tlsCreds.getCertificateChain()),
1✔
201
            new ByteArrayInputStream(tlsCreds.getPrivateKey()),
1✔
202
            tlsCreds.getPrivateKeyPassword());
1✔
203
      } else {
204
        throw new AssertionError("BUG! No key");
×
205
      }
206
      if (tlsCreds.getTrustManagers() != null) {
1✔
207
        builder.trustManager(new FixedTrustManagerFactory(tlsCreds.getTrustManagers()));
1✔
208
      } else if (tlsCreds.getRootCertificates() != null) {
1✔
209
        builder.trustManager(new ByteArrayInputStream(tlsCreds.getRootCertificates()));
1✔
210
      } // else use system default
211
      switch (tlsCreds.getClientAuth()) {
1✔
212
        case OPTIONAL:
213
          builder.clientAuth(io.netty.handler.ssl.ClientAuth.OPTIONAL);
1✔
214
          break;
1✔
215

216
        case REQUIRE:
217
          builder.clientAuth(io.netty.handler.ssl.ClientAuth.REQUIRE);
1✔
218
          break;
1✔
219

220
        case NONE:
221
          builder.clientAuth(io.netty.handler.ssl.ClientAuth.NONE);
1✔
222
          break;
1✔
223

224
        default:
225
          return FromServerCredentialsResult.error(
×
226
              "Unknown TlsServerCredentials.ClientAuth value: " + tlsCreds.getClientAuth());
×
227
      }
228
      SslContext sslContext;
229
      try {
230
        sslContext = builder.build();
1✔
231
      } catch (SSLException ex) {
×
232
        throw new IllegalArgumentException(
×
233
            "Unexpected error converting ServerCredentials to Netty SslContext", ex);
234
      }
1✔
235
      return FromServerCredentialsResult.negotiator(serverTlsFactory(sslContext));
1✔
236

237
    } else if (creds instanceof InsecureServerCredentials) {
1✔
238
      return FromServerCredentialsResult.negotiator(serverPlaintextFactory());
1✔
239

240
    } else if (creds instanceof NettyServerCredentials) {
1✔
241
      NettyServerCredentials nettyCreds = (NettyServerCredentials) creds;
1✔
242
      return FromServerCredentialsResult.negotiator(nettyCreds.getNegotiator());
1✔
243

244
    } else if (creds instanceof ChoiceServerCredentials) {
1✔
245
      ChoiceServerCredentials choiceCreds = (ChoiceServerCredentials) creds;
1✔
246
      StringBuilder error = new StringBuilder();
1✔
247
      for (ServerCredentials innerCreds : choiceCreds.getCredentialsList()) {
1✔
248
        FromServerCredentialsResult result = from(innerCreds);
1✔
249
        if (result.error == null) {
1✔
250
          return result;
1✔
251
        }
252
        error.append(", ");
1✔
253
        error.append(result.error);
1✔
254
      }
1✔
255
      return FromServerCredentialsResult.error(error.substring(2));
1✔
256

257
    } else {
258
      return FromServerCredentialsResult.error(
1✔
259
          "Unsupported credential type: " + creds.getClass().getName());
1✔
260
    }
261
  }
262

263
  public static final class FromChannelCredentialsResult {
264
    public final ProtocolNegotiator.ClientFactory negotiator;
265
    public final CallCredentials callCredentials;
266
    public final String error;
267

268
    private FromChannelCredentialsResult(ProtocolNegotiator.ClientFactory negotiator,
269
        CallCredentials creds, String error) {
1✔
270
      this.negotiator = negotiator;
1✔
271
      this.callCredentials = creds;
1✔
272
      this.error = error;
1✔
273
    }
1✔
274

275
    public static FromChannelCredentialsResult error(String error) {
276
      return new FromChannelCredentialsResult(
1✔
277
          null, null, Preconditions.checkNotNull(error, "error"));
1✔
278
    }
279

280
    public static FromChannelCredentialsResult negotiator(
281
        ProtocolNegotiator.ClientFactory factory) {
282
      return new FromChannelCredentialsResult(
1✔
283
          Preconditions.checkNotNull(factory, "factory"), null, null);
1✔
284
    }
285

286
    public FromChannelCredentialsResult withCallCredentials(CallCredentials callCreds) {
287
      Preconditions.checkNotNull(callCreds, "callCreds");
1✔
288
      if (error != null) {
1✔
289
        return this;
×
290
      }
291
      if (this.callCredentials != null) {
1✔
292
        callCreds = new CompositeCallCredentials(this.callCredentials, callCreds);
×
293
      }
294
      return new FromChannelCredentialsResult(negotiator, callCreds, null);
1✔
295
    }
296
  }
297

298
  public static final class FromServerCredentialsResult {
299
    public final ProtocolNegotiator.ServerFactory negotiator;
300
    public final String error;
301

302
    private FromServerCredentialsResult(ProtocolNegotiator.ServerFactory negotiator, String error) {
1✔
303
      this.negotiator = negotiator;
1✔
304
      this.error = error;
1✔
305
    }
1✔
306

307
    public static FromServerCredentialsResult error(String error) {
308
      return new FromServerCredentialsResult(null, Preconditions.checkNotNull(error, "error"));
1✔
309
    }
310

311
    public static FromServerCredentialsResult negotiator(ProtocolNegotiator.ServerFactory factory) {
312
      return new FromServerCredentialsResult(Preconditions.checkNotNull(factory, "factory"), null);
1✔
313
    }
314
  }
315

316
  public static ProtocolNegotiator.ServerFactory fixedServerFactory(
317
      ProtocolNegotiator negotiator) {
318
    return new FixedProtocolNegotiatorServerFactory(negotiator);
×
319
  }
320

321
  private static final class FixedProtocolNegotiatorServerFactory
322
      implements ProtocolNegotiator.ServerFactory {
323
    private final ProtocolNegotiator protocolNegotiator;
324

325
    public FixedProtocolNegotiatorServerFactory(ProtocolNegotiator protocolNegotiator) {
×
326
      this.protocolNegotiator =
×
327
          Preconditions.checkNotNull(protocolNegotiator, "protocolNegotiator");
×
328
    }
×
329

330
    @Override
331
    public ProtocolNegotiator newNegotiator(ObjectPool<? extends Executor> offloadExecutorPool) {
332
      return protocolNegotiator;
×
333
    }
334
  }
335

336
  /**
337
   * Create a server plaintext handler for gRPC.
338
   */
339
  public static ProtocolNegotiator serverPlaintext() {
340
    return new PlaintextProtocolNegotiator();
1✔
341
  }
342

343
  /**
344
   * Create a server plaintext handler factory for gRPC.
345
   */
346
  public static ProtocolNegotiator.ServerFactory serverPlaintextFactory() {
347
    return new PlaintextProtocolNegotiatorServerFactory();
1✔
348
  }
349

350
  @VisibleForTesting
351
  static final class PlaintextProtocolNegotiatorServerFactory
1✔
352
      implements ProtocolNegotiator.ServerFactory {
353
    @Override
354
    public ProtocolNegotiator newNegotiator(ObjectPool<? extends Executor> offloadExecutorPool) {
355
      return serverPlaintext();
1✔
356
    }
357
  }
358

359
  public static ProtocolNegotiator.ServerFactory serverTlsFactory(SslContext sslContext) {
360
    return new TlsProtocolNegotiatorServerFactory(sslContext);
1✔
361
  }
362

363
  @VisibleForTesting
364
  static final class TlsProtocolNegotiatorServerFactory
365
      implements ProtocolNegotiator.ServerFactory {
366
    private final SslContext sslContext;
367

368
    public TlsProtocolNegotiatorServerFactory(SslContext sslContext) {
1✔
369
      this.sslContext = Preconditions.checkNotNull(sslContext, "sslContext");
1✔
370
    }
1✔
371

372
    @Override
373
    public ProtocolNegotiator newNegotiator(ObjectPool<? extends Executor> offloadExecutorPool) {
374
      return serverTls(sslContext, offloadExecutorPool);
1✔
375
    }
376
  }
377

378
  /**
379
   * Create a server TLS handler for HTTP/2 capable of using ALPN/NPN.
380
   * @param executorPool a dedicated {@link Executor} pool for time-consuming TLS tasks
381
   */
382
  public static ProtocolNegotiator serverTls(final SslContext sslContext,
383
      final ObjectPool<? extends Executor> executorPool) {
384
    Preconditions.checkNotNull(sslContext, "sslContext");
1✔
385
    final Executor executor;
386
    if (executorPool != null) {
1✔
387
      // The handlers here can out-live the {@link ProtocolNegotiator}.
388
      // To keep their own reference to executor from executorPool, we use an extra (unused)
389
      // reference here forces the executor to stay alive, which prevents it from being re-created
390
      // for every connection.
391
      executor = executorPool.getObject();
1✔
392
    } else {
393
      executor = null;
1✔
394
    }
395
    return new ProtocolNegotiator() {
1✔
396
      @Override
397
      public ChannelHandler newHandler(GrpcHttp2ConnectionHandler handler) {
398
        ChannelHandler gnh = new GrpcNegotiationHandler(handler);
1✔
399
        ChannelHandler sth = new ServerTlsHandler(gnh, sslContext, executorPool);
1✔
400
        return new WaitUntilActiveHandler(sth, handler.getNegotiationLogger());
1✔
401
      }
402

403
      @Override
404
      public void close() {
405
        if (executorPool != null && executor != null) {
1✔
406
          executorPool.returnObject(executor);
1✔
407
        }
408
      }
1✔
409

410
      @Override
411
      public AsciiString scheme() {
412
        return Utils.HTTPS;
×
413
      }
414
    };
415
  }
416

417
  /**
418
   * Create a server TLS handler for HTTP/2 capable of using ALPN/NPN.
419
   */
420
  public static ProtocolNegotiator serverTls(final SslContext sslContext) {
421
    return serverTls(sslContext, null);
1✔
422
  }
423

424
  static final class ServerTlsHandler extends ChannelInboundHandlerAdapter {
425
    private Executor executor;
426
    private final ChannelHandler next;
427
    private final SslContext sslContext;
428

429
    private ProtocolNegotiationEvent pne = ProtocolNegotiationEvent.DEFAULT;
1✔
430

431
    ServerTlsHandler(ChannelHandler next,
432
        SslContext sslContext,
433
        final ObjectPool<? extends Executor> executorPool) {
1✔
434
      this.sslContext = Preconditions.checkNotNull(sslContext, "sslContext");
1✔
435
      this.next = Preconditions.checkNotNull(next, "next");
1✔
436
      if (executorPool != null) {
1✔
437
        this.executor = executorPool.getObject();
1✔
438
      }
439
    }
1✔
440

441
    @Override
442
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
443
      super.handlerAdded(ctx);
1✔
444
      SSLEngine sslEngine = sslContext.newEngine(ctx.alloc());
1✔
445
      ctx.pipeline().addBefore(ctx.name(), /* name= */ null, this.executor != null
1✔
446
          ? new SslHandler(sslEngine, false, this.executor)
1✔
447
          : new SslHandler(sslEngine, false));
1✔
448
    }
1✔
449

450
    @Override
451
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
452
      if (evt instanceof ProtocolNegotiationEvent) {
1✔
453
        pne = (ProtocolNegotiationEvent) evt;
1✔
454
      } else if (evt instanceof SslHandshakeCompletionEvent) {
1✔
455
        SslHandshakeCompletionEvent handshakeEvent = (SslHandshakeCompletionEvent) evt;
1✔
456
        if (!handshakeEvent.isSuccess()) {
1✔
457
          logSslEngineDetails(Level.FINE, ctx, "TLS negotiation failed for new client.", null);
1✔
458
          ctx.fireExceptionCaught(handshakeEvent.cause());
1✔
459
          return;
1✔
460
        }
461
        SslHandler sslHandler = ctx.pipeline().get(SslHandler.class);
1✔
462
        if (!sslContext.applicationProtocolNegotiator().protocols().contains(
1✔
463
                sslHandler.applicationProtocol())) {
1✔
464
          logSslEngineDetails(Level.FINE, ctx, "TLS negotiation failed for new client.", null);
1✔
465
          ctx.fireExceptionCaught(unavailableException(
1✔
466
              "Failed protocol negotiation: Unable to find compatible protocol"));
467
          return;
1✔
468
        }
469
        ctx.pipeline().replace(ctx.name(), null, next);
1✔
470
        fireProtocolNegotiationEvent(ctx, sslHandler.engine().getSession());
1✔
471
      } else {
1✔
472
        super.userEventTriggered(ctx, evt);
1✔
473
      }
474
    }
1✔
475

476
    private void fireProtocolNegotiationEvent(ChannelHandlerContext ctx, SSLSession session) {
477
      Security security = new Security(new Tls(session));
1✔
478
      Attributes attrs = pne.getAttributes().toBuilder()
1✔
479
          .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
1✔
480
          .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, session)
1✔
481
          .build();
1✔
482
      ctx.fireUserEventTriggered(pne.withAttributes(attrs).withSecurity(security));
1✔
483
    }
1✔
484
  }
485

486
  /**
487
   * Returns a {@link ProtocolNegotiator} that does HTTP CONNECT proxy negotiation.
488
   */
489
  public static ProtocolNegotiator httpProxy(final SocketAddress proxyAddress,
490
      final @Nullable Map<String, String> headers, final @Nullable String proxyUsername,
491
      final @Nullable String proxyPassword,
492
      final ProtocolNegotiator negotiator) {
493
    Preconditions.checkNotNull(negotiator, "negotiator");
1✔
494
    Preconditions.checkNotNull(proxyAddress, "proxyAddress");
1✔
495
    final AsciiString scheme = negotiator.scheme();
1✔
496
    class ProxyNegotiator implements ProtocolNegotiator {
1✔
497
      @Override
498
      public ChannelHandler newHandler(GrpcHttp2ConnectionHandler http2Handler) {
499
        ChannelHandler protocolNegotiationHandler = negotiator.newHandler(http2Handler);
1✔
500
        ChannelLogger negotiationLogger = http2Handler.getNegotiationLogger();
1✔
501
        HttpHeaders httpHeaders = toHttpHeaders(headers);
1✔
502
        return new ProxyProtocolNegotiationHandler(
1✔
503
            proxyAddress, httpHeaders, proxyUsername, proxyPassword, protocolNegotiationHandler,
504
            negotiationLogger);
505
      }
506

507
      @Override
508
      public AsciiString scheme() {
509
        return scheme;
×
510
      }
511

512
      // This method is not normally called, because we use httpProxy on a per-connection basis in
513
      // NettyChannelBuilder. Instead, we expect `negotiator' to be closed by NettyTransportFactory.
514
      @Override
515
      public void close() {
516
        negotiator.close();
×
517
      }
×
518
    }
519

520
    return new ProxyNegotiator();
1✔
521
  }
522

523
  /**
524
   * Converts generic Map of headers to Netty's HttpHeaders.
525
   * Returns null if the map is null or empty.
526
   */
527
  @Nullable
528
  private static HttpHeaders toHttpHeaders(@Nullable Map<String, String> headers) {
529
    if (headers == null || headers.isEmpty()) {
1✔
530
      return null;
1✔
531
    }
532
    HttpHeaders httpHeaders = new DefaultHttpHeaders();
1✔
533
    for (Map.Entry<String, String> entry : headers.entrySet()) {
1✔
534
      httpHeaders.add(entry.getKey(), entry.getValue());
1✔
535
    }
1✔
536
    return httpHeaders;
1✔
537
  }
538

539
  /**
540
   * A Proxy handler follows {@link ProtocolNegotiationHandler} pattern. Upon successful proxy
541
   * connection, this handler will install {@code next} handler which should be a handler from
542
   * other type of {@link ProtocolNegotiator} to continue negotiating protocol using proxy.
543
   */
544
  static final class ProxyProtocolNegotiationHandler extends ProtocolNegotiationHandler {
545

546
    private final SocketAddress address;
547
    @Nullable private final HttpHeaders httpHeaders;
548
    @Nullable private final String userName;
549
    @Nullable private final String password;
550

551
    public ProxyProtocolNegotiationHandler(
552
        SocketAddress address,
553
        @Nullable HttpHeaders httpHeaders,
554
        @Nullable String userName,
555
        @Nullable String password,
556
        ChannelHandler next,
557
        ChannelLogger negotiationLogger) {
558
      super(next, negotiationLogger);
1✔
559
      this.address = Preconditions.checkNotNull(address, "address");
1✔
560
      this.httpHeaders = httpHeaders;
1✔
561
      this.userName = userName;
1✔
562
      this.password = password;
1✔
563
    }
1✔
564

565
    @Override
566
    protected void protocolNegotiationEventTriggered(ChannelHandlerContext ctx) {
567
      HttpProxyHandler nettyProxyHandler;
568
      if (userName == null || password == null) {
1✔
569
        nettyProxyHandler = new HttpProxyHandler(address, httpHeaders);
1✔
570
      } else {
571
        nettyProxyHandler = new HttpProxyHandler(address, userName, password, httpHeaders);
×
572
      }
573
      ctx.pipeline().addBefore(ctx.name(), /* name= */ null, nettyProxyHandler);
1✔
574
    }
1✔
575

576
    @Override
577
    protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws Exception {
578
      if (evt instanceof ProxyConnectionEvent) {
1✔
579
        fireProtocolNegotiationEvent(ctx);
1✔
580
      } else {
581
        super.userEventTriggered(ctx, evt);
×
582
      }
583
    }
1✔
584
  }
585

586
  static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator {
587

588
    public ClientTlsProtocolNegotiator(SslContext sslContext,
589
        ObjectPool<? extends Executor> executorPool, Optional<Runnable> handshakeCompleteRunnable,
590
        X509TrustManager x509ExtendedTrustManager, String sni) {
1✔
591
      this.sslContext = Preconditions.checkNotNull(sslContext, "sslContext");
1✔
592
      this.executorPool = executorPool;
1✔
593
      if (this.executorPool != null) {
1✔
594
        this.executor = this.executorPool.getObject();
1✔
595
      }
596
      this.handshakeCompleteRunnable = handshakeCompleteRunnable;
1✔
597
      this.x509ExtendedTrustManager = x509ExtendedTrustManager;
1✔
598
      this.sni = sni;
1✔
599
    }
1✔
600

601
    private final SslContext sslContext;
602
    private final ObjectPool<? extends Executor> executorPool;
603
    private final Optional<Runnable> handshakeCompleteRunnable;
604
    private final X509TrustManager x509ExtendedTrustManager;
605
    private final String sni;
606
    private Executor executor;
607

608
    @Override
609
    public AsciiString scheme() {
610
      return Utils.HTTPS;
1✔
611
    }
612

613
    @Override
614
    public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
615
      ChannelHandler gnh = new GrpcNegotiationHandler(grpcHandler);
1✔
616
      ChannelLogger negotiationLogger = grpcHandler.getNegotiationLogger();
1✔
617
      String authority;
618
      if ("".equals(sni)) {
1✔
619
        authority = null;
1✔
620
      } else if (sni != null) {
1✔
621
        authority = sni;
1✔
622
      } else {
623
        authority = grpcHandler.getAuthority();
1✔
624
      }
625
      ChannelHandler cth = new ClientTlsHandler(gnh, sslContext,
1✔
626
          authority, this.executor, negotiationLogger, handshakeCompleteRunnable, this,
627
          x509ExtendedTrustManager);
628
      return new WaitUntilActiveHandler(cth, negotiationLogger);
1✔
629
    }
630

631
    @Override
632
    public void close() {
633
      if (this.executorPool != null && this.executor != null) {
1✔
634
        this.executorPool.returnObject(this.executor);
1✔
635
      }
636
    }
1✔
637

638
    @VisibleForTesting
639
    boolean hasX509ExtendedTrustManager() {
640
      return x509ExtendedTrustManager != null;
1✔
641
    }
642
  }
643

644
  static final class ClientTlsHandler extends ProtocolNegotiationHandler {
645

646
    private final SslContext sslContext;
647
    private final String host;
648
    private final int port;
649
    private Executor executor;
650
    private final Optional<Runnable> handshakeCompleteRunnable;
651
    private final X509TrustManager x509TrustManager;
652
    private SSLEngine sslEngine;
653

654
    ClientTlsHandler(ChannelHandler next, SslContext sslContext, String authority,
655
        Executor executor, ChannelLogger negotiationLogger,
656
        Optional<Runnable> handshakeCompleteRunnable,
657
        ClientTlsProtocolNegotiator clientTlsProtocolNegotiator,
658
        X509TrustManager x509TrustManager) {
659
      super(next, negotiationLogger);
1✔
660
      this.sslContext = Preconditions.checkNotNull(sslContext, "sslContext");
1✔
661
      if (authority != null) {
1✔
662
        HostPort hostPort = parseAuthority(authority);
1✔
663
        this.host = hostPort.host;
1✔
664
        this.port = hostPort.port;
1✔
665
      } else {
1✔
666
        this.host = null;
1✔
667
        this.port = 0;
1✔
668
      }
669
      this.executor = executor;
1✔
670
      this.handshakeCompleteRunnable = handshakeCompleteRunnable;
1✔
671
      this.x509TrustManager = x509TrustManager;
1✔
672
    }
1✔
673

674
    @Override
675
    @IgnoreJRERequirement
676
    protected void handlerAdded0(ChannelHandlerContext ctx) {
677
      if (host != null) {
1✔
678
        sslEngine = sslContext.newEngine(ctx.alloc(), host, port);
1✔
679
      } else {
680
        sslEngine = sslContext.newEngine(ctx.alloc());
1✔
681
      }
682
      SSLParameters sslParams = sslEngine.getSSLParameters();
1✔
683
      sslParams.setEndpointIdentificationAlgorithm("HTTPS");
1✔
684
      sslEngine.setSSLParameters(sslParams);
1✔
685
      ctx.pipeline().addBefore(ctx.name(), /* name= */ null, this.executor != null
1✔
686
          ? new SslHandler(sslEngine, false, this.executor)
1✔
687
          : new SslHandler(sslEngine, false));
1✔
688
    }
1✔
689

690
    @Override
691
    protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws Exception {
692
      if (evt instanceof SslHandshakeCompletionEvent) {
1✔
693
        SslHandshakeCompletionEvent handshakeEvent = (SslHandshakeCompletionEvent) evt;
1✔
694
        if (handshakeEvent.isSuccess()) {
1✔
695
          SslHandler handler = ctx.pipeline().get(SslHandler.class);
1✔
696
          if (sslContext.applicationProtocolNegotiator().protocols()
1✔
697
              .contains(handler.applicationProtocol())) {
1✔
698
            // Successfully negotiated the protocol.
699
            logSslEngineDetails(Level.FINER, ctx, "TLS negotiation succeeded.", null);
1✔
700
            propagateTlsComplete(ctx, handler.engine().getSession());
1✔
701
          } else {
702
            Exception ex =
1✔
703
                unavailableException("Failed ALPN negotiation: Unable to find compatible protocol");
1✔
704
            logSslEngineDetails(Level.FINE, ctx, "TLS negotiation failed.", ex);
1✔
705
            if (handshakeCompleteRunnable.isPresent()) {
1✔
706
              handshakeCompleteRunnable.get().run();
×
707
            }
708
            ctx.fireExceptionCaught(ex);
1✔
709
          }
710
        } else {
1✔
711
          Throwable t = handshakeEvent.cause();
1✔
712
          if (t instanceof ClosedChannelException) {
1✔
713
            // On channelInactive(), SslHandler creates its own ClosedChannelException and
714
            // propagates it before the actual channelInactive(). So we assume here that any
715
            // such exception is from channelInactive() and emulate the normal behavior of
716
            // WriteBufferingAndExceptionHandler
717
            t = Status.UNAVAILABLE
1✔
718
                .withDescription("Connection closed while performing TLS negotiation")
1✔
719
                .withCause(t)
1✔
720
                .asRuntimeException();
1✔
721
          }
722
          if (handshakeCompleteRunnable.isPresent()) {
1✔
723
            handshakeCompleteRunnable.get().run();
×
724
          }
725
          ctx.fireExceptionCaught(t);
1✔
726
        }
727
      } else {
1✔
728
        super.userEventTriggered0(ctx, evt);
1✔
729
      }
730
    }
1✔
731

732
    private void propagateTlsComplete(ChannelHandlerContext ctx, SSLSession session) {
733
      Security security = new Security(new Tls(session));
1✔
734
      ProtocolNegotiationEvent existingPne = getProtocolNegotiationEvent();
1✔
735
      Attributes attrs = existingPne.getAttributes().toBuilder()
1✔
736
          .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
1✔
737
          .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, session)
1✔
738
          .set(GrpcAttributes.ATTR_AUTHORITY_VERIFIER, new X509AuthorityVerifier(
1✔
739
              sslEngine, x509TrustManager))
740
          .build();
1✔
741
      replaceProtocolNegotiationEvent(existingPne.withAttributes(attrs).withSecurity(security));
1✔
742
      if (handshakeCompleteRunnable.isPresent()) {
1✔
743
        handshakeCompleteRunnable.get().run();
×
744
      }
745
      fireProtocolNegotiationEvent(ctx);
1✔
746
    }
1✔
747
  }
748

749
  @VisibleForTesting
750
  static HostPort parseAuthority(String authority) {
751
    URI uri = GrpcUtil.authorityToUri(Preconditions.checkNotNull(authority, "authority"));
1✔
752
    String host;
753
    int port;
754
    if (uri.getHost() != null) {
1✔
755
      host = uri.getHost();
1✔
756
      port = uri.getPort();
1✔
757
    } else {
758
      /*
759
       * Implementation note: We pick -1 as the port here rather than deriving it from the
760
       * original socket address.  The SSL engine doesn't use this port number when contacting the
761
       * remote server, but rather it is used for other things like SSL Session caching.  When an
762
       * invalid authority is provided (like "bad_cert"), picking the original port and passing it
763
       * in would mean that the port might used under the assumption that it was correct.   By
764
       * using -1 here, it forces the SSL implementation to treat it as invalid.
765
       */
766
      host = authority;
1✔
767
      port = -1;
1✔
768
    }
769
    return new HostPort(host, port);
1✔
770
  }
771

772
  /**
773
   * Returns a {@link ProtocolNegotiator} that ensures the pipeline is set up so that TLS will
774
   * be negotiated, the {@code handler} is added and writes to the {@link io.netty.channel.Channel}
775
   * may happen immediately, even before the TLS Handshake is complete.
776
   *
777
   * @param executorPool a dedicated {@link Executor} pool for time-consuming TLS tasks
778
   */
779
  public static ProtocolNegotiator tls(SslContext sslContext,
780
      ObjectPool<? extends Executor> executorPool, Optional<Runnable> handshakeCompleteRunnable,
781
      X509TrustManager x509ExtendedTrustManager, String sni) {
782
    return new ClientTlsProtocolNegotiator(sslContext, executorPool, handshakeCompleteRunnable,
1✔
783
        x509ExtendedTrustManager, sni);
784
  }
785

786
  /**
787
   * Returns a {@link ProtocolNegotiator} that ensures the pipeline is set up so that TLS will
788
   * be negotiated, the {@code handler} is added and writes to the {@link io.netty.channel.Channel}
789
   * may happen immediately, even before the TLS Handshake is complete.
790
   */
791
  public static ProtocolNegotiator tls(SslContext sslContext,
792
      X509TrustManager x509ExtendedTrustManager) {
793
    return tls(sslContext, null, Optional.absent(), x509ExtendedTrustManager, null);
1✔
794
  }
795

796
  public static ProtocolNegotiator.ClientFactory tlsClientFactory(SslContext sslContext,
797
      X509TrustManager x509ExtendedTrustManager) {
798
    return new TlsProtocolNegotiatorClientFactory(sslContext, x509ExtendedTrustManager);
1✔
799
  }
800

801
  @VisibleForTesting
802
  static final class TlsProtocolNegotiatorClientFactory
803
      implements ProtocolNegotiator.ClientFactory {
804
    private final SslContext sslContext;
805
    private final X509TrustManager x509ExtendedTrustManager;
806

807
    public TlsProtocolNegotiatorClientFactory(SslContext sslContext,
808
                                              X509TrustManager x509ExtendedTrustManager) {
1✔
809
      this.sslContext = Preconditions.checkNotNull(sslContext, "sslContext");
1✔
810
      this.x509ExtendedTrustManager = x509ExtendedTrustManager;
1✔
811
    }
1✔
812

813
    @Override public ProtocolNegotiator newNegotiator() {
814
      return tls(sslContext, x509ExtendedTrustManager);
1✔
815
    }
816

817
    @Override public int getDefaultPort() {
818
      return GrpcUtil.DEFAULT_PORT_SSL;
1✔
819
    }
820
  }
821

822
  /** A tuple of (host, port). */
823
  @VisibleForTesting
824
  static final class HostPort {
825
    final String host;
826
    final int port;
827

828
    public HostPort(String host, int port) {
1✔
829
      this.host = host;
1✔
830
      this.port = port;
1✔
831
    }
1✔
832
  }
833

834
  /**
835
   * Returns a {@link ProtocolNegotiator} used for upgrading to HTTP/2 from HTTP/1.x.
836
   */
837
  public static ProtocolNegotiator plaintextUpgrade() {
838
    return new PlaintextUpgradeProtocolNegotiator();
1✔
839
  }
840

841
  public static ProtocolNegotiator.ClientFactory plaintextUpgradeClientFactory() {
842
    return new PlaintextUpgradeProtocolNegotiatorClientFactory();
×
843
  }
844

845
  private static final class PlaintextUpgradeProtocolNegotiatorClientFactory
846
      implements ProtocolNegotiator.ClientFactory {
847
    @Override public ProtocolNegotiator newNegotiator() {
848
      return plaintextUpgrade();
×
849
    }
850

851
    @Override public int getDefaultPort() {
852
      return GrpcUtil.DEFAULT_PORT_PLAINTEXT;
×
853
    }
854
  }
855

856
  static final class PlaintextUpgradeProtocolNegotiator implements ProtocolNegotiator {
1✔
857

858
    @Override
859
    public AsciiString scheme() {
860
      return Utils.HTTP;
×
861
    }
862

863
    @Override
864
    public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
865
      ChannelHandler upgradeHandler =
1✔
866
          new Http2UpgradeAndGrpcHandler(grpcHandler.getAuthority(), grpcHandler);
1✔
867
      ChannelHandler plaintextHandler =
1✔
868
          new PlaintextHandler(upgradeHandler, grpcHandler.getNegotiationLogger());
1✔
869
      return new WaitUntilActiveHandler(plaintextHandler, grpcHandler.getNegotiationLogger());
1✔
870
    }
871

872
    @Override
873
    public void close() {}
1✔
874
  }
875

876
  /**
877
   * Acts as a combination of Http2Upgrade and {@link GrpcNegotiationHandler}.  Unfortunately,
878
   * this negotiator doesn't follow the pattern of "just one handler doing negotiation at a time."
879
   * This is due to the tight coupling between the upgrade handler and the HTTP/2 handler.
880
   */
881
  static final class Http2UpgradeAndGrpcHandler extends ChannelInboundHandlerAdapter {
882

883
    private final String authority;
884
    private final GrpcHttp2ConnectionHandler next;
885
    private final ChannelLogger negotiationLogger;
886

887
    private ProtocolNegotiationEvent pne;
888

889
    Http2UpgradeAndGrpcHandler(String authority, GrpcHttp2ConnectionHandler next) {
1✔
890
      this.authority = Preconditions.checkNotNull(authority, "authority");
1✔
891
      this.next = Preconditions.checkNotNull(next, "next");
1✔
892
      this.negotiationLogger = next.getNegotiationLogger();
1✔
893
    }
1✔
894

895
    @Override
896
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
897
      negotiationLogger.log(ChannelLogLevel.INFO, "Http2Upgrade started");
1✔
898
      HttpClientCodec httpClientCodec = new HttpClientCodec();
1✔
899
      ctx.pipeline().addBefore(ctx.name(), null, httpClientCodec);
1✔
900

901
      Http2ClientUpgradeCodec upgradeCodec = new Http2ClientUpgradeCodec(next);
1✔
902
      HttpClientUpgradeHandler upgrader =
1✔
903
          new HttpClientUpgradeHandler(httpClientCodec, upgradeCodec, /*maxContentLength=*/ 1000);
904
      ctx.pipeline().addBefore(ctx.name(), null, upgrader);
1✔
905

906
      // Trigger the HTTP/1.1 plaintext upgrade protocol by issuing an HTTP request
907
      // which causes the upgrade headers to be added
908
      DefaultHttpRequest upgradeTrigger =
1✔
909
          new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
910
      upgradeTrigger.headers().add(HttpHeaderNames.HOST, authority);
1✔
911
      ctx.writeAndFlush(upgradeTrigger).addListener(
1✔
912
          ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
913
      super.handlerAdded(ctx);
1✔
914
    }
1✔
915

916
    @Override
917
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
918
      if (evt instanceof ProtocolNegotiationEvent) {
1✔
919
        checkState(pne == null, "negotiation already started");
1✔
920
        pne = (ProtocolNegotiationEvent) evt;
1✔
921
      } else if (evt == HttpClientUpgradeHandler.UpgradeEvent.UPGRADE_SUCCESSFUL) {
1✔
922
        checkState(pne != null, "negotiation not yet complete");
1✔
923
        negotiationLogger.log(ChannelLogLevel.INFO, "Http2Upgrade finished");
1✔
924
        ctx.pipeline().remove(ctx.name());
1✔
925
        next.handleProtocolNegotiationCompleted(pne.getAttributes(), pne.getSecurity());
1✔
926
      } else if (evt == HttpClientUpgradeHandler.UpgradeEvent.UPGRADE_REJECTED) {
1✔
927
        ctx.fireExceptionCaught(unavailableException("HTTP/2 upgrade rejected"));
×
928
      } else {
929
        super.userEventTriggered(ctx, evt);
1✔
930
      }
931
    }
1✔
932
  }
933

934
  /**
935
   * Returns a {@link io.netty.channel.ChannelHandler} that ensures that the {@code handler} is
936
   * added to the pipeline writes to the {@link io.netty.channel.Channel} may happen immediately,
937
   * even before it is active.
938
   */
939
  public static ProtocolNegotiator plaintext() {
940
    return new PlaintextProtocolNegotiator();
1✔
941
  }
942

943
  public static ProtocolNegotiator.ClientFactory plaintextClientFactory() {
944
    return new PlaintextProtocolNegotiatorClientFactory();
1✔
945
  }
946

947
  @VisibleForTesting
948
  static final class PlaintextProtocolNegotiatorClientFactory
1✔
949
      implements ProtocolNegotiator.ClientFactory {
950
    @Override public ProtocolNegotiator newNegotiator() {
951
      return plaintext();
1✔
952
    }
953

954
    @Override public int getDefaultPort() {
955
      return GrpcUtil.DEFAULT_PORT_PLAINTEXT;
1✔
956
    }
957
  }
958

959
  private static RuntimeException unavailableException(String msg) {
960
    return Status.UNAVAILABLE.withDescription(msg).asRuntimeException();
1✔
961
  }
962

963
  @VisibleForTesting
964
  static void logSslEngineDetails(Level level, ChannelHandlerContext ctx, String msg,
965
      @Nullable Throwable t) {
966
    if (!log.isLoggable(level)) {
1✔
967
      return;
1✔
968
    }
969

970
    SslHandler sslHandler = ctx.pipeline().get(SslHandler.class);
1✔
971
    SSLEngine engine = sslHandler.engine();
1✔
972

973
    StringBuilder builder = new StringBuilder(msg);
1✔
974
    builder.append("\nSSLEngine Details: [\n");
1✔
975
    if (engine instanceof OpenSslEngine) {
1✔
976
      builder.append("    OpenSSL, ");
1✔
977
      builder.append("Version: 0x").append(Integer.toHexString(OpenSsl.version()));
1✔
978
      builder.append(" (").append(OpenSsl.versionString()).append("), ");
1✔
979
      builder.append("ALPN supported: ").append(SslProvider.isAlpnSupported(SslProvider.OPENSSL));
1✔
980
    } else if (JettyTlsUtil.isJettyAlpnConfigured()) {
×
981
      builder.append("    Jetty ALPN");
×
982
    } else if (JettyTlsUtil.isJettyNpnConfigured()) {
×
983
      builder.append("    Jetty NPN");
×
984
    } else if (JettyTlsUtil.isJava9AlpnAvailable()) {
×
985
      builder.append("    JDK9 ALPN");
×
986
    }
987
    builder.append("\n    TLS Protocol: ");
1✔
988
    builder.append(engine.getSession().getProtocol());
1✔
989
    builder.append("\n    Application Protocol: ");
1✔
990
    builder.append(sslHandler.applicationProtocol());
1✔
991
    builder.append("\n    Need Client Auth: " );
1✔
992
    builder.append(engine.getNeedClientAuth());
1✔
993
    builder.append("\n    Want Client Auth: ");
1✔
994
    builder.append(engine.getWantClientAuth());
1✔
995
    builder.append("\n    Supported protocols=");
1✔
996
    builder.append(Arrays.toString(engine.getSupportedProtocols()));
1✔
997
    builder.append("\n    Enabled protocols=");
1✔
998
    builder.append(Arrays.toString(engine.getEnabledProtocols()));
1✔
999
    builder.append("\n    Supported ciphers=");
1✔
1000
    builder.append(Arrays.toString(engine.getSupportedCipherSuites()));
1✔
1001
    builder.append("\n    Enabled ciphers=");
1✔
1002
    builder.append(Arrays.toString(engine.getEnabledCipherSuites()));
1✔
1003
    builder.append("\n]");
1✔
1004

1005
    log.log(level, builder.toString(), t);
1✔
1006
  }
1✔
1007

1008
  /**
1009
   * Adapts a {@link ProtocolNegotiationEvent} to the {@link GrpcHttp2ConnectionHandler}.
1010
   */
1011
  static final class GrpcNegotiationHandler extends ChannelInboundHandlerAdapter {
1012
    private final GrpcHttp2ConnectionHandler next;
1013

1014
    public GrpcNegotiationHandler(GrpcHttp2ConnectionHandler next) {
1✔
1015
      this.next = Preconditions.checkNotNull(next, "next");
1✔
1016
    }
1✔
1017

1018
    @Override
1019
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
1020
      if (evt instanceof ProtocolNegotiationEvent) {
1✔
1021
        ProtocolNegotiationEvent protocolNegotiationEvent = (ProtocolNegotiationEvent) evt;
1✔
1022
        ctx.pipeline().replace(ctx.name(), null, next);
1✔
1023
        next.handleProtocolNegotiationCompleted(
1✔
1024
            protocolNegotiationEvent.getAttributes(), protocolNegotiationEvent.getSecurity());
1✔
1025
      } else {
1✔
1026
        super.userEventTriggered(ctx, evt);
×
1027
      }
1028
    }
1✔
1029
  }
1030

1031
  /*
1032
   * Common {@link ProtocolNegotiator}s used by gRPC.  Protocol negotiation follows a pattern to
1033
   * simplify the pipeline.   The pipeline should look like:
1034
   *
1035
   * 1.  {@link ProtocolNegotiator#newHandler() PN.H}, created.
1036
   * 2.  [Tail], {@link WriteBufferingAndExceptionHandler WBAEH}, [Head]
1037
   * 3.  [Tail], WBAEH, PN.H, [Head]
1038
   *
1039
   * <p>Typically, PN.H with be an instance of {@link InitHandler IH}, which is a trivial handler
1040
   * that can return the {@code scheme()} of the negotiation.  IH, and each handler after,
1041
   * replaces itself with a "next" handler once its part of negotiation is complete.  This keeps
1042
   * the pipeline small, and limits the interaction between handlers.
1043
   *
1044
   * <p>Additionally, each handler may fire a {@link ProtocolNegotiationEvent PNE} just after
1045
   * replacing itself.  Handlers should capture user events of type PNE, and re-trigger the events
1046
   * once that handler's part of negotiation is complete.  This can be seen in the
1047
   * {@link WaitUntilActiveHandler WUAH}, which waits until the channel is active.  Once active, it
1048
   * replaces itself with the next handler, and fires a PNE containing the addresses.  Continuing
1049
   * with IH and WUAH:
1050
   *
1051
   * 3.  [Tail], WBAEH, IH, [Head]
1052
   * 4.  [Tail], WBAEH, WUAH, [Head]
1053
   * 5.  [Tail], WBAEH, {@link GrpcNegotiationHandler}, [Head]
1054
   * 6a. [Tail], WBAEH, {@link GrpcHttp2ConnectionHandler GHCH}, [Head]
1055
   * 6b. [Tail], GHCH, [Head]
1056
   */
1057

1058
  /**
1059
   * A negotiator that only does plain text.
1060
   */
1061
  static final class PlaintextProtocolNegotiator implements ProtocolNegotiator {
1✔
1062

1063
    @Override
1064
    public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
1065
      ChannelHandler grpcNegotiationHandler = new GrpcNegotiationHandler(grpcHandler);
1✔
1066
      ChannelHandler plaintextHandler =
1✔
1067
          new PlaintextHandler(grpcNegotiationHandler, grpcHandler.getNegotiationLogger());
1✔
1068
      ChannelHandler activeHandler = new WaitUntilActiveHandler(plaintextHandler,
1✔
1069
          grpcHandler.getNegotiationLogger());
1✔
1070
      return activeHandler;
1✔
1071
    }
1072

1073
    @Override
1074
    public void close() {}
1✔
1075

1076
    @Override
1077
    public AsciiString scheme() {
1078
      return Utils.HTTP;
1✔
1079
    }
1080
  }
1081

1082
  static final class PlaintextHandler extends ProtocolNegotiationHandler {
1083
    PlaintextHandler(ChannelHandler next, ChannelLogger negotiationLogger) {
1084
      super(next, negotiationLogger);
1✔
1085
    }
1✔
1086

1087
    @Override
1088
    protected void protocolNegotiationEventTriggered(ChannelHandlerContext ctx) {
1089
      ProtocolNegotiationEvent existingPne = getProtocolNegotiationEvent();
1✔
1090
      Attributes attrs = existingPne.getAttributes().toBuilder()
1✔
1091
              .set(GrpcAttributes.ATTR_AUTHORITY_VERIFIER, (authority) -> Status.OK)
1✔
1092
              .build();
1✔
1093
      replaceProtocolNegotiationEvent(existingPne.withAttributes(attrs));
1✔
1094
      fireProtocolNegotiationEvent(ctx);
1✔
1095
    }
1✔
1096
  }
1097

1098
  /**
1099
   * Waits for the channel to be active, and then installs the next Handler.  Using this allows
1100
   * subsequent handlers to assume the channel is active and ready to send.  Additionally, this a
1101
   * {@link ProtocolNegotiationEvent}, with the connection addresses.
1102
   */
1103
  static final class WaitUntilActiveHandler extends ProtocolNegotiationHandler {
1104

1105
    boolean protocolNegotiationEventReceived;
1106

1107
    WaitUntilActiveHandler(ChannelHandler next, ChannelLogger negotiationLogger) {
1108
      super(next, negotiationLogger);
1✔
1109
    }
1✔
1110

1111
    @Override
1112
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
1113
      if (protocolNegotiationEventReceived) {
1✔
1114
        replaceOnActive(ctx);
1✔
1115
        fireProtocolNegotiationEvent(ctx);
1✔
1116
      }
1117
      // Still propagate channelActive to the new handler.
1118
      super.channelActive(ctx);
1✔
1119
    }
1✔
1120

1121
    @Override
1122
    protected void protocolNegotiationEventTriggered(ChannelHandlerContext ctx) {
1123
      protocolNegotiationEventReceived = true;
1✔
1124
      if (ctx.channel().isActive()) {
1✔
1125
        replaceOnActive(ctx);
1✔
1126
        fireProtocolNegotiationEvent(ctx);
1✔
1127
      }
1128
    }
1✔
1129

1130
    private void replaceOnActive(ChannelHandlerContext ctx) {
1131
      ProtocolNegotiationEvent existingPne = getProtocolNegotiationEvent();
1✔
1132
      Attributes attrs = existingPne.getAttributes().toBuilder()
1✔
1133
          .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, ctx.channel().localAddress())
1✔
1134
          .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress())
1✔
1135
          // Later handlers are expected to overwrite this.
1136
          .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.NONE)
1✔
1137
          .build();
1✔
1138
      replaceProtocolNegotiationEvent(existingPne.withAttributes(attrs));
1✔
1139
    }
1✔
1140
  }
1141

1142
  /**
1143
   * ProtocolNegotiationHandler is a convenience handler that makes it easy to follow the rules for
1144
   * protocol negotiation.  Handlers should strongly consider extending this handler.
1145
   */
1146
  static class ProtocolNegotiationHandler extends ChannelDuplexHandler {
1147

1148
    private final ChannelHandler next;
1149
    private final String negotiatorName;
1150
    private ProtocolNegotiationEvent pne;
1151
    private final ChannelLogger negotiationLogger;
1152

1153
    protected ProtocolNegotiationHandler(ChannelHandler next, String negotiatorName,
1154
        ChannelLogger negotiationLogger) {
×
1155
      this.next = Preconditions.checkNotNull(next, "next");
×
1156
      this.negotiatorName = negotiatorName;
×
1157
      this.negotiationLogger = Preconditions.checkNotNull(negotiationLogger, "negotiationLogger");
×
1158
    }
×
1159

1160
    protected ProtocolNegotiationHandler(ChannelHandler next, ChannelLogger negotiationLogger) {
1✔
1161
      this.next = Preconditions.checkNotNull(next, "next");
1✔
1162
      this.negotiatorName = getClass().getSimpleName().replace("Handler", "");
1✔
1163
      this.negotiationLogger = Preconditions.checkNotNull(negotiationLogger, "negotiationLogger");
1✔
1164
    }
1✔
1165

1166
    @Override
1167
    public final void handlerAdded(ChannelHandlerContext ctx) throws Exception {
1168
      negotiationLogger.log(ChannelLogLevel.DEBUG, "{0} started", negotiatorName);
1✔
1169
      handlerAdded0(ctx);
1✔
1170
    }
1✔
1171

1172
    @ForOverride
1173
    protected void handlerAdded0(ChannelHandlerContext ctx) throws Exception {
1174
      super.handlerAdded(ctx);
1✔
1175
    }
1✔
1176

1177
    @Override
1178
    public final void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
1179
      if (evt instanceof ProtocolNegotiationEvent) {
1✔
1180
        checkState(pne == null, "pre-existing negotiation: %s < %s", pne, evt);
1✔
1181
        pne = (ProtocolNegotiationEvent) evt;
1✔
1182
        protocolNegotiationEventTriggered(ctx);
1✔
1183
      } else {
1184
        userEventTriggered0(ctx, evt);
1✔
1185
      }
1186
    }
1✔
1187

1188
    protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws Exception {
1189
      super.userEventTriggered(ctx, evt);
1✔
1190
    }
1✔
1191

1192
    @ForOverride
1193
    protected void protocolNegotiationEventTriggered(ChannelHandlerContext ctx) {
1194
      // no-op
1195
    }
1✔
1196

1197
    protected final ProtocolNegotiationEvent getProtocolNegotiationEvent() {
1198
      checkState(pne != null, "previous protocol negotiation event hasn't triggered");
1✔
1199
      return pne;
1✔
1200
    }
1201

1202
    protected final void replaceProtocolNegotiationEvent(ProtocolNegotiationEvent pne) {
1203
      checkState(this.pne != null, "previous protocol negotiation event hasn't triggered");
1✔
1204
      this.pne = Preconditions.checkNotNull(pne);
1✔
1205
    }
1✔
1206

1207
    protected final void fireProtocolNegotiationEvent(ChannelHandlerContext ctx) {
1208
      checkState(pne != null, "previous protocol negotiation event hasn't triggered");
1✔
1209
      negotiationLogger.log(ChannelLogLevel.INFO, "{0} completed", negotiatorName);
1✔
1210
      ctx.pipeline().replace(ctx.name(), /* newName= */ null, next);
1✔
1211
      ctx.fireUserEventTriggered(pne);
1✔
1212
    }
1✔
1213
  }
1214

1215
  static final class SslEngineWrapper extends NoopSslEngine {
1216
    private final SSLEngine sslEngine;
1217
    private final String peerHost;
1218

1219
    SslEngineWrapper(SSLEngine sslEngine, String peerHost) {
1✔
1220
      this.sslEngine = sslEngine;
1✔
1221
      this.peerHost = peerHost;
1✔
1222
    }
1✔
1223

1224
    @Override
1225
    public String getPeerHost() {
1226
      return peerHost;
×
1227
    }
1228

1229
    @Override
1230
    public SSLSession getHandshakeSession() {
1231
      return new FakeSslSession(peerHost);
1✔
1232
    }
1233

1234
    @Override
1235
    public SSLParameters getSSLParameters() {
1236
      return sslEngine.getSSLParameters();
1✔
1237
    }
1238
  }
1239

1240
  static final class FakeSslSession extends NoopSslSession {
1241
    private final String peerHost;
1242

1243
    FakeSslSession(String peerHost) {
1✔
1244
      this.peerHost = peerHost;
1✔
1245
    }
1✔
1246

1247
    @Override
1248
    public String getPeerHost() {
1249
      return peerHost;
1✔
1250
    }
1251
  }
1252
}
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