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

grpc / grpc-java / #20414

20 Aug 2026 06:02AM UTC coverage: 89.192% (-0.008%) from 89.2%
#20414

push

github

web-flow
netty: Support never-indexed metadata keys (#12976)

Add NettyChannelBuilder.neverIndexMetadataKey() and
neverIndexMetadataKeys() so callers can mark selected outbound metadata
keys for HPACK's never-indexed literal representation.

High-cardinality metadata values provide little compression benefit and
can churn the server's dynamic HPACK table. Keeping them out of the
table avoids unnecessary insertion and eviction work while preserving
dynamic indexing for other headers.

Propagate an immutable set of normalized metadata names through the
client transport and use it in Netty's HPACK sensitivity detector. Add
unit and interoperability coverage.

Generated with AI using OpenAI Codex (GPT-5).

Co-authored-by: Codex <noreply@openai.com>

38580 of 43255 relevant lines covered (89.19%)

0.89 hits per line

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

92.47
/../netty/src/main/java/io/grpc/netty/NettyServerTransport.java
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.netty;
18

19
import com.google.common.annotations.VisibleForTesting;
20
import com.google.common.base.MoreObjects;
21
import com.google.common.base.Preconditions;
22
import com.google.common.collect.ImmutableList;
23
import com.google.common.util.concurrent.ListenableFuture;
24
import com.google.common.util.concurrent.SettableFuture;
25
import io.grpc.Attributes;
26
import io.grpc.InternalChannelz.SocketStats;
27
import io.grpc.InternalLogId;
28
import io.grpc.MetricRecorder;
29
import io.grpc.ServerStreamTracer;
30
import io.grpc.Status;
31
import io.grpc.internal.ServerTransport;
32
import io.grpc.internal.ServerTransportListener;
33
import io.grpc.internal.TransportTracer;
34
import io.netty.channel.Channel;
35
import io.netty.channel.ChannelFuture;
36
import io.netty.channel.ChannelFutureListener;
37
import io.netty.channel.ChannelHandler;
38
import io.netty.channel.ChannelPromise;
39
import io.netty.util.AsciiString;
40
import io.netty.util.concurrent.Future;
41
import io.netty.util.concurrent.GenericFutureListener;
42
import java.io.IOException;
43
import java.net.SocketAddress;
44
import java.net.SocketException;
45
import java.util.List;
46
import java.util.Set;
47
import java.util.concurrent.ScheduledExecutorService;
48
import java.util.logging.Level;
49
import java.util.logging.Logger;
50

51
/**
52
 * The Netty-based server transport.
53
 */
54
class NettyServerTransport implements ServerTransport {
55
  // connectionLog is for connection related messages only
56
  private static final Logger connectionLog = Logger.getLogger(
1 ✔
57
      String.format("%s.connections", NettyServerTransport.class.getName()));
1 ✔
58

59
  // Some exceptions are not very useful and add too much noise to the log
60
  private static final ImmutableList<String> QUIET_EXCEPTIONS = ImmutableList.of(
1 ✔
61
      "NativeIoException" /* Netty exceptions */);
62

63
  private final InternalLogId logId;
64
  private final Channel channel;
65
  private final ChannelPromise channelUnused;
66
  private final ProtocolNegotiator protocolNegotiator;
67
  private final int maxStreams;
68
  // only accessed from channel event loop
69
  private NettyServerHandler grpcHandler;
70
  private ServerTransportListener listener;
71
  private boolean terminated;
72
  private final boolean autoFlowControl;
73
  private final int flowControlWindow;
74
  private final Set<AsciiString> neverIndexedMetadataKeys;
75
  private final int maxMessageSize;
76
  private final int maxHeaderListSize;
77
  private final int softLimitHeaderListSize;
78
  private final long keepAliveTimeInNanos;
79
  private final long keepAliveTimeoutInNanos;
80
  private final long maxConnectionIdleInNanos;
81
  private final long maxConnectionAgeInNanos;
82
  private final long maxConnectionAgeGraceInNanos;
83
  private final boolean permitKeepAliveWithoutCalls;
84
  private final long permitKeepAliveTimeInNanos;
85
  private final int maxRstCount;
86
  private final long maxRstPeriodNanos;
87
  private final Attributes eagAttributes;
88
  private final MetricRecorder metricRecorder;
89
  private final List<? extends ServerStreamTracer.Factory> streamTracerFactories;
90
  private final TransportTracer transportTracer;
91

92
  NettyServerTransport(
93
      Channel channel,
94
      ChannelPromise channelUnused,
95
      ProtocolNegotiator protocolNegotiator,
96
      List<? extends ServerStreamTracer.Factory> streamTracerFactories,
97
      TransportTracer transportTracer,
98
      int maxStreams,
99
      boolean autoFlowControl,
100
      int flowControlWindow,
101
      Set<AsciiString> neverIndexedMetadataKeys,
102
      int maxMessageSize,
103
      int maxHeaderListSize,
104
      int softLimitHeaderListSize,
105
      long keepAliveTimeInNanos,
106
      long keepAliveTimeoutInNanos,
107
      long maxConnectionIdleInNanos,
108
      long maxConnectionAgeInNanos,
109
      long maxConnectionAgeGraceInNanos,
110
      boolean permitKeepAliveWithoutCalls,
111
      long permitKeepAliveTimeInNanos,
112
      int maxRstCount,
113
      long maxRstPeriodNanos,
114
      Attributes eagAttributes,
115
      MetricRecorder metricRecorder) {
1 ✔
116
    this.channel = Preconditions.checkNotNull(channel, "channel");
1 ✔
117
    this.channelUnused = channelUnused;
1 ✔
118
    this.protocolNegotiator = Preconditions.checkNotNull(protocolNegotiator, "protocolNegotiator");
1 ✔
119
    this.streamTracerFactories =
1 ✔
120
        Preconditions.checkNotNull(streamTracerFactories, "streamTracerFactories");
1 ✔
121
    this.transportTracer = Preconditions.checkNotNull(transportTracer, "transportTracer");
1 ✔
122
    this.maxStreams = maxStreams;
1 ✔
123
    this.autoFlowControl = autoFlowControl;
1 ✔
124
    this.flowControlWindow = flowControlWindow;
1 ✔
125
    this.neverIndexedMetadataKeys =
1 ✔
126
        Preconditions.checkNotNull(neverIndexedMetadataKeys, "neverIndexedMetadataKeys");
1 ✔
127
    this.maxMessageSize = maxMessageSize;
1 ✔
128
    this.maxHeaderListSize = maxHeaderListSize;
1 ✔
129
    this.softLimitHeaderListSize = softLimitHeaderListSize;
1 ✔
130
    this.keepAliveTimeInNanos = keepAliveTimeInNanos;
1 ✔
131
    this.keepAliveTimeoutInNanos = keepAliveTimeoutInNanos;
1 ✔
132
    this.maxConnectionIdleInNanos = maxConnectionIdleInNanos;
1 ✔
133
    this.maxConnectionAgeInNanos = maxConnectionAgeInNanos;
1 ✔
134
    this.maxConnectionAgeGraceInNanos = maxConnectionAgeGraceInNanos;
1 ✔
135
    this.permitKeepAliveWithoutCalls = permitKeepAliveWithoutCalls;
1 ✔
136
    this.permitKeepAliveTimeInNanos = permitKeepAliveTimeInNanos;
1 ✔
137
    this.maxRstCount = maxRstCount;
1 ✔
138
    this.maxRstPeriodNanos = maxRstPeriodNanos;
1 ✔
139
    this.eagAttributes = Preconditions.checkNotNull(eagAttributes, "eagAttributes");
1 ✔
140
    this.metricRecorder = metricRecorder;
1 ✔
141
    SocketAddress remote = channel.remoteAddress();
1 ✔
142
    this.logId = InternalLogId.allocate(getClass(), remote != null ? remote.toString() : null);
1 ✔
143
  }
1 ✔
144

145
  public void start(ServerTransportListener listener) {
146
    Preconditions.checkState(this.listener == null, "Handler already registered");
1 ✔
147
    this.listener = listener;
1 ✔
148

149
    // Create the Netty handler for the pipeline.
150
    grpcHandler = createHandler(listener, channelUnused);
1 ✔
151

152
    // Notify when the channel closes.
153
    final class TerminationNotifier implements ChannelFutureListener {
1 ✔
154
      boolean done;
155

156
      @Override
157
      public void operationComplete(ChannelFuture future) throws Exception {
158
        if (!done) {
1 ✔
159
          done = true;
1 ✔
160
          notifyTerminated(grpcHandler.connectionError());
1 ✔
161
        }
162
      }
1 ✔
163
    }
164

165
    ChannelHandler negotiationHandler = protocolNegotiator.newHandler(grpcHandler);
1 ✔
166
    ChannelHandler bufferingHandler = new WriteBufferingAndExceptionHandler(negotiationHandler);
1 ✔
167

168
    ChannelFutureListener terminationNotifier = new TerminationNotifier();
1 ✔
169
    channelUnused.addListener(terminationNotifier);
1 ✔
170
    channel.closeFuture().addListener(terminationNotifier);
1 ✔
171

172
    channel.pipeline().addLast(bufferingHandler);
1 ✔
173
  }
1 ✔
174

175
  @Override
176
  public ScheduledExecutorService getScheduledExecutorService() {
177
    return channel.eventLoop();
1 ✔
178
  }
179

180
  @Override
181
  public void shutdown() {
182
    if (channel.isOpen()) {
1 ✔
183
      channel.close();
1 ✔
184
    }
185
  }
1 ✔
186

187
  @Override
188
  public void shutdownNow(Status reason) {
189
    if (channel.isOpen()) {
1 ✔
190
      channel.writeAndFlush(new ForcefulCloseCommand(reason));
1 ✔
191
    }
192
  }
1 ✔
193

194
  @Override
195
  public InternalLogId getLogId() {
196
    return logId;
1 ✔
197
  }
198

199
  /**
200
   * For testing purposes only.
201
   */
202
  Channel channel() {
203
    return channel;
1 ✔
204
  }
205

206
  /**
207
   * Accepts a throwable and returns the appropriate logging level. Uninteresting exceptions
208
   * should not clutter the log.
209
   */
210
  @VisibleForTesting
211
  static Level getLogLevel(Throwable t) {
212
    if (t.getClass().equals(IOException.class)
1 ✔
213
        || t.getClass().equals(SocketException.class)
1 ✔
214
        || QUIET_EXCEPTIONS.contains(t.getClass().getSimpleName())) {
1 ✔
215
      return Level.FINE;
1 ✔
216
    }
217
    return Level.INFO;
1 ✔
218
  }
219

220
  private void notifyTerminated(Throwable t) {
221
    if (t != null) {
1 ✔
222
      connectionLog.log(getLogLevel(t), "Transport failed", t);
1 ✔
223
    }
224
    if (!terminated) {
1 ✔
225
      terminated = true;
1 ✔
226
      listener.transportTerminated();
1 ✔
227
    }
228
  }
1 ✔
229

230
  @Override
231
  public ListenableFuture<SocketStats> getStats() {
232
    final SettableFuture<SocketStats> result = SettableFuture.create();
1 ✔
233
    if (channel.eventLoop().inEventLoop()) {
1 ✔
234
      // This is necessary, otherwise we will block forever if we get the future from inside
235
      // the event loop.
236
      result.set(getStatsHelper(channel));
×
237
      return result;
×
238
    }
239
    channel.eventLoop().submit(
1 ✔
240
        new Runnable() {
1 ✔
241
          @Override
242
          public void run() {
243
            result.set(getStatsHelper(channel));
1 ✔
244
          }
1 ✔
245
        })
246
        .addListener(
1 ✔
247
            new GenericFutureListener<Future<Object>>() {
1 ✔
248
              @Override
249
              public void operationComplete(Future<Object> future) throws Exception {
250
                if (!future.isSuccess()) {
1 ✔
251
                  result.setException(future.cause());
×
252
                }
253
              }
1 ✔
254
            });
255
    return result;
1 ✔
256
  }
257

258
  private SocketStats getStatsHelper(Channel ch) {
259
    Preconditions.checkState(ch.eventLoop().inEventLoop());
1 ✔
260
    return new SocketStats(
1 ✔
261
        transportTracer.getStats(),
1 ✔
262
        channel.localAddress(),
1 ✔
263
        channel.remoteAddress(),
1 ✔
264
        Utils.getSocketOptions(ch),
1 ✔
265
        grpcHandler == null ? null : grpcHandler.getSecurityInfo());
1 ✔
266

267
  }
268

269
  @Override
270
  public String toString() {
271
    return MoreObjects.toStringHelper(this)
×
272
        .add("logId", logId.getId())
×
273
        .add("channel", channel)
×
274
        .toString();
×
275
  }
276

277
  /**
278
   * Creates the Netty handler to be used in the channel pipeline.
279
   */
280
  private NettyServerHandler createHandler(
281
      ServerTransportListener transportListener, ChannelPromise channelUnused) {
282
    return NettyServerHandler.newHandler(
1 ✔
283
        transportListener,
284
        channelUnused,
285
        streamTracerFactories,
286
        transportTracer,
287
        maxStreams,
288
        autoFlowControl,
289
        flowControlWindow,
290
        neverIndexedMetadataKeys,
291
        maxHeaderListSize,
292
        softLimitHeaderListSize,
293
        maxMessageSize,
294
        keepAliveTimeInNanos,
295
        keepAliveTimeoutInNanos,
296
        maxConnectionIdleInNanos,
297
        maxConnectionAgeInNanos,
298
        maxConnectionAgeGraceInNanos,
299
        permitKeepAliveWithoutCalls,
300
        permitKeepAliveTimeInNanos,
301
        maxRstCount,
302
        maxRstPeriodNanos,
303
        eagAttributes,
304
        metricRecorder);
305
  }
306
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc