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

grpc / grpc-java / #20383

30 Jul 2026 04:33PM UTC coverage: 89.149% (-0.005%) from 89.154%
#20383

push

github

ejona86
api: Better explain the executors and how to configure them

38310 of 42973 relevant lines covered (89.15%)

0.89 hits per line

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

90.64
/../netty/src/main/java/io/grpc/netty/NettyServerHandler.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 static com.google.common.base.Preconditions.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21
import static io.grpc.internal.GrpcUtil.SERVER_KEEPALIVE_TIME_NANOS_DISABLED;
22
import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE;
23
import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_AGE_NANOS_DISABLED;
24
import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_IDLE_NANOS_DISABLED;
25
import static io.grpc.netty.Utils.CONTENT_TYPE_HEADER;
26
import static io.grpc.netty.Utils.HTTP_METHOD;
27
import static io.grpc.netty.Utils.TE_HEADER;
28
import static io.grpc.netty.Utils.TE_TRAILERS;
29
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
30
import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
31
import static io.netty.handler.codec.http2.DefaultHttp2LocalFlowController.DEFAULT_WINDOW_UPDATE_RATIO;
32
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.AUTHORITY;
33

34
import com.google.common.annotations.VisibleForTesting;
35
import com.google.common.base.Preconditions;
36
import com.google.common.base.Strings;
37
import com.google.common.base.Ticker;
38
import io.grpc.Attributes;
39
import io.grpc.ChannelLogger;
40
import io.grpc.ChannelLogger.ChannelLogLevel;
41
import io.grpc.InternalChannelz;
42
import io.grpc.InternalMetadata;
43
import io.grpc.InternalStatus;
44
import io.grpc.Metadata;
45
import io.grpc.MetricRecorder;
46
import io.grpc.ServerStreamTracer;
47
import io.grpc.Status;
48
import io.grpc.internal.GrpcUtil;
49
import io.grpc.internal.KeepAliveEnforcer;
50
import io.grpc.internal.KeepAliveManager;
51
import io.grpc.internal.LogExceptionRunnable;
52
import io.grpc.internal.MaxConnectionIdleManager;
53
import io.grpc.internal.ServerTransportListener;
54
import io.grpc.internal.StatsTraceContext;
55
import io.grpc.internal.TransportTracer;
56
import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2ServerHeadersDecoder;
57
import io.netty.buffer.ByteBuf;
58
import io.netty.buffer.ByteBufUtil;
59
import io.netty.buffer.Unpooled;
60
import io.netty.channel.ChannelFuture;
61
import io.netty.channel.ChannelFutureListener;
62
import io.netty.channel.ChannelHandlerContext;
63
import io.netty.channel.ChannelPromise;
64
import io.netty.handler.codec.http.HttpHeaderNames;
65
import io.netty.handler.codec.http2.DecoratingHttp2ConnectionEncoder;
66
import io.netty.handler.codec.http2.DecoratingHttp2FrameWriter;
67
import io.netty.handler.codec.http2.DefaultHttp2Connection;
68
import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder;
69
import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder;
70
import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
71
import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
72
import io.netty.handler.codec.http2.DefaultHttp2Headers;
73
import io.netty.handler.codec.http2.DefaultHttp2HeadersEncoder;
74
import io.netty.handler.codec.http2.DefaultHttp2LocalFlowController;
75
import io.netty.handler.codec.http2.DefaultHttp2RemoteFlowController;
76
import io.netty.handler.codec.http2.EmptyHttp2Headers;
77
import io.netty.handler.codec.http2.Http2Connection;
78
import io.netty.handler.codec.http2.Http2ConnectionAdapter;
79
import io.netty.handler.codec.http2.Http2ConnectionDecoder;
80
import io.netty.handler.codec.http2.Http2ConnectionEncoder;
81
import io.netty.handler.codec.http2.Http2Error;
82
import io.netty.handler.codec.http2.Http2Exception;
83
import io.netty.handler.codec.http2.Http2Exception.StreamException;
84
import io.netty.handler.codec.http2.Http2FrameAdapter;
85
import io.netty.handler.codec.http2.Http2FrameLogger;
86
import io.netty.handler.codec.http2.Http2FrameReader;
87
import io.netty.handler.codec.http2.Http2FrameWriter;
88
import io.netty.handler.codec.http2.Http2Headers;
89
import io.netty.handler.codec.http2.Http2HeadersDecoder;
90
import io.netty.handler.codec.http2.Http2HeadersEncoder;
91
import io.netty.handler.codec.http2.Http2InboundFrameLogger;
92
import io.netty.handler.codec.http2.Http2LifecycleManager;
93
import io.netty.handler.codec.http2.Http2OutboundFrameLogger;
94
import io.netty.handler.codec.http2.Http2Settings;
95
import io.netty.handler.codec.http2.Http2Stream;
96
import io.netty.handler.codec.http2.Http2StreamVisitor;
97
import io.netty.handler.codec.http2.UniformStreamByteDistributor;
98
import io.netty.handler.logging.LogLevel;
99
import io.netty.util.AsciiString;
100
import io.netty.util.ReferenceCountUtil;
101
import io.perfmark.PerfMark;
102
import io.perfmark.Tag;
103
import io.perfmark.TaskCloseable;
104
import java.text.MessageFormat;
105
import java.util.List;
106
import java.util.concurrent.Future;
107
import java.util.concurrent.ScheduledFuture;
108
import java.util.concurrent.TimeUnit;
109
import java.util.logging.Level;
110
import java.util.logging.Logger;
111
import javax.annotation.CheckForNull;
112
import javax.annotation.Nullable;
113

114
/**
115
 * Server-side Netty handler for GRPC processing. All event handlers are executed entirely within
116
 * the context of the Netty Channel thread.
117
 */
