@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); }