@Override public ChannelFuture resetStream( final ChannelHandlerContext ctx, int streamId, long errorCode, final ChannelPromise promise) { final Http2Stream stream = connection().stream(streamId); if (stream == null || stream.isResetSent()) { // Don't write a RST_STREAM frame if we are not aware of the stream, or if we have already // written one. return promise.setSuccess(); } ChannelFuture future = frameWriter().writeRstStream(ctx, streamId, errorCode, promise); // Synchronously set the resetSent flag to prevent any subsequent calls // from resulting in multiple reset frames being sent. stream.resetSent(); future.addListener( new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { closeStream(stream, promise); } else { // The connection will be closed and so no need to change the resetSent flag to false. onConnectionError(ctx, future.cause(), null); } } }); return future; }
/** * Closes the remote side of the given stream. If this causes the stream to be closed, adds a hook * to close the channel after the given future completes. * * @param stream the stream to be half closed. * @param future If closing, the future after which to close the channel. */ @Override public void closeStreamRemote(Http2Stream stream, ChannelFuture future) { switch (stream.state()) { case HALF_CLOSED_REMOTE: case OPEN: stream.closeRemoteSide(); break; default: closeStream(stream, future); break; } }
/** * Closes the local side of the given stream. If this causes the stream to be closed, adds a hook * to close the channel after the given future completes. * * @param stream the stream to be half closed. * @param future If closing, the future after which to close the channel. */ @Override public void closeStreamLocal(Http2Stream stream, ChannelFuture future) { switch (stream.state()) { case HALF_CLOSED_LOCAL: case OPEN: stream.closeLocalSide(); break; default: closeStream(stream, future); break; } }
@Override public void streamRemoved(Http2Stream stream) { final EmbeddedChannel decoder = stream.decompressor(); if (decoder != null) { cleanup(stream, decoder); } }
@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); } } }
/** * Release remaining content from the {@link EmbeddedChannel} and remove the decoder from the * {@link Http2Stream}. * * @param stream The stream for which {@code decoder} is the decompressor for * @param decoder The decompressor for {@code stream} */ private static void cleanup(Http2Stream stream, EmbeddedChannel decoder) { if (decoder.finish()) { for (; ; ) { final ByteBuf buf = decoder.readInbound(); if (buf == null) { break; } buf.release(); } } stream.decompressor(null); }
/** * 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); } } }
@Override public void closeStream(final Http2Stream stream, ChannelFuture future) { stream.close(); if (future.isDone()) { checkCloseConnection(future); } else { future.addListener( new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { checkCloseConnection(future); } }); } }
@Override public void onStreamRemoved(Http2Stream stream) { removeMessage(stream.id()); }