118
class NettyServerHandler extends AbstractNettyHandler {
119
  private static final Logger logger = Logger.getLogger(NettyServerHandler.class.getName());
1✔
120
  private static final long KEEPALIVE_PING = 0xDEADL;
121
  @VisibleForTesting
122
  static final long GRACEFUL_SHUTDOWN_PING = 0x97ACEF001L;
123
  private static final long GRACEFUL_SHUTDOWN_PING_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(10);
1✔
124
  /** Temporary workaround for #8674. Fine to delete after v1.45 release, and maybe earlier. */
125
  private static final boolean DISABLE_CONNECTION_HEADER_CHECK = Boolean.parseBoolean(
1✔
126
      System.getProperty("io.grpc.netty.disableConnectionHeaderCheck", "false"));
1✔
127

128
  private final Http2Connection.PropertyKey streamKey;
129
  private final ServerTransportListener transportListener;
130
  private final int maxMessageSize;
131
  private final TcpMetrics tcpMetrics;
132
  private final long keepAliveTimeInNanos;
133
  private final long keepAliveTimeoutInNanos;
134
  private final long maxConnectionAgeInNanos;
135
  private final long maxConnectionAgeGraceInNanos;
136
  private final RstStreamCounter rstStreamCounter;
137
  private final List<? extends ServerStreamTracer.Factory> streamTracerFactories;
138
  private final TransportTracer transportTracer;
139
  private final KeepAliveEnforcer keepAliveEnforcer;
140
  private final Attributes eagAttributes;
141
  /** Incomplete attributes produced by negotiator. */
142
  private Attributes negotiationAttributes;
143
  private InternalChannelz.Security securityInfo;
144
  /** Completed attributes produced by transportReady. */
145
  private Attributes attributes;
146
  private Throwable connectionError;
147
  private boolean teWarningLogged;
148
  private WriteQueue serverWriteQueue;
149
  private AsciiString lastKnownAuthority;
150
  @CheckForNull
151
  private KeepAliveManager keepAliveManager;
152
  @CheckForNull
153
  private MaxConnectionIdleManager maxConnectionIdleManager;
154
  @CheckForNull
155
  private ScheduledFuture<?> maxConnectionAgeMonitor;
156
  @CheckForNull
157
  private GracefulShutdown gracefulShutdown;
158

159
  static NettyServerHandler newHandler(
160
      ServerTransportListener transportListener,
161
      ChannelPromise channelUnused,
162
      List<? extends ServerStreamTracer.Factory> streamTracerFactories,
163
      TransportTracer transportTracer,
164
      int maxStreams,
165
      boolean autoFlowControl,
166
      int flowControlWindow,
167
      int maxHeaderListSize,
168
      int softLimitHeaderListSize,
169
      int maxMessageSize,
170
      long keepAliveTimeInNanos,
171
      long keepAliveTimeoutInNanos,
172
      long maxConnectionIdleInNanos,
173
      long maxConnectionAgeInNanos,
174
      long maxConnectionAgeGraceInNanos,
175
      boolean permitKeepAliveWithoutCalls,
176
      long permitKeepAliveTimeInNanos,
177
      int maxRstCount,
178
      long maxRstPeriodNanos,
179
      Attributes eagAttributes,
180
      MetricRecorder metricRecorder) {
181
    Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive: %s",
1✔
182
        maxHeaderListSize);
183
    Http2FrameLogger frameLogger = new Http2FrameLogger(LogLevel.DEBUG, NettyServerHandler.class);
1✔
184
    Http2HeadersDecoder headersDecoder = new GrpcHttp2ServerHeadersDecoder(maxHeaderListSize);
1✔
185
    Http2FrameReader frameReader = new Http2InboundFrameLogger(
1✔
186
        new DefaultHttp2FrameReader(headersDecoder), frameLogger);
187
    Http2HeadersEncoder encoder = new DefaultHttp2HeadersEncoder(
1✔
188
        Http2HeadersEncoder.NEVER_SENSITIVE, false, 16, Integer.MAX_VALUE);
189
    Http2FrameWriter frameWriter =
1✔
190
        new Http2OutboundFrameLogger(new DefaultHttp2FrameWriter(encoder), frameLogger);
191
    return newHandler(
1✔
192
        channelUnused,
193
        frameReader,
194
        frameWriter,
195
        transportListener,
196
        streamTracerFactories,
197
        transportTracer,
198
        maxStreams,
199
        autoFlowControl,
200
        flowControlWindow,
201
        maxHeaderListSize,
202
        softLimitHeaderListSize,
203
        maxMessageSize,
204
        keepAliveTimeInNanos,
205
        keepAliveTimeoutInNanos,
206
        maxConnectionIdleInNanos,
207
        maxConnectionAgeInNanos,
208
        maxConnectionAgeGraceInNanos,
209
        permitKeepAliveWithoutCalls,
210
        permitKeepAliveTimeInNanos,
211
        maxRstCount,
212
        maxRstPeriodNanos,
213
        eagAttributes,
214
        Ticker.systemTicker(),
1✔
215
        metricRecorder);
216
  }
217

218
  static NettyServerHandler newHandler(
219
      ChannelPromise channelUnused,
220
      Http2FrameReader frameReader,
221
      Http2FrameWriter frameWriter,
222
      ServerTransportListener transportListener,
223
      List<? extends ServerStreamTracer.Factory> streamTracerFactories,
224
      TransportTracer transportTracer,
225
      int maxStreams,
226
      boolean autoFlowControl,
227
      int flowControlWindow,
228
      int maxHeaderListSize,
229
      int softLimitHeaderListSize,
230
      int maxMessageSize,
231
      long keepAliveTimeInNanos,
232
      long keepAliveTimeoutInNanos,
233
      long maxConnectionIdleInNanos,
234
      long maxConnectionAgeInNanos,
235
      long maxConnectionAgeGraceInNanos,
236
      boolean permitKeepAliveWithoutCalls,
237
      long permitKeepAliveTimeInNanos,
238
      int maxRstCount,
239
      long maxRstPeriodNanos,
240
      Attributes eagAttributes,
241
      Ticker ticker,
242
      MetricRecorder metricRecorder) {
243
    Preconditions.checkArgument(maxStreams > 0, "maxStreams must be positive: %s", maxStreams);
1✔
244
    Preconditions.checkArgument(flowControlWindow > 0, "flowControlWindow must be positive: %s",
1✔
245
        flowControlWindow);
246
    Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive: %s",
1✔
247
        maxHeaderListSize);
248
    Preconditions.checkArgument(
1✔
249
        softLimitHeaderListSize > 0, "softLimitHeaderListSize must be positive: %s",
250
        softLimitHeaderListSize);
251
    Preconditions.checkArgument(maxMessageSize > 0, "maxMessageSize must be positive: %s",
1✔
252
        maxMessageSize);
253

254
    final Http2Connection connection = new DefaultHttp2Connection(true);
1✔
255
    connection.remote().maxActiveStreams(maxStreams);
1✔
256
    UniformStreamByteDistributor dist = new UniformStreamByteDistributor(connection);
1✔
257
    dist.minAllocationChunk(MIN_ALLOCATED_CHUNK); // Increased for benchmarks performance.
1✔
258
    DefaultHttp2RemoteFlowController controller =
1✔
259
        new DefaultHttp2RemoteFlowController(connection, dist);
260
    connection.remote().flowController(controller);
1✔
261
    final KeepAliveEnforcer keepAliveEnforcer = new KeepAliveEnforcer(
1✔
262
        permitKeepAliveWithoutCalls, permitKeepAliveTimeInNanos, TimeUnit.NANOSECONDS);
263

264
    if (ticker == null) {
1✔
265
      ticker = Ticker.systemTicker();
×
266
    }
267

268
    RstStreamCounter rstStreamCounter
1✔
269
        = new RstStreamCounter(maxRstCount, maxRstPeriodNanos, ticker);
270
    // Create the local flow controller configured to auto-refill the connection window.
271
    connection.local().flowController(
1✔
272
        new DefaultHttp2LocalFlowController(connection, DEFAULT_WINDOW_UPDATE_RATIO, true));
273
    frameWriter = new WriteMonitoringFrameWriter(frameWriter, keepAliveEnforcer);
1✔
274
    Http2ConnectionEncoder encoder =
1✔
275
        new DefaultHttp2ConnectionEncoder(connection, frameWriter);
276
    encoder = new Http2ControlFrameLimitEncoder(encoder, 10000);
1✔
277
    encoder = new Http2RstCounterEncoder(encoder, rstStreamCounter);
1✔
278
    Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder,
1✔
279
        frameReader);
280

281
    Http2Settings settings = new Http2Settings();
1✔
282
    settings.initialWindowSize(flowControlWindow);
1✔
283
    settings.maxConcurrentStreams(maxStreams);
1✔
284
    settings.maxHeaderListSize(maxHeaderListSize);
1✔
285

286
    return new NettyServerHandler(
1✔
287
        channelUnused,
288
        connection,
289
        transportListener,
290
        streamTracerFactories,
291
        transportTracer,
292
        decoder, encoder, settings,
293
        maxMessageSize,
294
        maxHeaderListSize,
295
        softLimitHeaderListSize,
296
        keepAliveTimeInNanos,
297
        keepAliveTimeoutInNanos,
298
        maxConnectionIdleInNanos,
299
        maxConnectionAgeInNanos, maxConnectionAgeGraceInNanos,
300
        keepAliveEnforcer,
301
        autoFlowControl,
302
        rstStreamCounter,
303
        eagAttributes, ticker,
304
        metricRecorder);
