private ChannelFuture resetStream(final ChannelHandlerContext ctx, final Http2Stream stream, long errorCode, ChannelPromise promise) { promise = promise.unvoid(); if (stream.isResetSent()) { return promise.setSuccess(); if (stream.state() == IDLE || connection().local().created(stream) && !stream.isHeadersSent() && !stream.isPushPromiseSent()) { future = promise.setSuccess(); } else { future = frameWriter().writeRstStream(ctx, stream.id(), errorCode, promise); stream.resetSent(); if (future.isDone()) { processRstStreamWriteResult(ctx, stream, future); } else { future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception {
void retryPushNotificationFromStream(final ChannelHandlerContext context, final int streamId) { final Http2Stream stream = this.connection().stream(streamId); final PushNotificationPromise responsePromise = stream.removeProperty(this.responsePromisePropertyKey); final ChannelPromise writePromise = context.channel().newPromise(); this.writePushNotification(context, responsePromise, writePromise); }
public UniformStreamByteDistributor(Http2Connection connection) { // Add a state for the connection. stateKey = connection.newKey(); Http2Stream connectionStream = connection.connectionStream(); connectionStream.setProperty(stateKey, new State(connectionStream)); // Register for notification of new streams. connection.addListener(new Http2ConnectionAdapter() { @Override public void onStreamAdded(Http2Stream stream) { stream.setProperty(stateKey, new State(stream)); } @Override public void onStreamClosed(Http2Stream stream) { state(stream).close(); } }); }
public DelegatingDecompressorFrameListener(Http2Connection connection, Http2FrameListener listener, boolean strict) { super(listener); this.connection = connection; this.strict = strict; propertyKey = connection.newKey(); connection.addListener(new Http2ConnectionAdapter() { @Override public void onStreamRemoved(Http2Stream stream) { final Http2Decompressor decompressor = decompressor(stream); if (decompressor != null) { cleanup(decompressor); } } }); }
final boolean consumeBytes(int streamId, int bytes) throws Http2Exception { Http2Stream stream = connection().stream(streamId); // Upgraded requests are ineligible for stream control. We add the null check // in case the stream has been deregistered. if (stream != null && streamId == Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) { Boolean upgraded = stream.getProperty(upgradeKey); if (Boolean.TRUE.equals(upgraded)) { return false; } } return connection().local().flowController().consumeBytes(stream, bytes); }
Http2Headers headers, int padding, ChannelPromise promise) { try { if (connection.goAwayReceived()) { throw connectionError(PROTOCOL_ERROR, "Sending PUSH_PROMISE after GO_AWAY received."); connection.local().reservePushStream(promisedStreamId, stream); Throwable failureCause = future.cause(); if (failureCause == null) { stream.pushPromiseSent(); if (!future.isSuccess()) { } catch (Throwable t) { lifecycleManager.onError(ctx, true, t); promise.tryFailure(t); return promise;
private void writePushNotification(final ChannelHandlerContext context, final PushNotificationPromise responsePromise, final ChannelPromise writePromise) { if (context.channel().isActive()) { final int streamId = this.connection().local().incrementAndGetNextStreamId(); final ChannelPromise headersPromise = context.newPromise(); this.encoder().writeHeaders(context, streamId, headers, 0, false, headersPromise); log.trace("Wrote headers on stream {}: {}", streamId, headers); final ByteBuf payloadBuffer = context.alloc().ioBuffer(INITIAL_PAYLOAD_BUFFER_CAPACITY); payloadBuffer.writeBytes(pushNotification.getPayload().getBytes(StandardCharsets.UTF_8)); final ChannelPromise dataPromise = context.newPromise(); this.encoder().writeData(context, streamId, payloadBuffer, 0, true, dataPromise); log.trace("Wrote payload on stream {}: {}", streamId, pushNotification.getPayload()); promiseCombiner.finish(writePromise); writePromise.addListener(new GenericFutureListener<ChannelPromise>() { writePromise.tryFailure(STREAMS_EXHAUSTED_EXCEPTION); context.channel().close(); writePromise.tryFailure(STREAM_CLOSED_BEFORE_REPLY_EXCEPTION);
@Override public void push(final String method, final String path, final Map<String, Object> headers) { ctx.channel().eventLoop().execute(() -> { AsciiString streamIdHeader = HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(); Http2Connection connection = encoder.connection(); int nextStreamId = connection.local().incrementAndGetNextStreamId(); Http2Headers h2headers = new DefaultHttp2Headers() .path(path) .method(method) .authority(authority) .scheme(scheme); headers.forEach((n, v) -> h2headers.add(n, v.toString())); encoder.writePushPromise(ctx, streamId, nextStreamId, h2headers, 0, ctx.newPromise()); // TODO: Is there another way of handling a push promise? DefaultFullHttpRequest pushRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(method.toUpperCase()), path, Unpooled.EMPTY_BUFFER, new DefaultHttpHeaders(false).set(streamIdHeader, nextStreamId), EmptyHttpHeaders.INSTANCE); ctx.pipeline().fireChannelRead(pushRequest); ctx.pipeline().fireChannelReadComplete(); }); }
final boolean exclusive, final int padding, final boolean endOfStream, ChannelPromise promise) { try { Http2Stream stream = connection.stream(streamId); if (stream == null) { try { stream = connection.local().createStream(streamId, endOfStream); } catch (Http2Exception cause) { if (connection.remote().mayHaveCreatedStream(streamId)) { promise.tryFailure(new IllegalStateException("Stream no longer exists: " + streamId, cause)); return promise; throw new IllegalStateException("Stream " + stream.id() + " in unexpected state " + stream.state()); boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream); if (endOfStream) { final Http2Stream finalStream = stream; promise = promise.unvoid().addListener(closeStreamLocalListener); weight, exclusive, padding, endOfStream, promise); Throwable failureCause = future.cause(); if (failureCause == null) { stream.headersSent(isInformational); if (!future.isSuccess()) {
final ByteBuf debugData, ChannelPromise promise) { try { promise = promise.unvoid(); final Http2Connection connection = connection(); if (connection().goAwaySent()) { if (lastStreamId == connection().remote().lastStreamKnownByPeer()) { return promise.setSuccess(); if (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); ChannelFuture future = frameWriter().writeGoAway(ctx, lastStreamId, errorCode, debugData, promise); } catch (Throwable cause) { // Make sure to catch Throwable because we are doing a retain() in this method. debugData.release(); return promise.setFailure(cause);
void retryPushNotificationFromStream(final ChannelHandlerContext context, final int streamId) { final Http2Stream stream = this.connection().stream(streamId); final PushNotificationPromise responsePromise = stream.removeProperty(this.responsePromisePropertyKey); final ChannelPromise writePromise = context.channel().newPromise(); this.writePushNotification(context, responsePromise, writePromise); writePromise.addListener(new GenericFutureListener<Future<Void>>() { @Override public void operationComplete(final Future<Void> writeFuture) { if (!writeFuture.isSuccess()) { responsePromise.tryFailure(writeFuture.cause()); } } }); }
if (streamCreatedAfterGoAwaySent(streamId)) { logger.info("{} ignoring {} frame for stream {}. Stream sent after GOAWAY sent", ctx.channel(), frameName, streamId); return true; } else if (stream.isResetSent() || streamCreatedAfterGoAwaySent(streamId)) { logger.info("{} ignoring {} frame for stream {} {}", ctx.channel(), frameName, stream.isResetSent() ? "RST_STREAM sent." : ("Stream created after GOAWAY sent. Last known stream by peer " + connection.remote().lastStreamKnownByPeer()));
@Override protected ChannelFuture doWriteReset(int id, int streamId, Http2Error error) { final Http2Stream stream = encoder.connection().stream(streamId); // Send a RST_STREAM frame only for an active stream which did not send a RST_STREAM frame already. if (stream != null && !stream.isResetSent()) { return encoder.writeRstStream(ctx, streamId, error.code(), ctx.newPromise()); } return ctx.writeAndFlush(Unpooled.EMPTY_BUFFER); }
@Override public void write(ChannelHandlerContext ctx, int allowedBytes) { boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream); if (promise.isVoid()) { promise = ctx.newPromise(); } promise.addListener(this); ChannelFuture f = frameWriter.writeHeaders(ctx, stream.id(), headers, streamDependency, weight, exclusive, padding, endOfStream, promise); // Writing headers may fail during the encode state if they violate HPACK limits. Throwable failureCause = f.cause(); if (failureCause == null) { // This just sets internal stream state which is used elsewhere in the codec and doesn't // necessarily mean the write will complete successfully. stream.headersSent(isInformational); } }
@Override protected ChannelFuture doWriteData(int id, int streamId, HttpData data, boolean endStream) { if (isStreamPresentAndWritable(streamId)) { // Write to an existing stream. return encoder.writeData(ctx, streamId, toByteBuf(data), 0, endStream, ctx.newPromise()); } if (encoder.connection().local().mayHaveCreatedStream(streamId)) { // Can't write to an outdated (closed) stream. ReferenceCountUtil.safeRelease(data); return data.isEmpty() ? ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) : newFailedFuture(ClosedPublisherException.get()); } // Cannot start a new stream with a DATA frame. It must start with a HEADERS frame. ReferenceCountUtil.safeRelease(data); return newFailedFuture(new IllegalStateException( "cannot start a new stream " + streamId + " with a DATA frame")); }
encoder().writeHeaders(ctx, headersFrame.stream().id(), headersFrame.headers(), headersFrame.padding(), headersFrame.isEndStream(), promise); } else { final DefaultHttp2FrameStream stream = (DefaultHttp2FrameStream) headersFrame.stream(); final Http2Connection connection = connection(); final int streamId = connection.local().incrementAndGetNextStreamId(); if (streamId < 0) { promise.setFailure(new Http2NoMoreStreamIdsException()); return; final ChannelPromise writePromise = ctx.newPromise(); encoder().writeHeaders(ctx, streamId, headersFrame.headers(), headersFrame.padding(), headersFrame.isEndStream(), writePromise); if (writePromise.isDone()) { notifyHeaderWritePromise(writePromise, promise); } else { numBufferedStreams++; writePromise.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception {
@Override public void write(ChannelHandlerContext ctx, int allowedBytes) { boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream); // The code is currently requiring adding this listener before writing, in order to call onError() before // closeStreamLocal(). promise.addListener(this); ChannelFuture f = frameWriter.writeHeaders(ctx, stream.id(), headers, streamDependency, weight, exclusive, padding, endOfStream, promise); // Writing headers may fail during the encode state if they violate HPACK limits. Throwable failureCause = f.cause(); if (failureCause == null) { // This just sets internal stream state which is used elsewhere in the codec and doesn't // necessarily mean the write will complete successfully. stream.headersSent(isInformational); } }
void writePushPromise(int streamId, Http2Headers headers, Handler<AsyncResult<Integer>> completionHandler) { int promisedStreamId = connection().local().incrementAndGetNextStreamId(); ChannelPromise promise = chctx.newPromise(); promise.addListener(fut -> { if (fut.isSuccess()) { completionHandler.handle(Future.succeededFuture(promisedStreamId)); } else { completionHandler.handle(Future.failedFuture(fut.cause())); } }); EventExecutor executor = chctx.executor(); if (executor.inEventLoop()) { _writePushPromise(streamId, promisedStreamId, headers, promise); } else { executor.execute(() -> { _writePushPromise(streamId, promisedStreamId, headers, promise); }); } }
final int lastStreamId = conn.local().lastStreamKnownByPeer(); if (lastStreamId < 0 || // Did not receive a GOAWAY yet or encoder.writeRstStream(ctx, streamId, Http2Error.CANCEL.code(), ctx.newPromise()); ctx.flush(); } else {
/** * Iterates over all active HTTP/2 streams. * * <p>This method must not be called outside of the event loop. */ final void forEachActiveStream(final Http2FrameStreamVisitor streamVisitor) throws Http2Exception { assert ctx.executor().inEventLoop(); connection().forEachActiveStream(new Http2StreamVisitor() { @Override public boolean visit(Http2Stream stream) { try { return streamVisitor.visit((Http2FrameStream) stream.getProperty(streamKey)); } catch (Throwable cause) { onError(ctx, false, cause); return false; } } }); }