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

grpc / grpc-java / #20488

17 Sep 2026 09:15AM UTC coverage: 89.322% (-0.007%) from 89.329%
#20488

push

github

web-flow
bazel: Adopt proto_lang_toolchain for java_grpc_library() (#12994)

39150 of 43830 relevant lines covered (89.32%)

0.89 hits per line

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

90.66
/../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.Set;
107
import java.util.concurrent.Future;
108
import java.util.concurrent.ScheduledFuture;
109
import java.util.concurrent.TimeUnit;
110
import java.util.logging.Level;
111
import java.util.logging.Logger;
112
import javax.annotation.CheckForNull;
113
import javax.annotation.Nullable;
114

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

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

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

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

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

269
    if (ticker == null) {
1✔
270
      ticker = Ticker.systemTicker();
×
271
    }
272

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

286
    Http2Settings settings = new Http2Settings();
1✔
287
    settings.initialWindowSize(flowControlWindow);
1✔
288
    settings.maxConcurrentStreams(maxStreams);
1✔
289
    settings.maxHeaderListSize(maxHeaderListSize);
1✔
290

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

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

347
    final MaxConnectionIdleManager maxConnectionIdleManager;
348
    if (maxConnectionIdleInNanos == MAX_CONNECTION_IDLE_NANOS_DISABLED) {
1✔
349
      maxConnectionIdleManager = null;
1✔
350
    } else {
351
      maxConnectionIdleManager = new MaxConnectionIdleManager(maxConnectionIdleInNanos);
1✔
352
    }
353

354
    connection.addListener(new Http2ConnectionAdapter() {
1✔
355
      @Override
356
      public void onStreamActive(Http2Stream stream) {
357
        if (connection.numActiveStreams() == 1) {
1✔
358
          keepAliveEnforcer.onTransportActive();
1✔
359
          if (maxConnectionIdleManager != null) {
1✔
360
            maxConnectionIdleManager.onTransportActive();
1✔
361
          }
362
        }
363
      }
1✔
364

365
      @Override
366
      public void onStreamClosed(Http2Stream stream) {
367
        if (connection.numActiveStreams() == 0) {
1✔
368
          keepAliveEnforcer.onTransportIdle();
1✔
369
          if (maxConnectionIdleManager != null) {
1✔
370
            maxConnectionIdleManager.onTransportIdle();
1✔
371
          }
372
        }
373
      }
1✔
374
    });
375

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

388
    streamKey = encoder.connection().newKey();
1✔
389
    this.transportListener = checkNotNull(transportListener, "transportListener");
1✔
390
    this.streamTracerFactories = checkNotNull(streamTracerFactories, "streamTracerFactories");
1✔
391
    this.transportTracer = checkNotNull(transportTracer, "transportTracer");
1✔
392
    // Set the frame listener on the decoder.
393
    decoder().frameListener(new FrameListener());
1✔
394
  }
1✔
395

396
  @Nullable
397
  Throwable connectionError() {
398
    return connectionError;
1✔
399
  }
400

401
  @Override
402
  public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
403
    serverWriteQueue = new WriteQueue(ctx.channel());
1✔
404

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

422
    if (maxConnectionIdleManager != null) {
1✔
423
      maxConnectionIdleManager.start(new Runnable() {
1✔
424
        @Override
425
        public void run() {
426
          if (gracefulShutdown == null) {
1✔
427
            gracefulShutdown = new GracefulShutdown("max_idle", null);
1✔
428
            gracefulShutdown.start(ctx);
1✔
429
            ctx.flush();
1✔
430
          }
431
        }
1✔
432
      }, ctx.executor());
1✔
433
    }
434

435
    if (keepAliveTimeInNanos != SERVER_KEEPALIVE_TIME_NANOS_DISABLED) {
1✔
436
      keepAliveManager = new KeepAliveManager(new KeepAlivePinger(ctx), ctx.executor(),
1✔
437
          keepAliveTimeInNanos, keepAliveTimeoutInNanos, true /* keepAliveDuringTransportIdle */);
438
      keepAliveManager.onTransportStarted();
1✔
439
    }
440

441
    assert encoder().connection().equals(decoder().connection());
1✔
442
    transportTracer.setFlowControlWindowReader(new Utils.FlowControlReader(encoder().connection()));
1✔
443

444
    super.handlerAdded(ctx);
1✔
445
  }
1✔
446

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

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

471
      // Remove the leading slash of the path and get the fully qualified method name
472
      CharSequence path = headers.path();
1✔
473