305
  }
306

307
  private NettyServerHandler(
308
      ChannelPromise channelUnused,
309
      final Http2Connection connection,
310
      ServerTransportListener transportListener,
311
      List<? extends ServerStreamTracer.Factory> streamTracerFactories,
312
      TransportTracer transportTracer,
313
      Http2ConnectionDecoder decoder,
314
      Http2ConnectionEncoder encoder,
315
      Http2Settings settings,
316
      int maxMessageSize,
317
      int maxHeaderListSize,
318
      int softLimitHeaderListSize,
319
      long keepAliveTimeInNanos,
320
      long keepAliveTimeoutInNanos,
321
      long maxConnectionIdleInNanos,
322
      long maxConnectionAgeInNanos,
323
      long maxConnectionAgeGraceInNanos,
324
      final KeepAliveEnforcer keepAliveEnforcer,
325
      boolean autoFlowControl,
326
      RstStreamCounter rstStreamCounter,
327
      Attributes eagAttributes,
328
      Ticker ticker,
329
      MetricRecorder metricRecorder) {
330
    super(
1✔
331
        channelUnused,
332
        decoder,
333
        encoder,
334
        settings,
335
        new ServerChannelLogger(),
336
        autoFlowControl,
337
        null,
338
        ticker,
339
        maxHeaderListSize,
340
        softLimitHeaderListSize);
341

342
    final MaxConnectionIdleManager maxConnectionIdleManager;
343
    if (maxConnectionIdleInNanos == MAX_CONNECTION_IDLE_NANOS_DISABLED) {
1✔
344
      maxConnectionIdleManager = null;
1✔
345
    } else {
346
      maxConnectionIdleManager = new MaxConnectionIdleManager(maxConnectionIdleInNanos);
1✔
347
    }
348

349
    connection.addListener(new Http2ConnectionAdapter() {
1✔
350
      @Override
351
      public void onStreamActive(Http2Stream stream) {
352
        if (connection.numActiveStreams() == 1) {
1✔
353
          keepAliveEnforcer.onTransportActive();
1✔
354
          if (maxConnectionIdleManager != null) {
1✔
355
            maxConnectionIdleManager.onTransportActive();
1✔
356
          }
357
        }
358
      }
1✔
359

360
      @Override
361
      public void onStreamClosed(Http2Stream stream) {
362
        if (connection.numActiveStreams() == 0) {
1✔
363
          keepAliveEnforcer.onTransportIdle();
1✔
364
          if (maxConnectionIdleManager != null) {
1✔
365
            maxConnectionIdleManager.onTransportIdle();
1✔
366
          }
367
        }
368
      }
1✔
369
    });
370

371
    checkArgument(maxMessageSize >= 0, "maxMessageSize must be non-negative: %s", maxMessageSize);
1✔
372
    this.maxMessageSize = maxMessageSize;
1✔
373
    this.tcpMetrics = new TcpMetrics(metricRecorder);
1✔
374
    this.keepAliveTimeInNanos = keepAliveTimeInNanos;
1✔
375
    this.keepAliveTimeoutInNanos = keepAliveTimeoutInNanos;
1✔
376
    this.maxConnectionIdleManager = maxConnectionIdleManager;
1✔
377
    this.maxConnectionAgeInNanos = maxConnectionAgeInNanos;
1✔
378
    this.maxConnectionAgeGraceInNanos = maxConnectionAgeGraceInNanos;
1✔
379
    this.keepAliveEnforcer = checkNotNull(keepAliveEnforcer, "keepAliveEnforcer");
1✔
380
    this.rstStreamCounter = rstStreamCounter;
1✔
381
    this.eagAttributes = checkNotNull(eagAttributes, "eagAttributes");
1✔
382

383
    streamKey = encoder.connection().newKey();
1✔
384
    this.transportListener = checkNotNull(transportListener, "transportListener");
1✔
385
    this.streamTracerFactories = checkNotNull(streamTracerFactories, "streamTracerFactories");
1✔
386
    this.transportTracer = checkNotNull(transportTracer, "transportTracer");
1✔
387
    // Set the frame listener on the decoder.
388
    decoder().frameListener(new FrameListener());
1✔
389
  }
1✔
390

391
  @Nullable
392
  Throwable connectionError() {
393
    return connectionError;
1✔
394
  }
395

396
  @Override
397
  public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
398
    serverWriteQueue = new WriteQueue(ctx.channel());
1✔
399

400
    // init max connection age monitor
401
    if (maxConnectionAgeInNanos != MAX_CONNECTION_AGE_NANOS_DISABLED) {
1✔
402
      maxConnectionAgeMonitor = ctx.executor().schedule(
1✔
403
          new LogExceptionRunnable(new Runnable() {
1✔
404
            @Override
405
            public void run() {
406
              if (gracefulShutdown == null) {
1✔
407
                gracefulShutdown = new GracefulShutdown("max_age", maxConnectionAgeGraceInNanos);
1✔
408
                gracefulShutdown.start(ctx);
1✔
409
                ctx.flush();
1✔
410
              }
411
            }
1✔
412
          }),
413
          maxConnectionAgeInNanos,
414
          TimeUnit.NANOSECONDS);
415
    }
416

417
    if (maxConnectionIdleManager != null) {
1✔
418
      maxConnectionIdleManager.start(new Runnable() {
1✔
419
        @Override
420
        public void run() {
421
          if (gracefulShutdown == null) {
1✔
422
            gracefulShutdown = new GracefulShutdown("max_idle", null);
1✔
423
            gracefulShutdown.start(ctx);
1✔
424
            ctx.flush();
1✔
425
          }
426
        }
1✔
427
      }, ctx.executor());
1✔
428
    }
429

430
    if (keepAliveTimeInNanos != SERVER_KEEPALIVE_TIME_NANOS_DISABLED) {
1✔
431
      keepAliveManager = new KeepAliveManager(new KeepAlivePinger(ctx), ctx.executor(),
1✔
432
          keepAliveTimeInNanos, keepAliveTimeoutInNanos, true /* keepAliveDuringTransportIdle */);
433
      keepAliveManager.onTransportStarted();
1✔
434
    }
435

436
    assert encoder().connection().equals(decoder().connection());
1✔
437
    transportTracer.setFlowControlWindowReader(new Utils.FlowControlReader(encoder().connection()));
1✔
438

439
    super.handlerAdded(ctx);
1✔
440
  }
1✔
441

442
  private void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers)
