@Override
  public ChannelFuture goAway(
      final ChannelHandlerContext ctx,
      final int lastStreamId,
      final long errorCode,
      final ByteBuf debugData,
      ChannelPromise promise) {
    try {
      final Http2Connection connection = connection();
      if (connection.goAwaySent() && lastStreamId > connection.remote().lastStreamKnownByPeer()) {
        throw connectionError(
            PROTOCOL_ERROR,
            "Last stream identifier must not increase between "
                + "sending multiple GOAWAY frames (was '%d', is '%d').",
            connection.remote().lastStreamKnownByPeer(),
            lastStreamId);
      }
      connection.goAwaySent(lastStreamId, errorCode, debugData);

      // Need to retain before we write the buffer because if we do it after the refCnt could
      // already be 0 and
      // result in an IllegalRefCountException.
      debugData.retain();
      ChannelFuture future =
          frameWriter().writeGoAway(ctx, lastStreamId, errorCode, debugData, promise);

      if (future.isDone()) {
        processGoAwayWriteResult(ctx, lastStreamId, errorCode, debugData, future);
      } else {
        future.addListener(
            new ChannelFutureListener() {
              @Override
              public void operationComplete(ChannelFuture future) throws Exception {
                processGoAwayWriteResult(ctx, lastStreamId, errorCode, debugData, future);
              }
            });
      }

      return future;
    } catch (
        Throwable
            cause) { // Make sure to catch Throwable because we are doing a retain() in this method.
      debugData.release();
      return promise.setFailure(cause);
    }
  }
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
      // Connection has terminated, close the encoder and decoder.
      encoder().close();
      decoder().close();

      final Http2Connection connection = connection();
      // Check if there are streams to avoid the overhead of creating the ChannelFuture.
      if (connection.numActiveStreams() > 0) {
        final ChannelFuture future = ctx.newSucceededFuture();
        connection.forEachActiveStream(
            new Http2StreamVisitor() {
              @Override
              public boolean visit(Http2Stream stream) throws Http2Exception {
                closeStream(stream, future);
                return true;
              }
            });
      }
    }
  public DefaultInboundFlowController(Http2Connection connection) {
    if (connection == null) {
      throw new NullPointerException("connecton");
    }
    connection.addListener(
        new Http2Connection.Listener() {
          @Override
          public void streamCreated(int streamId) {
            streamWindows.put(streamId, new StreamWindow(streamId));
          }

          @Override
          public void streamClosed(int streamId) {
            streamWindows.remove(streamId);
          }
        });
  }
  @Override
  protected void notifyListenerOnDataRead(
      ChannelHandlerContext ctx,
      int streamId,
      ByteBuf data,
      int padding,
      boolean endOfStream,
      Http2FrameListener listener)
      throws Http2Exception {
    final Http2Stream stream = connection.stream(streamId);
    final EmbeddedChannel decoder = stream == null ? null : stream.decompressor();
    if (decoder == null) {
      super.notifyListenerOnDataRead(ctx, streamId, data, padding, endOfStream, listener);
    } else {
      // call retain here as it will call release after its written to the channel
      decoder.writeInbound(data.retain());
      ByteBuf buf = nextReadableBuf(decoder);
      if (buf == null) {
        if (endOfStream) {
          super.notifyListenerOnDataRead(
              ctx, streamId, Unpooled.EMPTY_BUFFER, padding, true, listener);
        }
        // END_STREAM is not set and the data could not be decoded yet.
        // The assumption has to be there will be more data frames to complete the decode.
        // We don't have enough information here to know if this is an error.
      } else {
        for (; ; ) {
          final ByteBuf nextBuf = nextReadableBuf(decoder);
          if (nextBuf == null) {
            super.notifyListenerOnDataRead(ctx, streamId, buf, padding, endOfStream, listener);
            break;
          } else {
            super.notifyListenerOnDataRead(ctx, streamId, buf, padding, false, listener);
          }
          buf = nextBuf;
        }
      }

      if (endOfStream) {
        cleanup(stream, decoder);
      }
    }
  }
 /**
  * Checks if a new decoder object is needed for the stream identified by {@code streamId}. This
  * method will modify the {@code content-encoding} header contained in {@code builder}.
  *
  * @param streamId The identifier for the headers inside {@code builder}
  * @param builder Object representing headers which have been read
  * @param endOfStream Indicates if the stream has ended
  * @throws Http2Exception If the {@code content-encoding} is not supported
  */
 private void initDecoder(int streamId, Http2Headers headers, boolean endOfStream)
     throws Http2Exception {
   // Convert the names into a case-insensitive map.
   final Http2Stream stream = connection.stream(streamId);
   if (stream != null) {
     EmbeddedChannel decoder = stream.decompressor();
     if (decoder == null) {
       if (!endOfStream) {
         // Determine the content encoding.
         AsciiString contentEncoding = headers.get(CONTENT_ENCODING_LOWER_CASE);
         if (contentEncoding == null) {
           contentEncoding = IDENTITY;
         }
         decoder = newContentDecoder(contentEncoding);
         if (decoder != null) {
           stream.decompressor(decoder);
           // Decode the content and remove or replace the existing headers
           // so that the message looks like a decoded message.
           AsciiString targetContentEncoding = getTargetContentEncoding(contentEncoding);
           if (IDENTITY.equalsIgnoreCase(targetContentEncoding)) {
             headers.remove(CONTENT_ENCODING_LOWER_CASE);
           } else {
             headers.set(CONTENT_ENCODING_LOWER_CASE, targetContentEncoding);
           }
         }
       }
     } else if (endOfStream) {
       cleanup(stream, decoder);
     }
     if (decoder != null) {
       // The content length will be for the compressed data.  Since we will decompress the data
       // this content-length will not be correct.  Instead of queuing messages or delaying sending
       // header frames...just remove the content-length header
       headers.remove(CONTENT_LENGTH_LOWER_CASE);
     }
   }
 }
  /**
   * Create a new instance.
   *
   * @param strict
   *     <ul>
   *       <li>{@code true} to use use strict handling of deflate if used
   *       <li>{@code false} be more lenient with decompression
   *     </ul>
   */
  public DecompressorHttp2FrameReader(Http2Connection connection, boolean strict) {
    this.connection = connection;
    this.strict = strict;

    connection.addListener(CLEAN_UP_LISTENER);
  }
 /**
  * Returns the client preface string if this is a client connection, otherwise returns {@code
  * null}.
  */
 private static ByteBuf clientPrefaceString(Http2Connection connection) {
   return connection.isServer() ? connectionPrefaceBuf() : null;
 }
 /**
  * Create a new {@link FullHttpMessage} based upon the current connection parameters
  *
  * @param streamId The stream id to create a message for
  * @param headers The headers associated with {@code streamId}
  * @param validateHttpHeaders
  *     <ul>
  *       <li>{@code true} to validate HTTP headers in the http-codec
  *       <li>{@code false} not to validate HTTP headers in the http-codec
  *     </ul>
  *
  * @throws Http2Exception
  */
 protected FullHttpMessage newMessage(
     int streamId, Http2Headers headers, boolean validateHttpHeaders) throws Http2Exception {
   return connection.isServer()
       ? HttpConversionUtil.toHttpRequest(streamId, headers, validateHttpHeaders)
       : HttpConversionUtil.toHttpResponse(streamId, headers, validateHttpHeaders);
 }