474
      if (path == null) {
1✔
475
        respondWithHttpError(ctx, streamId, 404, Status.Code.UNIMPLEMENTED,
1✔
476
            "Expected path but is missing");
477
        return;
1✔
478
      }
479

480
      if (path.charAt(0) != '/') {
1✔
481
        respondWithHttpError(ctx, streamId, 404, Status.Code.UNIMPLEMENTED,
1✔
482
            String.format("Expected path to start with /: %s", path));
1✔
483
        return;
1✔
484
      }
485

486
      String method = path.subSequence(1, path.length()).toString();
1✔
487

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

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

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

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

527
      // The Http2Stream object was put by AbstractHttp2ConnectionHandler before calling this
528
      // method.
529
      Http2Stream http2Stream = requireHttp2Stream(streamId);
1✔
530

531
      Metadata metadata = Utils.convertHeaders(headers);
1✔
532
      StatsTraceContext statsTraceCtx =
1✔
533
          StatsTraceContext.newServerContext(streamTracerFactories, method, metadata);
1✔
534

535
      NettyServerStream.TransportState state = new NettyServerStream.TransportState(
1✔
536
          this,
537
          ctx.channel().eventLoop(),
1✔
538
          http2Stream,
539
          maxMessageSize,
540
          statsTraceCtx,
541
          transportTracer,
542
          method);
543

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

564
  private String getOrUpdateAuthority(AsciiString authority) {
565
    if (authority == null) {
1✔
566
      return null;
1✔
567
    } else if (!authority.equals(lastKnownAuthority)) {
1✔
568
      lastKnownAuthority = authority;
1✔
569
    }
570

571
    // AsciiString.toString() is internally cached, so subsequent calls will not
572
    // result in recomputing the String representation of lastKnownAuthority.
573
    return lastKnownAuthority.toString();
1✔
574
  }
575

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

595
  private void onRstStreamRead(int streamId, long errorCode) throws Http2Exception {
596
    Http2Exception tooManyRstStream = rstStreamCounter.countRstStream();
1✔
597
    if (tooManyRstStream != null) {
1✔
598
      throw tooManyRstStream;
1✔
599
    }
600

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

617
  @Override
618
  protected void onConnectionError(ChannelHandlerContext ctx, boolean outbound, Throwable cause,
619
      Http2Exception http2Ex) {
620
    logger.log(Level.FINE, "Connection Error", cause);
1✔
621
    connectionError = cause;
1✔
622
    super.onConnectionError(ctx, outbound, cause, http2Ex);
1✔
623
  }
1✔
624

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

647
  @Override
648
  public void handleProtocolNegotiationCompleted(
649
      Attributes attrs, InternalChannelz.Security securityInfo) {
650
    negotiationAttributes = attrs;
1✔
651
    this.securityInfo = securityInfo;
1✔
652
    super.handleProtocolNegotiationCompleted(attrs, securityInfo);
1✔
653
    NettyClientHandler.writeBufferingAndRemove(ctx().channel());
1✔
654
  }
1✔
655

656
  @Override
657
  public Attributes getEagAttributes() {
658
    return eagAttributes;
1✔
659
  }
660

661
  InternalChannelz.Security getSecurityInfo() {
662
    return securityInfo;
1✔
663
  }
664

665
  @VisibleForTesting
666
  KeepAliveManager getKeepAliveManagerForTest() {
667
    return keepAliveManager;
1✔
668
  }
669

670
  @VisibleForTesting
671
  void setKeepAliveManagerForTest(KeepAliveManager keepAliveManager) {
672
    this.keepAliveManager = keepAliveManager;
1✔
673
  }
1✔
674

675
  /**
676
   * Handler for the Channel shutting down.
677
   */
678
  @Override
679
  public void channelActive(ChannelHandlerContext ctx) throws Exception {
680
    tcpMetrics.channelActive(ctx.channel());
1✔
681
    super.channelActive(ctx);
1✔
682
  }
1✔
683

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

715
  WriteQueue getWriteQueue() {
716
    return serverWriteQueue;
1✔
717
  }
718

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

742
  @Override
743
  public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
744
    gracefulClose(ctx, new GracefulServerCloseCommand("app_requested"), promise);
1✔
745
    ctx.flush();
1✔
746
  }
1✔
747

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

759
  private void closeStreamWhenDone(ChannelPromise promise, Http2Stream stream) {
760
    promise.addListener(
1✔
761
        new ChannelFutureListener() {
1✔
762
          @Override
763
          public void operationComplete(ChannelFuture future) {
764
            serverStream(stream).complete();
1✔
765
          }
1✔
766
        });
767
  }