443
      throws Http2Exception {
444
    try {
445
      // Connection-specific header fields makes a request malformed. Ideally this would be handled
446
      // by Netty. RFC 7540 section 8.1.2.2
447
      if (!DISABLE_CONNECTION_HEADER_CHECK && headers.contains(CONNECTION)) {
1✔
448
        resetStream(ctx, streamId, Http2Error.PROTOCOL_ERROR.code(), ctx.newPromise());
×
449
        return;
×
450
      }
451

452
      if (headers.authority() == null) {
1✔
453
        List<CharSequence> hosts = headers.getAll(HOST);
1✔
454
        if (hosts.size() > 1) {
1✔
455
          // RFC 7230 section 5.4
456
          respondWithHttpError(ctx, streamId, 400, Status.Code.INTERNAL,
1✔
457
              "Multiple host headers");
458
          return;
1✔
459
        }
460
        if (!hosts.isEmpty()) {
1✔
461
          headers.add(AUTHORITY.value(), hosts.get(0));
1✔
462
        }
463
      }
464
      headers.remove(HOST);
1✔
465

466
      // Remove the leading slash of the path and get the fully qualified method name
467
      CharSequence path = headers.path();
1✔
468

469
      if (path == null) {
1✔
470
        respondWithHttpError(ctx, streamId, 404, Status.Code.UNIMPLEMENTED,
1✔
471
            "Expected path but is missing");
472
        return;
1✔
473
      }
474

475
      if (path.charAt(0) != '/') {
1✔
476
        respondWithHttpError(ctx, streamId, 404, Status.Code.UNIMPLEMENTED,
1✔
477
            String.format("Expected path to start with /: %s", path));
1✔
478
        return;
1✔
479
      }
480

481
      String method = path.subSequence(1, path.length()).toString();
1✔
482

483
      // Verify that the Content-Type is correct in the request.
484
      CharSequence contentType = headers.get(CONTENT_TYPE_HEADER);
1✔
485
      if (contentType == null) {
1✔
486
        respondWithHttpError(
1✔
487
            ctx, streamId, 415, Status.Code.INTERNAL, "Content-Type is missing from the request");
488
        return;
1✔
489
      }
490
      String contentTypeString = contentType.toString();
1✔
491
      if (!GrpcUtil.isGrpcContentType(contentTypeString)) {
1✔
492
        respondWithHttpError(ctx, streamId, 415, Status.Code.INTERNAL,
1✔
493
            String.format("Content-Type '%s' is not supported", contentTypeString));
1✔
494
        return;
1✔
495
      }
496

497
      if (!HTTP_METHOD.contentEquals(headers.method())) {
1✔
498
        Http2Headers extraHeaders = new DefaultHttp2Headers();
1✔
499
        extraHeaders.add(HttpHeaderNames.ALLOW, HTTP_METHOD);
1✔
500
        respondWithHttpError(ctx, streamId, 405, Status.Code.INTERNAL,
1✔
501
            String.format("Method '%s' is not supported", headers.method()), extraHeaders);
1✔
502
        return;
1✔
503
      }
504

505
      int h2HeadersSize = Utils.getH2HeadersSize(headers);
1✔
506
      if (Utils.shouldRejectOnMetadataSizeSoftLimitExceeded(
1✔
507
              h2HeadersSize, softLimitHeaderListSize, maxHeaderListSize)) {
508
        respondWithHttpError(ctx, streamId, 431, Status.Code.RESOURCE_EXHAUSTED, String.format(
×
509
                "Client Headers of size %d exceeded Metadata size soft limit: %d",
510
                h2HeadersSize,
×
511
                softLimitHeaderListSize));
×
512
        return;
×
513
      }
514

515
      if (!teWarningLogged && !TE_TRAILERS.contentEquals(headers.get(TE_HEADER))) {
1✔
516
        logger.warning(String.format("Expected header TE: %s, but %s is received. This means "
1✔
517
                + "some intermediate proxy may not support trailers",
518
            TE_TRAILERS, headers.get(TE_HEADER)));
1✔
519
        teWarningLogged = true;
1✔
520
      }
521

522
      // The Http2Stream object was put by AbstractHttp2ConnectionHandler before calling this
523
      // method.
524
      Http2Stream http2Stream = requireHttp2Stream(streamId);
1✔
525

526
      Metadata metadata = Utils.convertHeaders(headers);
1✔
527
      StatsTraceContext statsTraceCtx =
1✔
528
          StatsTraceContext.newServerContext(streamTracerFactories, method, metadata);
1✔
529

530
      NettyServerStream.TransportState state = new NettyServerStream.TransportState(
1✔
531
          this,
532
          ctx.channel().eventLoop(),
1✔
533
          http2Stream,
534
          maxMessageSize,
535
          statsTraceCtx,
536
          transportTracer,
537
          method);
538

539
      try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.onHeadersRead")) {
1✔
540
        PerfMark.attachTag(state.tag());
1✔
541
        String authority = getOrUpdateAuthority((AsciiString) headers.authority());
1✔
542
        NettyServerStream stream = new NettyServerStream(
1✔
543
            ctx.channel(),
1✔
544
            state,
545
            attributes,
546
            authority,
547
            statsTraceCtx);
548
        transportListener.streamCreated(stream, method, metadata);
1✔
549
        state.onStreamAllocated();
1✔
550
        http2Stream.setProperty(streamKey, state);
1✔
551
      }
552
    } catch (Exception e) {
×
553
      logger.log(Level.WARNING, "Exception in onHeadersRead()", e);
×
554
      // Throw an exception that will get handled by onStreamError.
555
      throw newStreamException(streamId, e);
×
556
    }
1✔
557
  }
1✔
558

559
  private String getOrUpdateAuthority(AsciiString authority) {
560
    if (authority == null) {
1✔
561
      return null;
1✔
562
    } else if (!authority.equals(lastKnownAuthority)) {
1✔
563
      lastKnownAuthority = authority;
1✔
564
    }
565

566
    // AsciiString.toString() is internally cached, so subsequent calls will not
567
    // result in recomputing the String representation of lastKnownAuthority.
568
    return lastKnownAuthority.toString();
1✔
569
  }
570

571
  private void onDataRead(int streamId, ByteBuf data, int padding, boolean endOfStream)
572
      throws Http2Exception {
573
    flowControlPing().onDataRead(data.readableBytes(), padding);
1✔
574
    try {
575
      NettyServerStream.TransportState stream = serverStream(requireHttp2Stream(streamId));
1✔
576
      if (stream == null) {
1✔
577
        return;
1✔
578
      }
579
      try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.onDataRead")) {
1✔
580
        PerfMark.attachTag(stream.tag());
1✔
581
        stream.inboundDataReceived(data, endOfStream);
1✔
582
      }
583
    } catch (Throwable e) {
×
584
      logger.log(Level.WARNING, "Exception in onDataRead()", e);
×
585
      // Throw an exception that will get handled by onStreamError.
586
      throw newStreamException(streamId, e);
×
587
    }
1✔
588
  }
1✔
589

590
  private void onRstStreamRead(int streamId, long errorCode) throws Http2Exception {
591
    Http2Exception tooManyRstStream = rstStreamCounter.countRstStream();
1✔
592
    if (tooManyRstStream != null) {
1✔
593
      throw tooManyRstStream;
1✔
594
    }
595

596
    try {
597
      NettyServerStream.TransportState stream = serverStream(connection().stream(streamId));
1✔
598
      if (stream != null) {
1✔
599
        try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.onRstStreamRead")) {
1✔
600
          PerfMark.attachTag(stream.tag());
1✔
601
          stream.transportReportStatus(
1✔
602
              Status.CANCELLED.withDescription("RST_STREAM received for code " + errorCode));
1✔
603
        }
604
      }
605
    } catch (Throwable e) {
×
606
      logger.log(Level.WARNING, "Exception in onRstStreamRead()", e);
×
607
      // Throw an exception that will get handled by onStreamError.
608
      throw newStreamException(streamId, e);
×
609
    }
1✔
610
  }
1✔
611

612
  @Override
613
  protected void onConnectionError(ChannelHandlerContext ctx, boolean outbound, Throwable cause,
614
      Http2Exception http2Ex) {
615
    logger.log(Level.FINE, "Connection Error", cause);
1✔
616
    connectionError = cause;
1✔
617
    super.onConnectionError(ctx, outbound, cause, http2Ex);
1✔
618
  }
1✔
619

620
  @Override
621
  protected void onStreamError(ChannelHandlerContext ctx, boolean outbound, Throwable cause,
622
      StreamException http2Ex) {
623
    NettyServerStream.TransportState serverStream = serverStream(
1✔
624
        connection().stream(Http2Exception.streamId(http2Ex)));
1✔
625
    Level level = Level.WARNING;
1✔
626
    if (serverStream == null && http2Ex.error() == Http2Error.STREAM_CLOSED) {
1✔
627
      level = Level.FINE;
1✔
628
    }
629
    logger.log(level, "Stream Error", cause);
1✔
630
    Tag tag = serverStream != null ? serverStream.tag() : PerfMark.createTag();
1✔
631
    try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.onStreamError")) {
1✔
632
      PerfMark.attachTag(tag);
1✔
633
      if (serverStream != null) {
1✔
634
        serverStream.transportReportStatus(Utils.statusFromThrowable(cause));
1✔
635
      }
636
      // TODO(ejona): Abort the stream by sending headers to help the client with debugging.
637
      // Delegate to the base class to send a RST_STREAM.
638
      super.onStreamError(ctx, outbound, cause, http2Ex);
1✔
639
    }
640
  }
1✔
641

642
  @Override
643
  public void handleProtocolNegotiationCompleted(
644
      Attributes attrs, InternalChannelz.Security securityInfo) {
645
    negotiationAttributes = attrs;
1✔
646
    this.securityInfo = securityInfo;
1✔
647
    super.handleProtocolNegotiationCompleted(attrs, securityInfo);
1✔
648
    NettyClientHandler.writeBufferingAndRemove(ctx().channel());
1✔
649
  }
1✔
650

651
  @Override
652
  public Attributes getEagAttributes() {
653
    return eagAttributes;
1✔
654
  }
655

656
  InternalChannelz.Security getSecurityInfo() {
657
    return securityInfo;
1✔
658
  }
659

660
  @VisibleForTesting
661
  KeepAliveManager getKeepAliveManagerForTest() {
662
    return keepAliveManager;
1✔
663
  }
664

665
  @VisibleForTesting
666
  void setKeepAliveManagerForTest(KeepAliveManager keepAliveManager) {
667
    this.keepAliveManager = keepAliveManager;
1✔
668
  }
1✔
669

670
  /**
671
   * Handler for the Channel shutting down.
672
   */
673
  @Override
674
  public void channelActive(ChannelHandlerContext ctx) throws Exception {
675
    tcpMetrics.channelActive(ctx.channel());
1✔
676
    super.channelActive(ctx);
1✔
677
  }
1✔
678

679
  @Override
680
  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
681
    try {
682
      tcpMetrics.channelInactive(ctx.channel());
1✔
683
      if (keepAliveManager != null) {
1✔
684
        keepAliveManager.onTransportTermination();
1✔
685
      }
686
      if (maxConnectionIdleManager != null) {
1✔
687
        maxConnectionIdleManager.onTransportTermination();
1✔
688
      }
689
      if (maxConnectionAgeMonitor != null) {
1✔
690
        maxConnectionAgeMonitor.cancel(false);
1✔
691
      }
692
      final Status status =
1✔
693
          Status.UNAVAILABLE.withDescription("connection terminated for unknown reason");
1✔
694
      // Any streams that are still active must be closed
695
      connection().forEachActiveStream(new Http2StreamVisitor() {
1✔
696
        @Override
697
        public boolean visit(Http2Stream stream) throws Http2Exception {
698
          NettyServerStream.TransportState serverStream = serverStream(stream);
1✔
699
          if (serverStream != null) {
1✔
700
            serverStream.transportReportStatus(status);
1✔
701
          }
702
          return true;
1✔
703
        }
704
      });
705
    } finally {
706
      super.channelInactive(ctx);
1✔
707
    }
708
  }
1✔
709

710
  WriteQueue getWriteQueue() {
711
    return serverWriteQueue;
1✔
712
  }
713

714
  /** Handler for commands sent from the stream. */
715
  @Override
716
  public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
717
      throws Exception {
718
    if (msg instanceof SendGrpcFrameCommand) {
1✔
719
      sendGrpcFrame(ctx, (SendGrpcFrameCommand) msg, promise);
1✔
720
    } else if (msg instanceof SendResponseHeadersCommand) {
1✔
721
      sendResponseHeaders(ctx, (SendResponseHeadersCommand) msg, promise);
1✔
722
    } else if (msg instanceof CancelServerStreamCommand) {
1✔
723
      cancelStream(ctx, (CancelServerStreamCommand) msg, promise);
1✔
724
    } else if (msg instanceof GracefulServerCloseCommand) {
1✔
725
      gracefulClose(ctx, (GracefulServerCloseCommand) msg, promise);
1✔
726
    } else if (msg instanceof ForcefulCloseCommand) {
1✔
727
      forcefulClose(ctx, (ForcefulCloseCommand) msg, promise);
1✔
728
    } else {
729
      AssertionError e =
×
730
          new AssertionError("Write called for unexpected type: " + msg.getClass().getName());
×
731
      ReferenceCountUtil.release(msg);
×
732
      promise.setFailure(e);
×
733
      throw e;
×
734
    }
735
  }
1✔
736

737
  @Override
738
  public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
739
    gracefulClose(ctx, new GracefulServerCloseCommand("app_requested"), promise);
1✔
740
    ctx.flush();
1✔
741
  }
1✔
742

743
  /**
744
   * Returns the given processed bytes back to inbound flow control.
745
   */
746
  void returnProcessedBytes(Http2Stream http2Stream, int bytes) {
747
    try {
748
      decoder().flowController().consumeBytes(http2Stream, bytes);
1✔
749
    } catch (Http2Exception e) {
×
750
      throw new RuntimeException(e);
×
751
    }
1✔
752
  }
1✔
753

754
  private void closeStreamWhenDone(ChannelPromise promise, Http2Stream stream) {
755
    promise.addListener(
1✔
756
        new ChannelFutureListener() {
1✔
757
          @Override
758
          public void operationComplete(ChannelFuture future) {
759
            serverStream(stream).complete();
1✔
760
          }
1✔
761
        });
762
  }
1✔
763

764
  private static void streamGone(int streamId, ChannelPromise promise) {
765
    promise.setFailure(
1✔
766
        new IllegalStateException(
767
            "attempting to write to stream " + streamId + " that no longer exists") {
1✔
768
          @Override
769
          public synchronized Throwable fillInStackTrace() {
770
            return this;
1✔
771
          }
772
        });
773
  }
1✔
774

775
  /** Sends the given gRPC frame to the client. */
776
  private void sendGrpcFrame(
777
      ChannelHandlerContext ctx, SendGrpcFrameCommand cmd, ChannelPromise promise)
778
      throws Http2Exception {
779
    try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.sendGrpcFrame")) {
1✔
780
      PerfMark.attachTag(cmd.stream().tag());
1✔
781
      PerfMark.linkIn(cmd.getLink());
1✔
782
      int streamId = cmd.stream().id();
1✔
783
      Http2Stream stream = connection().stream(streamId);
1✔
784
      if (stream == null) {
1✔
785
        cmd.release();
×
786
        streamGone(streamId, promise);
×
787
        return;
×
788
      }
789
      if (cmd.endStream()) {
1✔
790
        closeStreamWhenDone(promise, stream);
×
791
      }
792
      // Call the base class to write the HTTP/2 DATA frame.
793
      encoder().writeData(ctx, streamId, cmd.content(), 0, cmd.endStream(), promise);
1✔
794
    }
795
  }
1✔
796

797
  /**
798
   * Sends the response headers to the client.
799
   */