1✔
768

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

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

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

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

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

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

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

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

899
  private void respondWithHttpError(
900
      ChannelHandlerContext ctx, int streamId, int code, Status.Code statusCode, String msg) {
901
    respondWithHttpError(ctx, streamId, code, statusCode, msg, EmptyHttp2Headers.INSTANCE);
1✔
902
  }
1✔
903

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

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

924
  private Http2Stream requireHttp2Stream(int streamId) {
925
    Http2Stream stream = connection().stream(streamId);
1✔
926
    if (stream == null) {
1✔
927
      // This should never happen.
928
      throw new AssertionError("Stream does not exist: " + streamId);
×
929
    }
930
    return stream;
1✔
931
  }
932

933
  /**
934
   * Returns the server stream associated to the given HTTP/2 stream object.
935
   */
936
  private NettyServerStream.TransportState serverStream(Http2Stream stream) {
937
    return stream == null ? null : (NettyServerStream.TransportState) stream.getProperty(streamKey);
1✔
938
  }
939

940
  private Http2Exception newStreamException(int streamId, Throwable cause) {
941
    return Http2Exception.streamError(
×
942
        streamId, Http2Error.INTERNAL_ERROR, cause, Strings.nullToEmpty(cause.getMessage()));
×
943
  }
944

945
  private class FrameListener extends Http2FrameAdapter {
1✔
946
    private boolean firstSettings = true;
1✔
947

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

958
    @Override
959
    public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
960
        boolean endOfStream) throws Http2Exception {
961
      if (keepAliveManager != null) {
1✔
962
        keepAliveManager.onDataReceived();
1✔
963
      }
964
      NettyServerHandler.this.onDataRead(streamId, data, padding, endOfStream);
1✔
965
      return padding;
1✔
966
    }
967

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

986
    @Override
987
    public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode)
988
        throws Http2Exception {
989
      if (keepAliveManager != null) {
1✔
990
        keepAliveManager.onDataReceived();
1✔
991
      }
992
      NettyServerHandler.this.onRstStreamRead(streamId, errorCode);
1✔
993
    }
1✔
994

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

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

1035
  private final class KeepAlivePinger implements KeepAliveManager.KeepAlivePinger {
1036
    final ChannelHandlerContext ctx;
1037

1038
    KeepAlivePinger(ChannelHandlerContext ctx) {
1✔
1039
      this.ctx = ctx;
1✔
1040
    }
1✔
1041

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

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

1076
  private final class GracefulShutdown {
1077
    String goAwayMessage;
1078

1079
    /**
1080
     * The grace time between starting graceful shutdown and closing the netty channel,
1081
     * {@code null} is unspecified.
1082
     */
1083
    @CheckForNull
1084
    Long graceTimeInNanos;
1085

1086
    /**
1087
     * True if ping is Acked or ping is timeout.
1088
     */
1089
    boolean pingAckedOrTimeout;
1090

1091
    Future<?> pingFuture;
1092

1093
    GracefulShutdown(String goAwayMessage,
1094
        @Nullable Long graceTimeInNanos) {
1✔
1095
      this.goAwayMessage = goAwayMessage;
1✔
1096
      this.graceTimeInNanos = graceTimeInNanos;
1✔
1097
    }
1✔
1098

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

1110
      pingFuture = ctx.executor().schedule(
1✔
1111
          new Runnable() {
1✔
1112
            @Override
1113
            public void run() {
1114
              secondGoAwayAndClose(ctx);
1✔
1115
            }
1✔
1116
          },
1117
          GRACEFUL_SHUTDOWN_PING_TIMEOUT_NANOS,
1✔
1118
          TimeUnit.NANOSECONDS);
1119

1120
      encoder().writePing(ctx, false /* isAck */, GRACEFUL_SHUTDOWN_PING, ctx.newPromise());
1✔
1121
    }
1✔
1122

1123
    void secondGoAwayAndClose(ChannelHandlerContext ctx) {
1124
      if (pingAckedOrTimeout) {
1✔
1125
        return;
×
1126
      }
1127
      pingAckedOrTimeout = true;
1✔
1128

1129
      checkNotNull(pingFuture, "pingFuture");
1✔
1130
      pingFuture.cancel(false);
1✔
1131

1132
      // send the second GOAWAY with last stream id
1133
      goAway(
1✔
1134
          ctx,
1135
          connection().remote().lastStreamCreated(),
1✔
1136
          Http2Error.NO_ERROR.code(),
1✔
1137
          ByteBufUtil.writeAscii(ctx.alloc(), goAwayMessage),
1✔
1138
          ctx.newPromise());
1✔
1139

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

1153
    private long graceTimeOverrideMillis(long originalMillis) {
1154
      if (graceTimeInNanos == null) {
1✔
1155
        return originalMillis;
1✔
1156
      }
1157
      if (graceTimeInNanos == MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE) {
1✔
1158
        // netty treats -1 as "no timeout"
1159
        return -1L;
1✔
1160
      }
1161
      return TimeUnit.NANOSECONDS.toMillis(graceTimeInNanos);
1✔
1162
    }
1163
  }
1164

1165
  // Use a frame writer so that we know when frames are through flow control and actually being
1166
  // written.
1167
  private static class WriteMonitoringFrameWriter extends DecoratingHttp2FrameWriter {
1168
    private final KeepAliveEnforcer keepAliveEnforcer;
1169

1170
    public WriteMonitoringFrameWriter(Http2FrameWriter delegate,
1171
        KeepAliveEnforcer keepAliveEnforcer) {
1172
      super(delegate);
1✔
1173
      this.keepAliveEnforcer = keepAliveEnforcer;
1✔
1174
    }
1✔
1175

1176
    @Override
1177
    public ChannelFuture writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
1178
        int padding, boolean endStream, ChannelPromise promise) {
1179
      keepAliveEnforcer.resetCounters();
1✔
1180
      return super.writeData(ctx, streamId, data, padding, endStream, promise);
1✔
1181
    }
1182

1183
    @Override
1184
    public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
1185
        int padding, boolean endStream, ChannelPromise promise) {
1186
      keepAliveEnforcer.resetCounters();
1✔
1187
      return super.writeHeaders(ctx, streamId, headers, padding, endStream, promise);
1✔
1188
    }