800
  private void sendResponseHeaders(ChannelHandlerContext ctx, SendResponseHeadersCommand cmd,
801
      ChannelPromise promise) throws Http2Exception {
802
    try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.sendResponseHeaders")) {
1✔
803
      PerfMark.attachTag(cmd.stream().tag());
1✔
804
      PerfMark.linkIn(cmd.getLink());
1✔
805
      int streamId = cmd.stream().id();
1✔
806
      Http2Stream stream = connection().stream(streamId);
1✔
807
      if (stream == null) {
1✔
808
        streamGone(streamId, promise);
1✔
809
        return;
1✔
810
      }
811
      if (cmd.endOfStream()) {
1✔
812
        closeStreamWhenDone(promise, stream);
1✔
813
      }
814
      encoder().writeHeaders(ctx, streamId, cmd.headers(), 0, cmd.endOfStream(), promise);
1✔
815
    }
816
  }
1✔
817

818
  private void cancelStream(ChannelHandlerContext ctx, CancelServerStreamCommand cmd,
819
      ChannelPromise promise) {
820
    try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.cancelStream")) {
1✔
821
      PerfMark.attachTag(cmd.stream().tag());
1✔
822
      PerfMark.linkIn(cmd.getLink());
1✔
823
      // Notify the listener if we haven't already.
824
      cmd.stream().transportReportStatus(cmd.reason());
1✔
825

826
      // Now we need to decide how we're going to notify the peer that this stream is closed.
827
      // If possible, it's nice to inform the peer _why_ this stream was cancelled by sending
828
      // a structured headers frame.
829
      if (shouldCloseStreamWithHeaders(cmd, connection())) {
1✔
830
        Metadata md = new Metadata();
1✔
831
        md.put(InternalStatus.CODE_KEY, cmd.reason());
1✔
832
        if (cmd.reason().getDescription() != null) {
1✔
833
          md.put(InternalStatus.MESSAGE_KEY, cmd.reason().getDescription());
1✔
834
        }
835
        Http2Headers headers = Utils.convertServerHeaders(md);
1✔
836
        encoder().writeHeaders(
1✔
837
            ctx, cmd.stream().id(), headers, /* padding = */ 0, /* endStream = */ true, promise);
1✔
838
      } else {
1✔
839
        // Terminate the stream.
840
        encoder().writeRstStream(ctx, cmd.stream().id(), Http2Error.CANCEL.code(), promise);
1✔
841
      }
842
    }
843
  }
1✔
844

845
  // Determine whether a CancelServerStreamCommand should try to close the stream with a
846
  // HEADERS or a RST_STREAM frame. The caller has some influence over this (they can
847
  // configure cmd.wantsHeaders()). The state of the stream also has an influence: we
848
  // only try to send HEADERS if the stream exists and hasn't already sent any headers.
849
  private static boolean shouldCloseStreamWithHeaders(
850
          CancelServerStreamCommand cmd, Http2Connection conn) {
851
    if (!cmd.wantsHeaders()) {
1✔
852
      return false;
1✔
853
    }
854
    Http2Stream stream = conn.stream(cmd.stream().id());
1✔
855
    return stream != null && !stream.isHeadersSent();
1✔
856
  }
857

858
  private void gracefulClose(final ChannelHandlerContext ctx, final GracefulServerCloseCommand msg,
859
      ChannelPromise promise) throws Exception {
860
    // Ideally we'd adjust a pre-existing graceful shutdown's grace period to at least what is
861
    // requested here. But that's an edge case and seems bug-prone.
862
    if (gracefulShutdown == null) {
1✔
863
      Long graceTimeInNanos = null;
1✔
864
      if (msg.getGraceTimeUnit() != null) {
1✔
865
        graceTimeInNanos = msg.getGraceTimeUnit().toNanos(msg.getGraceTime());
1✔
866
      }
867
      gracefulShutdown = new GracefulShutdown(msg.getGoAwayDebugString(), graceTimeInNanos);
1✔
868
      gracefulShutdown.start(ctx);
1✔
869
    }
870
    promise.setSuccess();
1✔
871
  }
1✔
872

873
  private void forcefulClose(final ChannelHandlerContext ctx, final ForcefulCloseCommand msg,
874
      ChannelPromise promise) throws Exception {
875
    super.close(ctx, promise);
1✔
876
    connection().forEachActiveStream(new Http2StreamVisitor() {
1✔
877
      @Override
878
      public boolean visit(Http2Stream stream) throws Http2Exception {
879
        NettyServerStream.TransportState serverStream = serverStream(stream);
1✔
880
        if (serverStream != null) {
1✔
881
          try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.forcefulClose")) {
1✔
882
            PerfMark.attachTag(serverStream.tag());
1✔
883
            PerfMark.linkIn(msg.getLink());
1✔
884
            serverStream.transportReportStatus(msg.getStatus());
1✔
885
            resetStream(ctx, stream.id(), Http2Error.CANCEL.code(), ctx.newPromise());
1✔
886
          }
887
        }
888
        stream.close();
1✔
889
        return true;
1✔
890
      }
891
    });
892
  }
1✔
893

894
  private void respondWithHttpError(
895
      ChannelHandlerContext ctx, int streamId, int code, Status.Code statusCode, String msg) {
896
    respondWithHttpError(ctx, streamId, code, statusCode, msg, EmptyHttp2Headers.INSTANCE);
1✔
897
  }
1✔
898

899
  private void respondWithHttpError(
900
      ChannelHandlerContext ctx, int streamId, int code, Status.Code statusCode, String msg,
901
      Http2Headers extraHeaders) {
902
    Metadata metadata = new Metadata();
1✔
903
    metadata.put(InternalStatus.CODE_KEY, statusCode.toStatus());
1✔
904
    metadata.put(InternalStatus.MESSAGE_KEY, msg);
1✔
905
    byte[][] serialized = InternalMetadata.serialize(metadata);
1✔
906

907
    Http2Headers headers = new DefaultHttp2Headers(true, serialized.length / 2)
1✔
908
        .status("" + code)
1✔
909
        .set(CONTENT_TYPE_HEADER, "text/plain; charset=utf-8");
1✔
910
    for (int i = 0; i < serialized.length; i += 2) {
1✔
911
      headers.add(new AsciiString(serialized[i], false), new AsciiString(serialized[i + 1], false));
1✔
912
    }
913
    headers.add(extraHeaders);
1✔
914
    encoder().writeHeaders(ctx, streamId, headers, 0, false, ctx.newPromise());
1✔
915
    ByteBuf msgBuf = ByteBufUtil.writeUtf8(ctx.alloc(), msg);
1✔
916
    encoder().writeData(ctx, streamId, msgBuf, 0, true, ctx.newPromise());
1✔
917
  }
1✔
918

919
  private Http2Stream requireHttp2Stream(int streamId) {
920
    Http2Stream stream = connection().stream(streamId);
1✔
921
    if (stream == null) {
1✔
922
      // This should never happen.
923
      throw new AssertionError("Stream does not exist: " + streamId);
×
924
    }
925
    return stream;
1✔
926
  }
927

928
  /**
929
   * Returns the server stream associated to the given HTTP/2 stream object.
930
   */
931
  private NettyServerStream.TransportState serverStream(Http2Stream stream) {
932
    return stream == null ? null : (NettyServerStream.TransportState) stream.getProperty(streamKey);
1✔
933
  }
934

935
  private Http2Exception newStreamException(int streamId, Throwable cause) {
936
    return Http2Exception.streamError(
×
937
        streamId, Http2Error.INTERNAL_ERROR, cause, Strings.nullToEmpty(cause.getMessage()));
×
938
  }
939

940
  private class FrameListener extends Http2FrameAdapter {
1✔
941
    private boolean firstSettings = true;
1✔
942

943
    @Override
944
    public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
945
      if (firstSettings) {
1✔
946
        firstSettings = false;
1✔
947
        // Delay transportReady until we see the client's HTTP handshake, for coverage with
948
        // handshakeTimeout
949
        attributes = transportListener.transportReady(negotiationAttributes);
1✔
950
      }
951
    }
1✔
952

953
    @Override
954
    public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
955
        boolean endOfStream) throws Http2Exception {
956
      if (keepAliveManager != null) {
1✔
957
        keepAliveManager.onDataReceived();
1✔
958
      }
959
      NettyServerHandler.this.onDataRead(streamId, data, padding, endOfStream);
1✔
960
      return padding;
1✔
961
    }
962

963
    @Override
964
    public void onHeadersRead(ChannelHandlerContext ctx,
965
        int streamId,
966
        Http2Headers headers,
967
        int streamDependency,
968
        short weight,
969
        boolean exclusive,
970
        int padding,
971
        boolean endStream) throws Http2Exception {
972
      if (keepAliveManager != null) {
1✔
973
        keepAliveManager.onDataReceived();
1✔
974
      }
975
      NettyServerHandler.this.onHeadersRead(ctx, streamId, headers);
1✔
976
      if (endStream) {
1✔
977
        NettyServerHandler.this.onDataRead(streamId, Unpooled.EMPTY_BUFFER, 0, endStream);
×
978
      }
979
    }
1✔
980

981
    @Override
982
    public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode)
983
        throws Http2Exception {
984
      if (keepAliveManager != null) {
1✔
985
        keepAliveManager.onDataReceived();
1✔
986
      }
987
      NettyServerHandler.this.onRstStreamRead(streamId, errorCode);
1✔
988
    }
1✔
989

990
    @Override
991
    public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
992
      if (keepAliveManager != null) {
1✔
993
        keepAliveManager.onDataReceived();
1✔
994
      }
995
      if (!keepAliveEnforcer.pingAcceptable()) {
1✔
996
        ByteBuf debugData = ByteBufUtil.writeAscii(ctx.alloc(), "too_many_pings");
1✔
997
        goAway(ctx, connection().remote().lastStreamCreated(), Http2Error.ENHANCE_YOUR_CALM.code(),
1✔
998
            debugData, ctx.newPromise());
1✔
999
        Status status = Status.RESOURCE_EXHAUSTED.withDescription("Too many pings from client");
1✔
1000
        try {
1001
          forcefulClose(ctx, new ForcefulCloseCommand(status), ctx.newPromise());
1✔
1002
        } catch (Exception ex) {
×
1003
          onError(ctx, /* outbound= */ true, ex);
×
1004
        }
1✔
1005
      }
1006
    }
1✔
1007

1008
    @Override
1009
    public void onPingAckRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
1010
      if (keepAliveManager != null) {
1✔
1011
        keepAliveManager.onDataReceived();
1✔
1012
      }
1013
      if (data == flowControlPing().payload()) {
1✔
1014
        flowControlPing().updateWindow();
1✔
1015
        logger.log(Level.FINE, "Window: {0}",
1✔
1016
            decoder().flowController().initialWindowSize(connection().connectionStream()));
1✔
1017
      } else if (data == GRACEFUL_SHUTDOWN_PING) {
1✔
1018
        if (gracefulShutdown == null) {
1✔
1019
          // this should never happen
1020
          logger.warning("Received GRACEFUL_SHUTDOWN_PING Ack but gracefulShutdown is null");
×
1021
        } else {
1022
          gracefulShutdown.secondGoAwayAndClose(ctx);
1✔
1023
        }
1024
      } else if (data != KEEPALIVE_PING) {
1✔
1025
        logger.warning("Received unexpected ping ack. No ping outstanding");
×
1026
      }
1027
    }
1✔
1028
  }
1029

1030
  private final class KeepAlivePinger implements KeepAliveManager.KeepAlivePinger {
1031
    final ChannelHandlerContext ctx;
1032

1033
    KeepAlivePinger(ChannelHandlerContext ctx) {
1✔
1034
      this.ctx = ctx;
1✔
1035
    }
1✔
1036

1037
    @Override
1038
    public void ping() {
1039
      ChannelFuture pingFuture = encoder().writePing(
1✔
1040
          ctx, false /* isAck */, KEEPALIVE_PING, ctx.newPromise());
1✔
1041
      ctx.flush();
1✔
1042
      pingFuture.addListener(new ChannelFutureListener() {
1✔
1043
        @Override
1044
        public void operationComplete(ChannelFuture future) throws Exception {
1045
          if (future.isSuccess()) {
1✔
1046
            transportTracer.reportKeepAliveSent();
1✔
1047
          }
1048
        }
1✔
1049
      });
1050
    }
1✔
1051

1052
    @Override
1053
    public void onPingTimeout() {
1054
      try {
1055
        forcefulClose(
1✔
1056
            ctx,
1057
            new ForcefulCloseCommand(Status.UNAVAILABLE
1058
                .withDescription("Keepalive failed. The connection is likely gone")),
1✔
1059
            ctx.newPromise());
1✔
1060
      } catch (Exception ex) {
×
1061
        try {
1062
          exceptionCaught(ctx, ex);
×
1063
        } catch (Exception ex2) {
×
1064
          logger.log(Level.WARNING, "Exception while propagating exception", ex2);
×
1065
          logger.log(Level.WARNING, "Original failure", ex);
×
1066
        }
×
1067
      }
1✔
1068
    }
1✔
1069
  }
1070

1071
  private final class GracefulShutdown {
1072
    String goAwayMessage;
1073

1074
    /**
1075
     * The grace time between starting graceful shutdown and closing the netty channel,
1076
     * {@code null} is unspecified.
1077
     */
1078
    @CheckForNull
1079
    Long graceTimeInNanos;
1080

1081
    /**
1082
     * True if ping is Acked or ping is timeout.
1083
     */
1084
    boolean pingAckedOrTimeout;
1085

1086
    Future<?> pingFuture;
1087

1088
    GracefulShutdown(String goAwayMessage,
1089
        @Nullable Long graceTimeInNanos) {
1✔
1090
      this.goAwayMessage = goAwayMessage;
1✔
1091
      this.graceTimeInNanos = graceTimeInNanos;
1✔
1092
    }
1✔
1093

1094
    /**
1095
     * Sends out first GOAWAY and ping, and schedules second GOAWAY and close.
1096
     */
1097
    void start(final ChannelHandlerContext ctx) {
1098
      goAway(
1✔
1099
          ctx,
1100
          Integer.MAX_VALUE,
1101
          Http2Error.NO_ERROR.code(),
1✔
1102
          ByteBufUtil.writeAscii(ctx.alloc(), goAwayMessage),
1✔
1103
          ctx.newPromise());
1✔
1104

1105
      pingFuture = ctx.executor().schedule(
1✔
1106
          new Runnable() {
1✔
1107
            @Override
1108
            public void run() {
1109
              secondGoAwayAndClose(ctx);
1✔
1110
            }
1✔
1111
          },
1112
          GRACEFUL_SHUTDOWN_PING_TIMEOUT_NANOS,
1✔
1113
          TimeUnit.NANOSECONDS);
1114

1115
      encoder().writePing(ctx, false /* isAck */, GRACEFUL_SHUTDOWN_PING, ctx.newPromise());
1✔
1116
    }
1✔
1117

1118
    void secondGoAwayAndClose(ChannelHandlerContext ctx) {
1119
      if (pingAckedOrTimeout) {
1✔
1120
        return;
×
1121
      }
1122
      pingAckedOrTimeout = true;
1✔
1123

1124
      checkNotNull(pingFuture, "pingFuture");
1✔
1125
      pingFuture.cancel(false);
1✔
1126

1127
      // send the second GOAWAY with last stream id
1128
      goAway(
1✔
1129
          ctx,
1130
          connection().remote().lastStreamCreated(),
1✔
1131
          Http2Error.NO_ERROR.code(),
1✔
1132
          ByteBufUtil.writeAscii(ctx.alloc(), goAwayMessage),
1✔
1133
          ctx.newPromise());
1✔
1134

1135
      // gracefully shutdown with specified grace time
1136
      long savedGracefulShutdownTimeMillis = gracefulShutdownTimeoutMillis();
1✔
1137
      long overriddenGraceTime = graceTimeOverrideMillis(savedGracefulShutdownTimeMillis);
1✔
1138
      try {
1139
        gracefulShutdownTimeoutMillis(overriddenGraceTime);
1✔
1140
        NettyServerHandler.super.close(ctx, ctx.newPromise());
1✔
1141
      } catch (Exception e) {
×
1142
        onError(ctx, /* outbound= */ true, e);
×
1143
      } finally {
1144
        gracefulShutdownTimeoutMillis(savedGracefulShutdownTimeMillis);
1✔
1145
      }
1146
    }
1✔
1147

1148
    private long graceTimeOverrideMillis(long originalMillis) {
1149
      if (graceTimeInNanos == null) {
1✔
1150
        return originalMillis;
1✔
1151
      }
1152
      if (graceTimeInNanos == MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE) {
1✔
1153
        // netty treats -1 as "no timeout"
1154
        return -1L;
1✔
1155
      }
1156
      return TimeUnit.NANOSECONDS.toMillis(graceTimeInNanos);
1✔
1157
    }
1158
  }