1189

1190
    @Override
1191
    public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
1192
        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream,
1193
        ChannelPromise promise) {
1194
      keepAliveEnforcer.resetCounters();
×
1195
      return super.writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive,
×
1196
          padding, endStream, promise);
1197
    }
1198
  }
1199

1200
  private static final class Http2RstCounterEncoder extends DecoratingHttp2ConnectionEncoder {
1201
    private final RstStreamCounter rstStreamCounter;
1202
    private Http2LifecycleManager lifecycleManager;
1203

1204
    Http2RstCounterEncoder(Http2ConnectionEncoder encoder, RstStreamCounter rstStreamCounter) {
1205
      super(encoder);
1✔
1206
      this.rstStreamCounter = rstStreamCounter;
1✔
1207
    }
1✔
1208

1209
    @Override
1210
    public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
1211
      this.lifecycleManager = lifecycleManager;
1✔
1212
      super.lifecycleManager(lifecycleManager);
1✔
1213
    }
1✔
1214

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

1234
  private static final class RstStreamCounter {
1235
    private final int maxRstCount;
1236
    private final long maxRstPeriodNanos;
1237
    private final Ticker ticker;
1238
    private int rstCount;
1239
    private long lastRstNanoTime;
1240

1241
    RstStreamCounter(int maxRstCount, long maxRstPeriodNanos, Ticker ticker) {
1✔
1242
      checkArgument(maxRstCount >= 0, "maxRstCount must be non-negative: %s", maxRstCount);
1✔
1243
      this.maxRstCount = maxRstCount;
1✔
1244
      this.maxRstPeriodNanos = maxRstPeriodNanos;
1✔
1245
      this.ticker = checkNotNull(ticker, "ticker");
1✔
1246
      this.lastRstNanoTime = ticker.read();
1✔
1247
    }
1✔
1248

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

1275
  private static class ServerChannelLogger extends ChannelLogger {
1276
    private static final Logger log = Logger.getLogger(ChannelLogger.class.getName());
1✔
1277

1278
    @Override
1279
    public void log(ChannelLogLevel level, String message) {
1280
      log.log(toJavaLogLevel(level), message);
1✔
1281
    }
1✔
1282

1283
    @Override
1284
    public void log(ChannelLogLevel level, String messageFormat, Object... args) {
1285
      log(level, MessageFormat.format(messageFormat, args));
1✔
1286
    }
1✔
1287
  }
1288

1289
  private static Level toJavaLogLevel(ChannelLogLevel level) {
1290
    switch (level) {
1✔
1291
      case ERROR:
1292
        return Level.FINE;
×
1293
      case WARNING:
1294
        return Level.FINER;
×
1295
      default:
1296
        return Level.FINEST;
1✔
1297
    }
1298
  }
1299
}
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