1159

1160
  // Use a frame writer so that we know when frames are through flow control and actually being
1161
  // written.
1162
  private static class WriteMonitoringFrameWriter extends DecoratingHttp2FrameWriter {
1163
    private final KeepAliveEnforcer keepAliveEnforcer;
1164

1165
    public WriteMonitoringFrameWriter(Http2FrameWriter delegate,
1166
        KeepAliveEnforcer keepAliveEnforcer) {
1167
      super(delegate);
1✔
1168
      this.keepAliveEnforcer = keepAliveEnforcer;
1✔
1169
    }
1✔
1170

1171
    @Override
1172
    public ChannelFuture writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
1173
        int padding, boolean endStream, ChannelPromise promise) {
1174
      keepAliveEnforcer.resetCounters();
1✔
1175
      return super.writeData(ctx, streamId, data, padding, endStream, promise);
1✔
1176
    }
1177

1178
    @Override
1179
    public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
1180
        int padding, boolean endStream, ChannelPromise promise) {
1181
      keepAliveEnforcer.resetCounters();
1✔
1182
      return super.writeHeaders(ctx, streamId, headers, padding, endStream, promise);
1✔
1183
    }
1184

1185
    @Override
1186
    public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
1187
        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream,
1188
        ChannelPromise promise) {
1189
      keepAliveEnforcer.resetCounters();
×
1190
      return super.writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive,
×
1191
          padding, endStream, promise);
1192
    }
1193
  }
1194

1195
  private static final class Http2RstCounterEncoder extends DecoratingHttp2ConnectionEncoder {
1196
    private final RstStreamCounter rstStreamCounter;
1197
    private Http2LifecycleManager lifecycleManager;
1198

1199
    Http2RstCounterEncoder(Http2ConnectionEncoder encoder, RstStreamCounter rstStreamCounter) {
1200
      super(encoder);
1✔
1201
      this.rstStreamCounter = rstStreamCounter;
1✔
1202
    }
1✔
1203

1204
    @Override
1205
    public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
1206
      this.lifecycleManager = lifecycleManager;
1✔
1207
      super.lifecycleManager(lifecycleManager);
1✔
1208
    }
1✔
1209

1210
    @Override
1211
    public ChannelFuture writeRstStream(
1212
        ChannelHandlerContext ctx, int streamId, long errorCode, ChannelPromise promise) {
1213
      ChannelFuture future = super.writeRstStream(ctx, streamId, errorCode, promise);
1✔
1214
      // We want to count "induced" RST_STREAM, where the server sent a reset because of a malformed
1215
      // frame.
1216
      boolean normalRst
1✔
1217
          = errorCode == Http2Error.NO_ERROR.code() || errorCode == Http2Error.CANCEL.code();
1✔
1218
      if (!normalRst) {
1✔
1219
        Http2Exception tooManyRstStream = rstStreamCounter.countRstStream();
1✔
1220
        if (tooManyRstStream != null) {
1✔
1221
          lifecycleManager.onError(ctx, true, tooManyRstStream);
1✔
1222
          ctx.close();
1✔
1223
        }
1224
      }
1225
      return future;
1✔
1226
    }
1227
  }
1228

1229
  private static final class RstStreamCounter {
1230
    private final int maxRstCount;
1231
    private final long maxRstPeriodNanos;
1232
    private final Ticker ticker;
1233
    private int rstCount;
1234
    private long lastRstNanoTime;
1235

1236
    RstStreamCounter(int maxRstCount, long maxRstPeriodNanos, Ticker ticker) {
1✔
1237
      checkArgument(maxRstCount >= 0, "maxRstCount must be non-negative: %s", maxRstCount);
1✔
1238
      this.maxRstCount = maxRstCount;
1✔
1239
      this.maxRstPeriodNanos = maxRstPeriodNanos;
1✔
1240
      this.ticker = checkNotNull(ticker, "ticker");
1✔
1241
      this.lastRstNanoTime = ticker.read();
1✔
1242
    }
1✔
1243

1244
    /** Returns non-{@code null} when the connection should be killed by the caller. */
1245
    private Http2Exception countRstStream() {
1246
      if (maxRstCount == 0) {
1✔
1247
        return null;
1✔
1248
      }
1249
      long now = ticker.read();
1✔
1250
      if (now - lastRstNanoTime > maxRstPeriodNanos) {
1✔
1251
        lastRstNanoTime = now;
1✔
1252
        rstCount = 1;
1✔
1253
      } else {
1254
        rstCount++;
1✔
1255
        if (rstCount > maxRstCount) {
1✔
1256
          return new Http2Exception(Http2Error.ENHANCE_YOUR_CALM, "too_many_rststreams") {
1✔
1257
            @SuppressWarnings("UnsynchronizedOverridesSynchronized") // No memory accesses
1258
            @Override
1259
            public Throwable fillInStackTrace() {
1260
              // Avoid the CPU cycles, since the resets may be a CPU consumption attack
1261
              return this;
1✔
1262
            }
1263
          };
1264
        }
1265
      }
1266
      return null;
1✔
1267
    }
1268
  }
1269

1270
  private static class ServerChannelLogger extends ChannelLogger {
1271
    private static final Logger log = Logger.getLogger(ChannelLogger.class.getName());
1✔
1272

1273
    @Override
1274
    public void log(ChannelLogLevel level, String message) {
1275
      log.log(toJavaLogLevel(level), message);
1✔
1276
    }
1✔
1277

1278
    @Override
1279
    public void log(ChannelLogLevel level, String messageFormat, Object... args) {
1280
      log(level, MessageFormat.format(messageFormat, args));
1✔
1281
    }
1✔
1282
  }
1283

1284
  private static Level toJavaLogLevel(ChannelLogLevel level) {
1285
    switch (level) {
1✔
1286
      case ERROR:
1287
        return Level.FINE;
×
1288
      case WARNING:
1289
        return Level.FINER;
×
1290
      default:
1291
        return Level.FINEST;
1✔
1292
    }
1293
  }
1294
}
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